{"id":27065633,"url":"https://github.com/teacher-zhou/componentbuilder","last_synced_at":"2025-08-03T23:07:53.374Z","repository":{"id":39711020,"uuid":"429722144","full_name":"teacher-zhou/ComponentBuilder","owner":"teacher-zhou","description":"An automation framework to create Blazor component","archived":false,"fork":false,"pushed_at":"2024-03-07T03:08:08.000Z","size":2116,"stargazers_count":48,"open_issues_count":2,"forks_count":4,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-08-01T01:08:50.073Z","etag":null,"topics":["blazor","component","razor","rendertreebuilder"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/teacher-zhou.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOGS.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2021-11-19T08:26:20.000Z","updated_at":"2025-07-14T06:11:42.000Z","dependencies_parsed_at":"2023-11-16T03:22:50.011Z","dependency_job_id":"dbb22b17-6cf5-4640-94ae-5e5acfbe889f","html_url":"https://github.com/teacher-zhou/ComponentBuilder","commit_stats":{"total_commits":239,"total_committers":3,"mean_commits":79.66666666666667,"dds":0.08368200836820083,"last_synced_commit":"be21e04f73aaceae342f88d905963126fc2932e0"},"previous_names":["achievedowner/componentbuilder"],"tags_count":35,"template":false,"template_full_name":null,"purl":"pkg:github/teacher-zhou/ComponentBuilder","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teacher-zhou%2FComponentBuilder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teacher-zhou%2FComponentBuilder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teacher-zhou%2FComponentBuilder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teacher-zhou%2FComponentBuilder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/teacher-zhou","download_url":"https://codeload.github.com/teacher-zhou/ComponentBuilder/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teacher-zhou%2FComponentBuilder/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268625009,"owners_count":24280188,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-03T02:00:12.545Z","response_time":2577,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["blazor","component","razor","rendertreebuilder"],"created_at":"2025-04-05T18:18:37.393Z","updated_at":"2025-08-03T23:07:53.225Z","avatar_url":"https://github.com/teacher-zhou.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ComponentBuilder\n\nThe best automation framework for RCL(Razor Component Library) to help you easy and quickly building your own Razor Component Library\n\n[中文介绍](README.zh-cn.md) | [Quick Start](./docs/readme.md) | [Document](https://playermaker.gitbook.io/componentbuilder/english/introduction)\n\n![Latest Version](https://img.shields.io/github/v/release/AchievedOwner/ComponentBuilder)\n\nv4.x supports\n![.net6](https://img.shields.io/badge/.net-6-blue)\n![.net7](https://img.shields.io/badge/.net-7-blue)\n\nv5.x supports\n![.net8](https://img.shields.io/badge/.net-8-blue)\n\n## :sparkles: Features\n* OOP mindset creating component\n* Attribute first, easy define CSS from parameters\n* Easy to associate with components via Attributes\n* Cusomization CSS and attributes of component by coding logic\n* Support `Pre-definition` for components with simular parameters\n* New lifecycle definition of Component with interceptor design pattern\n* Renderer pipeline pattern to regonize dynamic render of components\n\n\n## :rainbow: Quick Start\n\n**Only change `ComponentBase` to `BlazorComponetBase` for derived component class**\n\n* Sample to create a button component with C# class:\n```csharp\n[HtmlTag(\"button\")] //define HTML element tag\n[CssClass(\"btn\")] //define component necessary CSS class\npublic class Button : BlazorComponentBase, IHasChildContent, IHasOnClick\n{\n\t[Parameter][CssClass(\"active\")]public bool Active { get; set; } \t\n\t[Parameter][CssClass(\"btn-\")]public Color? Color { get; set; } \n\t[Parameter]public RenderFragment? ChildContent { get; set; }\n\t[Parameter][HtmlData(\"tooltip\")]public string? Tooltip { get; set; }\n\t[Parameter][HtmlAttribute(\"onclick\")]public EventCallback\u003cClickEventArgs\u003e OnClick { get; set; }\n\t[Parameter][HtmlAttribute]public string? Title { get; set; }\n}\n\npublic enum Color\n{\n\tPrimary,\n\tSecondary,\n\t[CssClass(\"info\")]Information,\n}\n```\nOR you also can define most part of automation features in razor file:\n\n```cshtml\n@inherits BlazorComponentBase\n\n\u003c!--Bind GetAttributes() for @attributes to getting automation features--\u003e\n\n\u003cbutton @attributes=\"@GetAttributes()\"\u003e \n\t@ChildContent\n\u003c/button\u003e\n\n@code{\n\t[CssClass(\"btn\")]\n\tpublic Button()\n\t{\n\t}\n\n\t[Parameter][CssClass(\"active\")]public bool Active { get; set; } \t\n\t[Parameter][CssClass(\"btn-\")]public Color? Color { get; set; } \n\t[Parameter]public RenderFragment? ChildContent { get; set; } \n\t[Parameter][HtmlData(\"tooltip\")]public string? Tooltip { get; set; }\n\t[Parameter][HtmlAttribute(\"onclick\")]public EventCallback\u003cClickEventArgs\u003e OnClick { get; set; } \n\t[Parameter][HtmlAttribute]public string? Title { get; set; }\n\t\n\tpublic enum Color\n\t{\n\t\tPrimary,\n\t\tSecondary,\n\t\t[CssClass(\"info\")]Information,\n\t}\n}\n```\n\n* Use component\n```html\n\u003c!--razor--\u003e\n\u003cButton Color=\"Color.Primary\"\u003eSubmit\u003c/Button\u003e\n\u003c!--html--\u003e\n\u003cbutton class=\"btn btn-primary\"\u003eSubmit\u003c/button\u003e\n\n\u003c!--razor--\u003e\n\u003cButton Active Tooltip=\"active button\" Color=\"Color.Information\" Title=\"click me\"\u003eActive Button\u003c/Button\u003e\n\u003c!--html--\u003e\n\u003cbutton class=\"btn btn-info active\" data-tooltip=\"active button\" title=\"click me\"\u003eActive Button\u003c/button\u003e\n```\n\n\n## :information_source: Logical CSS/Style/Attributes\n* Using different logic for groups creates code with OOP mindset\n\n```csharp\nprotected override void BuildCssClass(ICssClassBuilder builder)\n{\n\tif(builder.Contains(\"annotation-enter\"))\n\t{\n\t\tbuilder.Remove(\"annotation-exist\");\n\t}\n\telse\n\t{\n\t\tbuilder.Append(\"annotation-enter\").Append(\"annotation-exist\");\n\t}\n}\n\nprotected override void BuildStyle(IStyleBuilder builder)\n{\n\tif(Height.HasValue)\n\t{\n\t\tbuilder.Append($\"height:{Height}px\");\n\t}\n}\n\nprotected override void BuildAttributes(IDictionary\u003cstring, object\u003e attributes)\n{\t\n\tif(attrbutes.ContainKey(\"data-toggle\"))\n\t{\n\t\tattributes[\"data-toggle\"] = \"collapse\";\n\t}\n}\n```\n\n## :children_crossing: Parent/Child component\n\nEasy to create parent/child pair components using `ParentComponentAttribute` and `ChildComponentAttribute\u003cTParent\u003e`\n\n* For `List` component class\n```csharp\n[ParentComponent] //auto creating the cascading parameter for current \n[HtmlTag(\"ul\")]\npublic class List : BlazorComponentBase, IHasChildContent\n{\n\n}\n```\n* For `ListItem` component class\n```cs\n[ChildComponent\u003cList\u003e] //Strong association with List\n[HtmlTag(\"li\")]\npublic class ListItem : BlazorComponentBase, IHasChildContent\n{\n\t[CascadingParameter]public List? CascadedList { get; set; }//Auto getting the instance of cascading parameter\n\n\t[Parameter] public RenderFragment? ChildContent { get; set; }\n}\n```\n### Use in blazor\n```html\n\u003cList\u003e\n\t\u003cListItem\u003e...\u003c/ListItem\u003e\n\u003c/List\u003e\n\n\u003cListItem /\u003e \u003c!--throw exception because ListItem must be the child component of List coponent--\u003e\n\n```\n\n### In .razor file\n\nIn razor file component, you should create cascading parameter by yourself\n\n`List.razor`:\n```html\n\u003cul @attributes=\"@GetAttributes()\"\u003e\n\t\u003cCascadingValue Value=\"this\"\u003e\n\t\t@ChildContent\n\t\u003c/CascadingValue\u003e\n\u003c/ul\u003e\n```\n\n`ListItem.razor`:\n```html\n\u003cli @attributes=\"GetAttributes()\"\u003e@ChildContent\u003c/li\u003e\n\n@code{\n\t[ChildComponent\u003cList\u003e]\n\tpublic ListItem()\n\t{\n\t}\n\n\t[CascadingParameter] public List? CascadedList { get; set; }\n\n\t[Parameter] public RenderFragment? ChildContent { get; set; }\n}\n```\n\n\n\n## :smile: Other extensions\n\n* Extensions for `RenderTreeBuilder`\n\u003e It's very useful for dynamic component creating using OOP mindset\n```cs\nbuilder.CreateElement(0, \"div\",\"any text\", new { @class=\"main\" });\t\t\n//\u003cdiv class=\"main\"\u003eany text\u003c/div\u003e\n\nbuilder.CreateComponent\u003cMyComponent\u003e(attributes: new { Visible = true }); \n//\u003cMyComponent Visible /\u003e\n\nbuilder.CreateCascadingValue\u003cT\u003e(value); \n//\u003cCascadingValue Value=\"this\"\u003e\u003c/CascadingValue\u003e\n```\n* FluentRenderTreeBuilder\n\u003e Write RenderTreeBuilder as fluent API\n\n```cs\n//import namespace\nusing ComponentBuilder.FluentRenderTree;\n\nbuilder.Element(\"p\", \"default-class\")\t\t// create \u003cp\u003e element with default class\n\t\t.Class(\"hover\", Hoverable)\t\t\t// append class if Hoverable parameter is true\n\t\t.Attribute(\"disabled\", Disabled)\t// add HTML attribute if Disabled is true\n\t\t.Data(\"trigger\", \"string\")\t\t\t// add data-trigger=\"string\" HTML attribute if String parameter not empty\n\t\t.Callback\u003cMouseEventArgs\u003e(\"onmouseover\", this, e =\u003e MyHandler())\t// add event named 'onmouseover' with a event handler code\n\t\t.Content(\"content text\")\t\t\t// add inner text for this element\n\t.Close()\n\n//HTML element generate like:\n\u003cp class=\"default-class hover\" data-trigger=\"string\" disabled\u003econtent text\u003c/p\u003e\n\n// normally in razor file:\n\u003cp class=\"default-class @(Hoverable?\"hover\":\"\")\" disabled=\"@Disabled\" data-trigger=\"string\" @onmouseover=\"@(e =\u003e MyHandler())\"\u003econtent text\u003c/p\u003e\n\n\nbuilder.Component\u003cMyComponent\u003e()\n\t\t.Parameter(m =\u003e m.Disabled, true)\n\t\t.Parameter(m =\u003e Size, 5)\n\t\t.ChildContent(\"My name is hello world\")\n\t.Close();\n\n```\n* Create dynamic Class/Style/Callback\n```cs\n//import namespace\nusing ComponentBuilder.JSInterop\n\n//create dynamic css class string\nHtmlHelper.Class.Append(\"class1\").Append(\"disabled\", Disabled).ToString();\n\n//create dynamic style string\nHtmlHelper.Style.Append($\"width:{Width}px\").Append($\"height:{Height}px\", Height.HasValue).ToString();\n\n//create dynamic EventCallback\nHtmlHelper.Callback.Create(this, ()=\u003e{ //action for callback });\n```\n* ComponentBuilder.JSInterop\n\u003e Interactive with C# and JS\n\n```js\nexport function sayHello(){\n\t//...\n}\n\nexport function getClient(){\n\t//..\n\treturn name;\n}\n```\n\n```cs\n\n\n@inject IJSRuntime JS\n\nvar module = JS.ImportAsync(\"./module.js\");\t//Import js module\n\nawait module.Module.InvokeVoidAsync(\"sayHello\");\nvar name = await module.Module.InvokeAsync\u003cstring\u003e(\"getClient\");\n```\n\n\n## :crossed_swords: Interceptors\nYou can intercept the lifecycle of component\n\n* Define an interceptor\n```csharp\npublic class LogInterceptor : ComponentInterceptorBase\n{\n\tprivate readonly ILogger\u003cLogInterceptor\u003e _logger;\n\tpublic LogInterceptor(ILogger\u003cLogInterceptor\u003e logger)\n\t{\n\t\t_logger = logger;\n\t}\n\n\t//Run in SetParameterAsync method is called\n\tpublic override void InterceptSetParameters(IBlazorComponent component, ParameterView parameters)\n\t{\n\t\tforeach(var item in parameters)\n\t\t{\n\t\t\t_logger.LogDebug($\"Key:{item.Name}, Value:{item.Value}\");\n\t\t}\n\t}\n}\n```\n* Register interceptor\n```csharp\nbuilder.Services.AddComponentBuilder(configure =\u003e {\n\tconfigure.Interceptors.Add\u003cLogInterceptor\u003e();\n})\n```\n\n![BlazorComponentBase Lifecycle](./asset/BlazorComponentBaseLifecycle.png)\n\n### Why interceptors?\nFollow SOLID pricipal when designing a component. So you no need break the lifecycle or using override any protected method such as `OnParameterSet` to create new HTML attribute whatever you want.\n\n\n## :recycle: Renderer Pipeline\nRecognize special case to render specified component\n```csharp\npublic class NavLinkComponentRender : IComponentRender\n{\n\tpublic bool Render(IBlazorComponent component, RenderTreeBuilder builder)\n\t{\n\t\tif ( component is IHasNavLink navLink )\n\t\t{\n\t\t\tbuilder.OpenComponent\u003cNavLink\u003e(0);\n\t\t\tbuilder.AddAttribute(1, nameof(NavLink.Match), navLink.Match);\n\t\t\tbuilder.AddAttribute(2, nameof(NavLink.ActiveClass), navLink.ActiveCssClass);\n\t\t\tbuilder.AddAttribute(3, nameof(NavLink.ChildContent), navLink.ChildContent);\n\t\t\tbuilder.AddMultipleAttributes(4, component.GetAttributes());\n\t\t\tbuilder.CloseComponent();\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t}\n}\n```\n* Register renderer in configuration\n```csharp\nbuilder.Services.AddComponentBuilder(configure =\u003e {\n\tconfigure.Renderers.Add\u003cNavLinkComponentRenderer\u003e();\n});\n```\n\n\n## :blue_book: Installation Guide\n\n* Install from `Nuget.org`\n\n```bash\nInstall-Package ComponentBuilder\n```\n\n* Register service\n\n```csharp\nbuilder.Services.AddComponentBuilder();\n\n//configure costomization such as Interceptors\nbuilder.Services.AddComponentBuilder(configure =\u003e {\n\t//...\n})\n```\n\n[Read document for more informations](https://playermaker.gitbook.io/componentbuilder)\n\n\n## :pencil: Component Library Solution Template\nUse `ComponentBuilder.Templates` to generate a razor component library solution and online demo site\n```bash\ndotnet new install ComponentBuilder.Templates\ndotnet new blazor-sln -n {YourRazorLibraryName}\n```\nMore information see [templates](./templates/readme.md)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fteacher-zhou%2Fcomponentbuilder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fteacher-zhou%2Fcomponentbuilder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fteacher-zhou%2Fcomponentbuilder/lists"}