Category Archives: Blazor

Blazor templates

Way back I wrote the post Blazor Components. This post demonstrated how to create simple components within Blazor. I didn’t progress massively, using Blazor back when that post was written, but I’ve been getting back into Blazor recently.

Let’s look at a powerful feature of Blazor (a little bit like WPF lookless controls) in that we can create Blazor templates using code behind with entry points (placeholders) for the UI to interact with.

One of the obvious uses of such a template might be for lists or grids etc. as these might allow us to add a header and/or footer along with rows which have the same look and feel but with different data.

Let’s look at a very simple example, a ListView template which allows us to output a list of rows where the list (at a top level) can be styled by the code using it and each row and be styled as well. Ofcourse you could style things quite easily if you know the CSS class etc. but here I mean styled as in you can change the UI itself, hence let’s begin by looking at how we could change a ListView to use ordered lists or unordered lists.

Note: I’m going to reuse the weather forecast data supplied when creating a Blazor WebAssembly application in Visual Studio

The component

First off, let’s create a component named ListView, I’m creating it within a folder named Components. The ListView.razor look like this

@typeparam TItem

@if (ListTemplate is not null)
{
    @ListTemplate(
        @:@{foreach (var item in Items) 
            {
                @ItemTemplate(item)
            }
          }
    )
}

We’re declaring a typeparam named TItem as this adds generic type parameter support which you’ll see being used in the code behind file. We can add a constraint to this type if required, but for this example we’re not too concerned about what type TItem takes. In the example above we’re assuming the user will supply the ListTemplate otherwise nothing is displayed, ofcourse we could create a default output if we wished or display a message for the developer.

The code behind, the ListView.razor.cs looks like this

using Microsoft.AspNetCore.Components;

namespace BlazorTemplates.Components;

public partial class ListView<TItem>
{
    [Parameter] public RenderFragment<RenderFragment>? ListTemplate { get; set; }
    [Parameter] public RenderFragment<TItem> ItemTemplate { get; set; } = default!;
    [Parameter] public IReadOnlyList<TItem> Items { get; set; } = default!;
}

What’s happening here is that we’re declaring a ListTemplate which is a RenderFragment, which is simple put, a piece of UI content, the generic type is also a RenderFragment – so ListTemplate is UI content of UI content essentially. This will act as our “outer” UI, so for example, if were using the ListView for ordered list items, then this would contain the <ol></ot> section of the UI.

The ItemTemplate is again a RenderFragment, hence UI content supplied by the calling code, but this type it takes a generic TItem type, this refers to the type of the list items themselves, i.e. for the WeatherForeast sample code, this is a WeatherForecast class/type. Hence the ItemTemplate will be passed an item from the supplied Items list (within our template) and allows the calling code to render each item.

The Items property is what’s used by the calling code to supply the list of data to our template.

Calling the component

If we look again at the .razor code you’ll see that really all we’re doing it calling the ListTemplate and passing through a segment of code using Wig-Pig syntax which creates a RanderFragment. This then gets used to call through to the ItemTemplate and this template is passed each item from the list of Items so that we can render each row, let’s see the code in use

<ListView Items="_forecasts">
  <ListTemplate Context="rows">
    <ul>@rows</ul>
  </ListTemplate>
  <ItemTemplate Context="forecast">
    <li>@forecast.Summary</li>
  </ItemTemplate>
</ListView>

Note: _forecasts is the same as the Weather page in the default Blazor WebAssembly template, hence an array of WeatherForeast items

In the example above we’re renaming the context for use within each template, we could have just used the following if we preferred

<ListView Items="_forecasts">
  <ListTemplate>
    <ul>@context</ul>
  </ListTemplate>
  <ItemTemplate>
    <li>@context.Summary</li>
  </ItemTemplate>
</ListView>

