{"id":13430888,"url":"https://github.com/conficient/BlazorDynamicList","last_synced_at":"2025-03-16T06:31:34.406Z","repository":{"id":37997336,"uuid":"184409974","full_name":"conficient/BlazorDynamicList","owner":"conficient","description":"Demonstrates dynamic component binding for a generic list","archived":false,"fork":false,"pushed_at":"2022-12-08T09:03:41.000Z","size":544,"stargazers_count":52,"open_issues_count":1,"forks_count":9,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-08-01T02:27:18.348Z","etag":null,"topics":["aspnetcore","blazor","demo"],"latest_commit_sha":null,"homepage":null,"language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/conficient.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-05-01T12:01:08.000Z","updated_at":"2023-11-21T14:23:59.000Z","dependencies_parsed_at":"2023-01-24T21:45:54.822Z","dependency_job_id":null,"html_url":"https://github.com/conficient/BlazorDynamicList","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/conficient%2FBlazorDynamicList","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/conficient%2FBlazorDynamicList/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/conficient%2FBlazorDynamicList/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/conficient%2FBlazorDynamicList/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/conficient","download_url":"https://codeload.github.com/conficient/BlazorDynamicList/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221656463,"owners_count":16858776,"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","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":["aspnetcore","blazor","demo"],"created_at":"2024-07-31T02:00:58.723Z","updated_at":"2024-10-27T09:31:03.400Z","avatar_url":"https://github.com/conficient.png","language":"C#","funding_links":[],"categories":["Sample Projects"],"sub_categories":["Others"],"readme":"# BlazorDynamicList sample\n\n[![Build Status](https://dev.azure.com/conficient/BlazorDynamicList/_apis/build/status/conficient.BlazorDynamicList?branchName=master)](https://dev.azure.com/conficient/BlazorDynamicList/_build/latest?definitionId=1\u0026branchName=master)\n\u003e Note: the code for this example uses Blazor WebAssembly 3.2.0 RTM\n\nThis demo application shows how Blazor's component model permits \nus to easily encapsulate code, UI and behaviours in reusable modules, \nand even load components dynamically in code.\n\nA demo is hosted at https://blazordynamiclist.azurewebsites.net \n\n## Background\n\nI currently have a large ASP.NET web application which has services\nfor a wide range of products and services it must support.\n\nEach product has its own settings, properties and behavior. Although I can\nencapsulate the product behaviours in class libraries, it's been very hard \nto create UI in these class libraries as neither ASP.NET or JavaScript \nreally lends themselves to this.\n\nThis results in a lot of supplier-specific and product-specific UI in \nthe ASP.NET project, resulting in a monolithic web app that is \nonly losely bound to the product code.\n\n### Blazor\n\nWhen Blazor came onto the scene in 2017, I was excited for two reasons. \n\nFirst, the ability to use C# in the client meant we no longer had to \nre-write the same C# code in JavaScript to get front-end behaviours. The \nserver and the client can share the models.\n\nSecondly, and in my opinion more importantly, Blazor's excellent component \nmodel permits us to encapsulate the UI in these libraries as well.\n\n### Proof of Concept\n\nSo I decided to write this project as a proof-of-concept. Could I encapsulate\nbehaviours and UI in libraries, and then handle generic lists of objects \nand display the correct UI for each one?\n\n## Overview\n\nThe business case is a product list, where each product has some common \nfeatures, e.g. `Name` and `Price` but also specific properties for each type.\n\n**Product1** has a `HasFlange` property and **Product2** has a `Grommets` \nproperty. We want to have a custom view that shows each product according to \nits type and is contained in the same library that defines the ProductX class.\n\n`Component1` displays a Product1 and `Component2` displays a Product2. \nIn a further complication I decided that any Product1 that has `HasFlange` \nset as true should use the `Component1b` component.\n\n## Creation\n\nThe project uses the Blazor (ASP.NET Core hosted) template as a starting point.\n\n### Product Libraries\n\nI created a .NET Standard library **BaseClasses** to hold a common `ProductBase` \nabstract base class, which has `ID`, `Name`, `Price` and `Image` properties. \nIt also defines an abstract method `GetViewComponent()` that returns the \ntype of the Razor Component we want to use to view the product.\n\nI then added new Blazor library projects **Library1** and **Library2** using \nthe console command `dotnet new blazorlib -o Library1` etc. These represent \nour two product libraries. `Product1` and `Product2` inherit from `ProductBase`.\n\nI also created a **RepositoryLib** .NET Standard library which represents \nour 'datasource' (which in a real-world example would be a database of \nsome kind). The `ProductRepository` class just generates random products \nof either type using the `GetProducts()` method.\n\n### Index\n\nThe `Index.razor` page on the client I used to test the basic component \nbinding. `Component1` and `Component2` are placed in the HTML with binding \nto page properties `p1` and `p2` respectively. \n\nThese are initially `null` but are populated using the button. You should \nsee the products render when the button is clicked.\n\n![Index.razor](./img/index.png)\n\n\n### FetchData\n\nI modified the `FetchData.razor` page to use a list of `ProductBase`. The\ninitalization code calls the WebAPI method on the server to fetch a random \nlist of products.\n\n#### Deserialization Issue\n\nAlthough the FetchData list is `ProductBase`, we cannot use this code:\n```cs\nproducts = await Http.GetJsonAsync\u003cProductBase\u003e(\"api/SampleData/Products?count=6\");\n```\nThe deserializer cannot create instances of the abstract class `ProductBase`, and \nanyway we want the specific `Product1` and `Product2` instances. The solution\nis to use the [NewtonsoftTypeNameHandling](https://www.newtonsoft.com/json/help/html/SerializeTypeNameHandling.htm) \noption on the server API to generate JSON with embedded types, and then \ndeserialize this on the client in the same way. I created a simple class\n`TypedSerializer` in my **BaseClasses** library to achieve this. \n\nThe FetchData page can then deserialize as follows:\n```cs\n    protected override async Task OnInitAsync()\n    {\n        // get sample data as JSON string\n        var json = await Http.GetStringAsync(\"api/SampleData/Products?count=6\");\n        // deserialize the list using a typed-deserializer\n        var data = BaseClasses.TypedSerializer.Deserialize(json);\n        // set value\n        products = (ProductBase[])data;\n    }\n```\n\n### List Display\n\nThe page shows the list of products in three different ways.\n![FetchData.razor](./img/fetchdata.png)\n\n\nThe first just shows a list of `ProductBase` values - only the common \nproperties can be used when we do this.\n\n#### If-then-else\nThe second list shows a manual `if-then-else` type of binding where we check \nthe product type and show a bound component manually selecting either \n`\u003cComponent1\u003e` or `\u003cComponent2\u003e`.\n```cs\n@foreach (var product in products)\n    {\n        @* here we manually bind - simple with two, but quickly becomes untenable with say a hundred product types! *@\n        @if (product is Library1.Product1 p1)\n        {\n            \u003cLibrary1.Component1 Product=@p1\u003e\u003c/Library1.Component1\u003e\n        }\n        @if (product is Library2.Product2 p2)\n        {\n            \u003cLibrary2.Component2 Product=@p2\u003e\u003c/Library2.Component2\u003e\n        }\n    }\n```\nWhile this works for two products, if we had hundreds or even thousands\nof products this becomes a nightmare. Worse, it's putting product specific\nlogic into the web application and making maintaining it much harder.\n\n#### Dynamic Component\n\nThe third list is the cool one! We display each product using a `DynamicComponent`:\n```cs\n @foreach (var product in products)\n    {\n        \u003cDynamicComponent Product=@product /\u003e\n    }\n```\nThis dynamic component selects and binds the correct component for each product. \nAlso notice it uses the correct `\u003cComponent1b\u003e` if the `HasFlange` property is set.\n\nThe code for this class is in the root of the Blazor client (although really it \nshould be in the BaseClasses library). This is a manually coded Razor Component\nthat determines which component to use by calling the `GetViewComponent()` method.\n\nIt then manually builds the render tree, binding the property thus:\n```cs\n protected override void BuildRenderTree(RenderTreeBuilder builder)\n    {\n        base.BuildRenderTree(builder);\n        Type componentType = Product.GetViewComponent();\n        builder.OpenComponent(0, componentType);\n        builder.AddAttribute(1, \"Product\", Product);\n        builder.CloseComponent();\n    }\n```\nI figured out the syntax by simply looking at the generated C# code for the `Index.razor`\npage in the file `BlazorDynamicList.Client\\obj\\Debug\\netstandard2.0\\Razor\\Pages\\Index.razor.g.s`\n\n### Other interesting points\n\nYou may notice both product libraries come with `styles.css` and \n`background.png` files. These are loaded by Blazor for us as virtual\nlibraries e.g. `/_content/Library1/styles.css`\n\n\n### Acknowledgements\n\nThanks to all those fellow Blazorians on [Gitter](https://gitter.im/aspnet/Blazor) and\nespecially to [Chris Sainty](https://chrissainty.com) for his excellent Blog articles on \nhow to use Github and Azure pipelines to publish the site.\n\n### Updates\n\nUpdated application to .NET Core 3.1 and .NET Standard 2.1, and now to \n[Blazor 3.2 RTM](https://devblogs.microsoft.com/aspnet/blazor-webassembly-3-2-0-now-available/)\nas Blazor WASM is now an official, supported product!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fconficient%2FBlazorDynamicList","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fconficient%2FBlazorDynamicList","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fconficient%2FBlazorDynamicList/lists"}