As can be seen (which ever version you use) then ListTemplate is creating the “outer” element, then passing the context into the RenderFragement we supplied within the ListView.razor file, which itself then loops through each item with the Items list (which was defined on the ListView Items property in the above code.

The template then calls the ItemTemplate for each item and the template for this is supplied in the ItemTemplate fragment in the above code. In this case we using li and displaying the summary for each weather forecast.

Using the template if different scenarios

Now using the template to display an unordered list is great, and yes it’s obvious how we can easily change the ol to an ul but I’m sure this looks like a lot of effort for something very easy in Blazor anyway. So let’s look at extending this into something a little more useful, but before we do that, here’s the example of using this template with ordered lists, unordered lists and tables

<ListView Items="_forecasts">
  <ListTemplate>
    <ul>@context</ul>
  </ListTemplate>
  <ItemTemplate>
    <li>@context.Summary</li>
  </ItemTemplate>
</ListView>

<ListView Items="_forecasts">
  <ListTemplate>
    <ol>@context</ol>
  </ListTemplate>
  <ItemTemplate>
    <li>@context.Summary</li>
  </ItemTemplate>
</ListView>

<ListView Items="_forecasts">
  <ListTemplate>
    <table>@context</table>
  </ListTemplate>
  <ItemTemplate>
    <tr>@context.Summary</tr>
  </ItemTemplate>
</ListView>

How about we extend our template to allow for a header and a footer, hence allowing us to use more of the table functionality but also have the same functionality now for the ordered and unordered lists.

We can simply add the following to our ListView.razor.cs class

[Parameter] public RenderFragment? HeaderTemplate { get; set; }
[Parameter] public RenderFragment? FooterTemplate { get; set; }

and within the ListView.razor file just add HeaderTemplate and FooterTemplate like this

@typeparam TItem

@if (ListTemplate is not null)
{
    @HeaderTemplate

    @ListTemplate(
        @:@{foreach (var item in Items) 
            {
                @ItemTemplate(item)
            }
          }
    )

    @FooterTemplate
}

All that’s left to do is add HeaderTemplate code to our calling code, so for example the table based version would look like this (I’ve added more table like element/attribute parts to this code)

<ListView Items="_forecasts">
  <HeaderTemplate>
    <thead><tr><th scope="col">Summary</th></tr></thead>
  </HeaderTemplate>
  <FooterTemplate>
    <tfoot><tr><th scope="row">Footer</th></tr></tfoot>
  </FooterTemplate>
  <ListTemplate>
    <table>
      <caption>This is a table</caption>
      <tbody>@context</tbody>
     </table>
  </ListTemplate>
  <ItemTemplate>
    <tr scope="row">@context.Summary</tr>
  </ItemTemplate>
</ListView>

Now we can also add a header and footer to our ordered and unordered lists in the same way, i.e.

<ListView Items="_forecasts">
  <HeaderTemplate>
    <div>Summary</div>
  </HeaderTemplate>
  <FooterTemplate>
    <div>Footer</div>
  </FooterTemplate>
  <ListTemplate>
    <ul>@context</ul>
  </ListTemplate>
  <ItemTemplate>
    <li>@context.Summary</li>
   </ItemTemplate>
</ListView>

Note: ofcourse this is not very pretty, so I’ll leave it to the reader to create some nice CSS for the header and footer divs

That’s pretty much it – templates can be useful if you’re going to abstract some UI pattern in a reusable way.

Code for this post is available on GitHub.

Blazor and TypeScript

In the past I wrote a blog post on Blazor and the JavaScript interop. but what about TypeScript, I mean it obviously transpiles to JavaScript, so we should be able to use it, right ?

So yes, we can easily use TypeScript within a Blazor application, let’s do the following

  • Create a Blazor WebAssembly Standalone App (you can use the server one’s if you prefer, but for this post that’s the template I’m starting with)
  • Add the NuGet Package Microsoft.TypeScript.MSBuild, this will allow us to transpile our TypeSript code as part of the build process. This will add the following to the csproj (the version may differ ofcourse)
    <PackageReference Include="Microsoft.TypeScript.MSBuild" Version="5.7.1">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    
  • Within the project root, i.e. where Program.cs is located, add a file tsconfig.json, mine looks like this
    {
      "compilerOptions": {
        "module": "ES2015",
        "target": "ES2024",
        "sourceMap": true
      },
      "exclude": [
        "node_modules"
      ]
    }
    
  • Let’s create a scripts folder off of wwwroot
  • Now create a file, mine’s not very imaginatively named Example.ts and here’s the code
    namespace Example {
        export class Prompt {
            public showAlert(message: string): string {
                return prompt(message, "Hey");
            }
        }
    }
    
    export function getPromptInstance(): Example.Prompt {
        return new Example.Prompt();
    }
    

Before we look at using this code let’s just review a few of the steps.

The tsconfig.json if, ofcourse, used to configure the TypeScript transpiler. The module and target do NOT need to be these two values, but we do need to use a module type which will generate an export function. Some of the other module types will not include the export definition and then we cannot access the factory method getPromptInstance. So feel free to change these two options but before your JavaScript needs to export our factory function.

As mentioned, we’re using the getPromptInstance to create an instance to our class, ofcourse we could export more functions and remove the class in this example.

Interop from Blazor

Interop. with our transpiled code uses the same mechanism/code as my old post Blazor and the JavaScript interop., but let’s create some code in this sample anyway.

Let’s do the following

  • Add a Button to the Home.razor page that looks like this
    <button class="btn btn-primary" @onclick="DisplayMessage">Alert me</button>
    
  • Now add a code block which looks like this
    @code{
        private async Task DisplayMessage()
        {
            var module = await JsRuntime.InvokeAsync<IJSObjectReference>("import", "./scripts/Example.js");
            var o = await module.InvokeAsync<IJSObjectReference>("getPromptInstance");
            await o.InvokeVoidAsync("showAlert", "Hello TypeScript World");
        }
    }
    
  • We also need to add the following to the top of the Home.razore file, below the @page line
    @inject IJSRuntime JsRuntime
    

Now when you run the application you’ll see the “Alert me” button, clicking this will load the script (note we reference the .js script here obviously) and then we get a reference to the getPromptInstance function and call the showAlert method of the previously defined TypeScript class.

Obviously you’ll probably prefer to create a new C# class that holds onto a reference to the imported script, but hopefully this post gives you a starting point to porting/using TypeScript code within your Blazor app.

Code for this post is available at GitHub

Blazor and the GetFromJsonAsync exception TypeError: Failed to Fetch

I have an Azure hosted web api. I also have a simple Blazor standalone application that’s meant to call the API to get a list of categories to display. i.e. the Blazor app is meant to call the Azure web api, fetch the data and display it – should be easy enough, right ?

The web api can easily accessed via a web browser or a console app using the .NET HttpClient, but the Blazor code using the following simply kept throwing an exception with the cryptic message “TypeError: Failed to Fetch”

@inject HttpClient Http

// Blazor and other code

protected override async Task OnInitializedAsync()
{
   try
   {
      _categories = await Http.GetFromJsonAsync<string[]>("categories");
   }
   catch (Exception e)
   {
      Debug.WriteLine(e);
   }
}

What was happening is I was actually getting a CORS error, sadly not really reported via the exception so not exactly obvious.

If you get this error interacting with your web api via Blazor then go to the Azure dashboard. I’m running my web api as a container app, type CORS into the left search bar of the resource (in my case a Container App). you should see the Settings section CORS subsection.

Add * to the Allowed Origins and click apply.

Now your Blazor app should be able to interact with the Azure web api app.

Blazor and the JavaScript interop.

In the post Logging in Blazor we saw how we can use the IJSRuntime to call out to the JavaScript runtime.

Ofcourse we can interop with any JavaScript code within our application, so for example let’s make some simple changes to the index.html from that post and add a script block, such as this

<script>
   function displayMessage(msg) {
      alert(msg);
   }
</script>

Nothing very special here, but we just want something visual to more easily see our interop calls.

Next, in our .razor file let’s simple add a button and associated code block

<button @onclick="DisplayMessage">Alert</button>

@code {
   private void DisplayMessage()
   {
      JsRuntime.InvokeVoidAsync("displayMessage", "Hello JavaScript World");
   }
}

The IJSRuntime interface has the following methods

public interface IJSRuntime
{
   ValueTask<TValue> InvokeAsync<TValue>(string identifier, 
      object[] args);
   ValueTask<TValue> InvokeAsync<TValue>(string identifier, 
      CancellationToken cancellationToken, object[] args);
}

As you can see, both return a ValueTask, hence are awaitable, but neither matches our InvokeVoidAsync method. This is because there are a bunch of extension methods within JSRuntimeExtensions. These extension methods check that the supplied IJSRuntime is not null and then calls the interface methods.

In the example above, we are not interested in the return value, so let’s now return some value from our JavaScript…

Change the script to

<script>
   function displayMessage(msg) {
      return prompt(msg);
   }
</script>

and now change the C# code in our code block to

private async void DisplayMessage()
{
   Debug.WriteLine(
      await JsRuntime.InvokeAsync<string>(
         "displayMessage", "Hello JavaScript World"
      )
   );
}

At this point, we’re receiving the return from our JavaScript function and (in the case of a WebAssembly application) outputting to the browser console.

Ofcourse we can now write JavaScript code and call this from Blazor as well.

Blazor/ASP.NET core on docker

I wanted to get a Blazor server application up and running on Ubuntu within a docker container (I’m running the whole thing on a Raspberry Pi 4 with Ubuntu Server).

The first stage for this post will simply be about creating a Dockerfile and creating a Blazor server application via the dotnet template.

We’re going to want the latest version of dotnet core, so let’s start by creating a very bare bones Dockerfile which will create an image based upon
mcr.microsoft.com/dotnet/core/sdk:3.1, it will also expose the standard HTTP port used in the Blazor server template, i.e. port 5000

Here’s the Dockerfile

FROM mcr.microsoft.com/dotnet/core/sdk:3.1
ENV APP_HOME /home
RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME
EXPOSE 5000
CMD [ "bash" ]

To build this image run the following

docker rmi dotnet-env --force
docker build -t dotnet-env .

The first line is solely there to remove any existing image (which will be especially useful whilst developing the image). The second line will build the Dockerfile and name it dotnet-env.

Once built, let’s run the image to see all is good with it. So simply run

docker run -it --rm -p 5000:5000 -v /home/share:/home/share dotnet-env

In this example we’ll run docker in interactive mode and map ports using -p to map the host port 5000 to the exposed port in the image. We’ll also also created a volume link from the container to the host.

Once we’ve run the image up we should be placed into a BASH command prompt, now we can simply run

dotnet new blazorserver -o MyServer

To create the project MyServer, once created cd into the MyServer folder. Now run

dotnet run

A kestrel server should start up, and you might be able to access the server using http://server-ip-address. I say might, because you may well see an error at startup, saying something like

Unable to bind to http://localhost:5000 on the IPv6 loopback interface: ‘Cannot assign requested address’.

What you need to do is go into the Properties folder and open launchSettings.json, change the line

"applicationUrl": "https://localhost:5001;http://localhost:5000",

to

"applicationUrl": "http://0.0.0.0:5001;http://0.0.0.0:5000",

Next Step

This obvious next step to our docker build is to create a Docker image which contains our application and runs it when the container is started. We’re going to build and publish the using dotnet publish -c Release -o publish and then include the published files in our docker container, alternatively you might prefer to have the Dockerfile build and publish the project as part of its build process.

For now let’s just build our Blazor server application, then publish it to a folder.

We’re going to host the application in Kestrel, so before we go any further open the appsetting.json file from the publish folder and add the following

"Kestrel": {
  "EndPoints": {
    "Http": {
      "Url": "http://0.0.0.0:5000"
    }   
  }
},

Now we’ll make the changes to the Dockerfile to copy the published folder to the image and start up the Kestrel server when the image is run, here’s the Dockerfile

FROM mcr.microsoft.com/dotnet/core/sdk:3.1

ENV APP_HOME /home
RUN mkdir -p $APP_HOME

WORKDIR $APP_HOME

COPY ./BlazorServer/BlazorServer/publish ${APP_HOME}

EXPOSE 5000

CMD [ "dotnet", "BlazorServer.dll" ]

Now you should be able to access your server using http://your_server_name:5000.

Blazor Components

We can create Blazor components within our Blazor application by simply right mouse clicking on a folder or project in Visual Studio and select Add | Razor Component.

A component derives from Microsoft.AspNetCore.Components.ComponentBase, whether implicitly, such as within a .razor file or explicitly, such as in a C# code file.

Razor component

If we create a .razor file we are already implementing a ComponentBase, but usually without an explicit inherit such as the following

@inherits ComponentBase

With or without this @inherit, we will get a generated file (written to obj\Debug\netstandard2.1\Razor) that is a class which implements the ComponentBase.

We can add code to our .razor file in a @code block, thus combining markup and code within the same file, as per this example

@namespace BlazorApp

<h3>@Text</h3>

@code {
    [Parameter]
    public string Text { get; set; }
}

Separate code and razor files

As alternative to a single .razor file with both markup and code, we can create a .cs file for our code. For example if our .razor file is MyComponent.razor we could create a MyComponent.cs and put our code into a C# class. The class should derive from ComponentBase and also be partial

Hence now we have a MyComponent.razor file that looks like this

@namespace BlazorApp

<h3>@Text</h3>

and a C# file, MyComponent.cs that looks like this

using Microsoft.AspNetCore.Components;

namespace BlazorApp
{
    public partial class MyComponent : ComponentBase
    {
        [Parameter]
        public string Text { get; set; }
    }
}

Ofcourse the use of the partial keyword is the key to this working. If we name the file MyComponent.cs all will be fine, but if we name the file MyComponent.razor.cs the file will appear like a code-behind file in other C# scenarios.

Code only component

We could also write a code only component, so let’s assume we have only a MyComponent.cs file

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;

namespace BlazorApp
{
    public class MyComponent : ComponentBase
    {
        protected override void BuildRenderTree(RenderTreeBuilder builder)
        {
            builder.OpenElement(1, "h3");
            builder.AddContent(2, Text);
            builder.CloseElement();
        }

        [Parameter]
        public string Text { get; set; }
    }
}

Obviously we have to put a little bit of effort into constructing the elements in the BuildRenderTree method, but this might suit some scenarios.

Pages and Layouts

Pages and Layouts are just components, just like those discussed above.

The only difference of between a Page and component is that our class is annotated with a PageAttribute for Page. Obviously in a .razor file this happens automatically when we add a page declaration as below

@page "/"

As for the layout, we inherit from LayoutComponentBase, in a .razor file this looks like this

@inherits LayoutComponentBase

Dependency Injection and Blazor

In a previous post Blazor routing and Navigation we injected the NavigationManager into out page using the following

@inject NavigationManager NavManager

So when we use @inject followed by the type we want injected, ASP.NET/Blazor will automatically supply the NavigationManager (assuming one exists).

Adding services

Ofcourse we can also add our own types/services to the DI container.

On a Blazor WebAssembly application, we can add types to the Program.cs, Main method, for example

public static async Task Main(string[] args)
{
   var builder = WebAssemblyHostBuilder.CreateDefault(args);
   // template generated code here

   // my custom DataService
   builder.Services.AddSingleton<IDataService, DataService>();

   await builder.Build().RunAsync();
}

In Blazor Server, we add our types to the Startup.cs, ConfigureServices method, for example

public void ConfigureServices(IServiceCollection services)
{
   // template generated code here

   // my custom DataService
   services.AddSingleton<IDataService, DataService>();
}

Service lifetime

In the examples in the previous section we added the service as a singleton.

  • Scoped – this is means the service is scoped to the connection. This is the preferred way to handle per user services – there is no concept of scope services in WebAssembly as obviously it’s a client technology at this point and already per user scoped
  • Singleton – As you’d expect, this is a single instance for the lifetime of the application
  • Transient – each request for a transient service will receive a new instance of the service
  • If you need access to service is a Component class, i.e. you’re creating your own IComponent you have mark a property with the InjectAttribute

    public class MyService
    {
       [Inject]
       IDataService DataService { get; set; }
    }
    

    Ofcourse constructor injection (my preferred way to do things) is also available, so we just write code such as this, assuming that MyService is created using the service container

    public class MyService
    {
    public MyService(IDataService dataService)
    {
    // do something with dataService
    }
    }

    Configuration and Blazor

    In a previous post we looked at dependency injection within Blazor. One of the services available by default is an implementation of IConfiguration, hence to inject the configuration object we do the following

    @inject IConfiguration Configuration
    

    and we can interact with the configuration using a key, i.e.

    @Configuration["SomeKey"]
    

    For Blazor on the server we can simply add the key/value to the appsettings.json file, like this

    "SomeKey":  "Hello World" 
    

    By default Blazor WebAssembly does not come with an appsettings.json, so you’ll need to add one yourself. This file will need to be in the wwwroot folder as these files will be deployed at part of your client.

    You can put sensitive data in the server appsettings.json because it’s hosted on the server, do not put sensitive information in the appsettings.json for a WebAssembly/client otherwise this will be downloaded to the user’s machine.

    If you want to store more complicated data in the appsettings.json, for example a JSON object, you’ll need to create a class that looks like the data, for example if your appsettings.json had the following

    "Person": {
      "FirstName": "Scooby",
      "LastName": "Doo"
    } 
    

    So now we’ll need a class to match the structure of the data. The name of the class is not important, the structure is, so here we have

    public class MyPerson
    {
       public string FirstName { get; set; }
       public string LastName { get; set; }
    }
    

    Now to access this we need to use

    Configuration.GetSection("Person").Get<MyPerson>();
    

    References

    Carl Franklin’s Blazor Train: Configuration and Dependency Injection

    Logging in Blazor

    It’s not unusual for us to need to output log message to file, console etc. Obviously there’s going to be a different in capabilities for logging from WASM (essentially on the client browser) and on a server.

    Blazor supplies the logging framework

    Blazor supplies loggin via the ILogger, ILogger, ILoggerFactory and ILoggerProvider interfaces. In most cases you’ll use the ILogger and ILogger.

    Logging capabilities are supplied via the WebAssemblyHostBuilder on WebAssembly and Host on the server (see Program.cs in both project types).

    We can therefore use these interfaces/implementation via injection like this

    @inject ILogger Logger;
    // OR
    @inject ILogger<MyPage> Logger;
    

    Alternatively we might use the ILogFactory like this

    @inject ILoggerFactory LoggerFactory
    
    // with the following
    var logger = LoggerFactory.CreateLogger<MyPage>();
    

    Logging in WebAssembly

    With WebAssembly on the client, we’d obviously expect to be logging to the Browser devtools. Whilst we can actually just use

    Console.WriteLine("Message 1");
    Debug.WriteLine("Message 2");
    

    the Logging framework offers some extra functionality, such as categorisation of logs etc. To use the ILogger or ILogger, we simply use

    Logger.LogWarning("Log a warning");
    

    (we have the ability to log debug, warning, info etc.)

    Logging to Server

    For Blazor Server, the output from the logging will be visible in the console window if running as a standalone application or the output window of Visual Studio.

    However if you want your Blazor server application to output to the browser, then you need to use the IJSRuntime, which we can inject like this

    @inject IJSRuntime JsRuntime
    

    and then use the runtime like this

    JsRuntime.InvokeAsync<string>("console.log", "Browser Message");
    

    as you can see, the runtime allows us access to the JavaScript runtime hosting our pages and we can call the console.log JavaScript method, passing the parameters for the method call. In our case it’s just a string “Browser Message”.

    Adding custom logger

    To add a custom logger we can use something like this, where the DebugProvider is our implementation of an ILoggingProvider

    builder.Logging.AddProvider(new DebugProvider());
    

    Configuration

    Configuration is placed in the appsettings.json file (remember if you’re using appsettings.json on WebAssembly client then this should be placed in the wwwroot folder otherwise it’s not set to the browser). For server the appsettings.json can be in the project folder.

    Here’s the template generated appsettings section for logging

    "Logging": {
      "LogLevel": {
        "Default": "Trace",
        "Microsoft": "Warning",
        "Microsoft.Hosting.Lifetime": "Information"
      }
    },
    

    Creating Blazor fragments

    At some point you may need to dynamically create elements of mark-up in a Blazor application. For example, you might create a reusable class library for returning components, icons or the likes depending upon the user.

    Ofcourse we can do this from the @code block within a page or we might prefer to simply return mark-up from a function.

    Let’s look at a simple example method which, depending upon the id sent to it returns a different icon, here’s an abridge snippet of the code

    public static RenderFragment GetIconForId(int id)
    {
       var iconType = typeof(ListIcon);
       switch (id)
       {
          case 0:
             iconType = typeof(HomeIcon);
             break;
          case 1:
             iconType = typeof(NotificationImportantIcon);
             break;
          // etc.
       }
       
       return builder =>
       {
          builder.OpenComponent(0, iconType);
          builder.CloseComponent();
       };
    }
    

    As you can see we return a RenderFragment which is actually just a delegate which takes a RenderTreeBuilder argument. From this builder we create/append a child component, the first value is a sequence (a position of the instructions in the source code) followed by the type of the child component to add.

    If the case above, the HomeIcon is an Icon from Skclusive.Material, which is ultimately an Microsoft.AspNetCore.Components.IComponent.

    So anything that takes a RenderFragment will now display the icon that’s returned via the method in our example.

    We can also use AddAttribute to our component, see RenderTreeBuilder for more information.

    Basically it’s not dissimilar (as you’ve expect) to create XML documents using the XmlDocument classes etc.