Dynamically-rendered ASP.NET Core Razor components

Note

This isn't the latest version of this article. To switch to the latest, use the ASP.NET Core version selector at the top of the table of contents.

Version selector

If the selector isn't visible in a narrow browser window, widen the window or select the vertical ellipsis () > Table of contents.

Table of contents selector

By Dave Brock

Use the built-in DynamicComponent component to render components by type.

A DynamicComponent is useful for rendering components without iterating through possible types or using conditional logic. For example, DynamicComponent can render a component based on a user selection from a dropdown list.

In the following example:

  • componentType specifies the type.
  • parameters specifies component parameters to pass to the componentType component.
<DynamicComponent Type="@componentType" Parameters="@parameters" />

@code {
    private Type componentType = ...;
    private IDictionary<string, object> parameters = ...;
}

For more information on passing parameter values, see the Pass parameters section later in this article.

Use the Instance property to access the dynamically-created component instance:

<DynamicComponent Type="@typeof({COMPONENT})" @ref="dc" />

<button @onclick="Refresh">Refresh</button>

@code {
    private DynamicComponent? dc;

    private Task Refresh()
    {
        return (dc?.Instance as IRefreshable)?.Refresh();
    }
}

In the preceding example:

  • The {COMPONENT} placeholder is the dynamically-created component type.
  • IRefreshable is an example interface provided by the developer for the dynamic component instance.

Example

In the following example, a Razor component renders a component based on the user's selection from a dropdown list of four possible values.

User spaceflight carrier selection Shared Razor component to render
Rocket Lab® Shared/RocketLab.razor
SpaceX® Shared/SpaceX.razor
ULA® Shared/UnitedLaunchAlliance.razor
Virgin Galactic® Shared/VirginGalactic.razor

Shared/RocketLab.razor:

<h2>Rocket Lab®</h2>

<p>
    Rocket Lab is a registered trademark of 
    <a href="https://www.rocketlabusa.com/">Rocket Lab USA Inc.</a>
</p>

Shared/SpaceX.razor:

<h2>SpaceX®</h2>

<p>
    SpaceX is a registered trademark of 
    <a href="https://www.spacex.com/">Space Exploration Technologies Corp.</a>
</p>

Shared/UnitedLaunchAlliance.razor:

<h2>United Launch Alliance®</h2>

<p>
    United Launch Alliance and ULA are registered trademarks of
    <a href="https://www.ulalaunch.com/">United Launch Alliance, LLC</a>.
</p>

Shared/VirginGalactic.razor:

<h2>Virgin Galactic®</h2>

<p>
    Virgin Galactic is a registered trademark of 
    <a href="https://www.virgingalactic.com/">Galactic Enterprises, LLC</a>.
</p>

Pages/DynamicComponentExample1.razor:

@page "/dynamiccomponent-example-1"

<h1><code>DynamicComponent</code> Component Example 1</h1>

<p>
    <label>
        Select your transport:
        <select @onchange="OnDropdownChange">
            <option value="">Select a value</option>
            <option value="@nameof(RocketLab)">Rocket Lab</option>
            <option value="@nameof(SpaceX)">SpaceX</option>
            <option value="@nameof(UnitedLaunchAlliance)">ULA</option>
            <option value="@nameof(VirginGalactic)">Virgin Galactic</option>
        </select>
    </label>
</p>

@if (selectedType is not null)
{
    <div class="border border-primary my-1 p-1">
        <DynamicComponent Type="@selectedType" />
    </div>
}

@code {
    private Type? selectedType;

    private void OnDropdownChange(ChangeEventArgs e)
    {
        selectedType = e.Value?.ToString()?.Length > 0 ? 
            Type.GetType($"BlazorSample.Shared.{e.Value}") : null;
    }
}

Shared/RocketLab.razor:

<h2>Rocket Lab®</h2>

<p>
    Rocket Lab is a registered trademark of 
    <a href="https://www.rocketlabusa.com/">Rocket Lab USA Inc.</a>
</p>

Shared/SpaceX.razor:

<h2>SpaceX®</h2>

<p>
    SpaceX is a registered trademark of 
    <a href="https://www.spacex.com/">Space Exploration Technologies Corp.</a>
</p>

Shared/UnitedLaunchAlliance.razor:

<h2>United Launch Alliance®</h2>

<p>
    United Launch Alliance and ULA are registered trademarks of
    <a href="https://www.ulalaunch.com/">United Launch Alliance, LLC</a>.
</p>

Shared/VirginGalactic.razor:

<h2>Virgin Galactic®</h2>

<p>
    Virgin Galactic is a registered trademark of 
    <a href="https://www.virgingalactic.com/">Galactic Enterprises, LLC</a>.
</p>

Pages/DynamicComponentExample1.razor:

@page "/dynamiccomponent-example-1"

<h1><code>DynamicComponent</code> Component Example 1</h1>

<p>
    <label>
        Select your transport:
        <select @onchange="OnDropdownChange">
            <option value="">Select a value</option>
            <option value="@nameof(RocketLab)">Rocket Lab</option>
            <option value="@nameof(SpaceX)">SpaceX</option>
            <option value="@nameof(UnitedLaunchAlliance)">ULA</option>
            <option value="@nameof(VirginGalactic)">Virgin Galactic</option>
        </select>
    </label>
</p>

@if (selectedType is not null)
{
    <div class="border border-primary my-1 p-1">
        <DynamicComponent Type="@selectedType" />
    </div>
}

@code {
    private Type? selectedType;

    private void OnDropdownChange(ChangeEventArgs e)
    {
        selectedType = e.Value?.ToString()?.Length > 0 ? 
            Type.GetType($"BlazorSample.Shared.{e.Value}") : null;
    }
}

In the preceding example:

  • Component names are used as the option values using the nameof operator, which returns component names as constant strings.
  • The namespace of the app is BlazorSample. Change the namespace to match your app's namespace.

Pass parameters

If dynamically-rendered components have component parameters, pass them into the DynamicComponent as an IDictionary<string, object>. The string is the name of the parameter, and the object is the parameter's value.

The following example configures a component metadata object (ComponentMetadata) to supply parameter values to dynamically-rendered components based on the type name. The example is just one of several approaches that you can adopt. Parameter data can also be provided from a web API, a database, or a method. The only requirement is that the approach returns an IDictionary<string, object>.

ComponentMetadata.cs:

public class ComponentMetadata
{
    public string? Name { get; set; }
    public Dictionary<string, object> Parameters { get; set; } = 
        new Dictionary<string, object>();
}

The following RocketLabWithWindowSeat component (Shared/RocketLabWithWindowSeat.razor) has been updated from the preceding example to include a component parameter named WindowSeat to specify if the passenger prefers a window seat on their flight:

Shared/RocketLabWithWindowSeat.razor:

<h2>Rocket Lab®</h2>

<p>
    User selected a window seat: @WindowSeat
</p>

<p>
    Rocket Lab is a trademark of 
    <a href="https://www.rocketlabusa.com/">Rocket Lab USA Inc.</a>
</p>

@code {
    [Parameter]
    public bool WindowSeat { get; set; }
}
<h2>Rocket Lab®</h2>

<p>
    User selected a window seat: @WindowSeat
</p>

<p>
    Rocket Lab is a trademark of 
    <a href="https://www.rocketlabusa.com/">Rocket Lab USA Inc.</a>
</p>

@code {
    [Parameter]
    public bool WindowSeat { get; set; }
}

In the following example:

  • Only the RocketLabWithWindowSeat component's parameter for a window seat (WindowSeat) receives the value of the Window Seat checkbox.
  • The namespace of the app is BlazorSample. Change the namespace to match your app's namespace.
  • The dynamically-rendered components are shared components in the app's Shared folder:
    • Shown in this article section: RocketLabWithWindowSeat (Shared/RocketLabWithWindowSeat.razor)
    • Components shown in the Example section earlier in this article:
      • SpaceX (Shared/SpaceX.razor)
      • UnitedLaunchAlliance (Shared/UnitedLaunchAlliance.razor)
      • VirginGalactic (Shared/VirginGalactic.razor)

Pages/DynamicComponentExample2.razor:

@page "/dynamiccomponent-example-2"

<h1><code>DynamicComponent</code> Component Example 2</h1>

<p>
    <label>
        <input type="checkbox" @bind="WindowSeat" />
        Window Seat (Rocket Lab only)
    </label>
</p>

<p>
    <label>
        Select your transport:
        <select @onchange="OnDropdownChange">
            <option value="">Select a value</option>
            @foreach (var c in components)
            {
                <option value="@c.Key">@c.Value.Name</option>
            }
        </select>
    </label>
</p>

@if (selectedType is not null)
{
    <div class="border border-primary my-1 p-1">
        <DynamicComponent Type="@selectedType" 
            Parameters="@components[selectedType.Name].Parameters" />
    </div>
}

@code {
    private Dictionary<string, ComponentMetadata> components =
        new()
        {
            {
                "RocketLabWithWindowSeat",
                new ComponentMetadata
                {
                    Name = "Rocket Lab with Window Seat",
                    Parameters = new() { { "WindowSeat", false } }
                }
            },
            {
                "VirginGalactic",
                new ComponentMetadata { Name = "Virgin Galactic" }
            },
            {
                "UnitedLaunchAlliance",
                new ComponentMetadata { Name = "ULA" }
            },
            {
                "SpaceX",
                new ComponentMetadata { Name = "SpaceX" }
            }
        };
    private Type? selectedType;
    private bool windowSeat;

    private bool WindowSeat
    {
        get { return windowSeat; }
        set
        {
            windowSeat = value;
            components[nameof(RocketLabWithWindowSeat)].Parameters["WindowSeat"] = 
                windowSeat;
        }
    }

    private void OnDropdownChange(ChangeEventArgs e)
    {
        selectedType = e.Value?.ToString()?.Length > 0 ? 
            Type.GetType($"BlazorSample.Shared.{e.Value}") : null;
    }
}
@page "/dynamiccomponent-example-2"

<h1><code>DynamicComponent</code> Component Example 2</h1>

<p>
    <label>
        <input type="checkbox" @bind="WindowSeat" />
        Window Seat (Rocket Lab only)
    </label>
</p>

<p>
    <label>
        Select your transport:
        <select @onchange="OnDropdownChange">
            <option value="">Select a value</option>
            @foreach (var c in components)
            {
                <option value="@c.Key">@c.Value.Name</option>
            }
        </select>
    </label>
</p>

@if (selectedType is not null)
{
    <div class="border border-primary my-1 p-1">
        <DynamicComponent Type="@selectedType" 
            Parameters="@components[selectedType.Name].Parameters" />
    </div>
}

@code {
    private Dictionary<string, ComponentMetadata> components =
        new()
        {
            {
                "RocketLabWithWindowSeat",
                new ComponentMetadata
                {
                    Name = "Rocket Lab with Window Seat",
                    Parameters = new() { { "WindowSeat", false } }
                }
            },
            {
                "VirginGalactic",
                new ComponentMetadata { Name = "Virgin Galactic" }
            },
            {
                "UnitedLaunchAlliance",
                new ComponentMetadata { Name = "ULA" }
            },
            {
                "SpaceX",
                new ComponentMetadata { Name = "SpaceX" }
            }
        };
    private Type? selectedType;
    private bool windowSeat;

    private bool WindowSeat
    {
        get { return windowSeat; }
        set
        {
            windowSeat = value;
            components[nameof(RocketLabWithWindowSeat)].Parameters["WindowSeat"] = 
                windowSeat;
        }
    }

    private void OnDropdownChange(ChangeEventArgs e)
    {
        selectedType = e.Value?.ToString()?.Length > 0 ? 
            Type.GetType($"BlazorSample.Shared.{e.Value}") : null;
    }
}

Event callbacks (EventCallback)

Event callbacks (EventCallback) can be passed to a DynamicComponent in its parameter dictionary.

ComponentMetadata.cs:

public class ComponentMetadata
{
    public string? Name { get; set; }
    public Dictionary<string, object> Parameters { get; set; } =
        new Dictionary<string, object>();
}

Implement an event callback parameter (EventCallback) within each dynamically-rendered component.

Shared/RocketLab2.razor:

<h2>Rocket Lab®</h2>

<p>
    Rocket Lab is a registered trademark of
    <a href="https://www.rocketlabusa.com/">Rocket Lab USA Inc.</a>
</p>

<button @onclick="OnClickCallback">
    Trigger a Parent component method
</button>

@code {
    [Parameter]
    public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
}

Shared/SpaceX2.razor:

<h2>SpaceX®</h2>

<p>
    SpaceX is a registered trademark of
    <a href="https://www.spacex.com/">Space Exploration Technologies Corp.</a>
</p>

<button @onclick="OnClickCallback">
    Trigger a Parent component method
</button>

@code {
    [Parameter]
    public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
}

Shared/UnitedLaunchAlliance2.razor:

<h2>United Launch Alliance®</h2>

<p>
    United Launch Alliance and ULA are registered trademarks of
    <a href="https://www.ulalaunch.com/">United Launch Alliance, LLC</a>.
</p>

<button @onclick="OnClickCallback">
    Trigger a Parent component method
</button>

@code {
    [Parameter]
    public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
}

Shared/VirginGalactic2.razor:

<h2>Virgin Galactic®</h2>

<p>
    Virgin Galactic is a registered trademark of
    <a href="https://www.virgingalactic.com/">Galactic Enterprises, LLC</a>.
</p>

<button @onclick="OnClickCallback">
    Trigger a Parent component method
</button>

@code {
    [Parameter]
    public EventCallback<MouseEventArgs> OnClickCallback { get; set; }
}

In the following parent component example, the ShowDTMessage method assigns a string with the current time to message, and the value of message is rendered.

The parent component passes the callback method, ShowDTMessage in the parameter dictionary:

  • The string key is the callback method's name, OnClickCallback.
  • The object value is created by EventCallbackFactory.Create for the parent callback method, ShowDTMessage. Note that the this keyword isn't supported in C# fields, so a C# property is used for the parameter dictionary.

For the following component, change the namespace name of BlazorSample in the OnDropdownChange method to match your app's namespace.

Pages/DynamicComponentExample3.razor:

@page "/dynamiccomponent-example-3"

<h1><code>DynamicComponent</code> Component Example 3</h1>

<p>
    <label>
        Select your transport:
        <select @onchange="OnDropdownChange">
            <option value="">Select a value</option>
            <option value="@nameof(RocketLab2)">Rocket Lab</option>
            <option value="@nameof(SpaceX2)">SpaceX</option>
            <option value="@nameof(UnitedLaunchAlliance2)">ULA</option>
            <option value="@nameof(VirginGalactic2)">Virgin Galactic</option>
        </select>
    </label>
</p>

@if (selectedType is not null)
{
    <div class="border border-primary my-1 p-1">
        <DynamicComponent Type="@selectedType"
            Parameters="@Components[selectedType.Name].Parameters" />
    </div>
}

<p>
    @message
</p>

@code {
    private Type? selectedType;
    private string? message;

    private Dictionary<string, ComponentMetadata> Components
    {
        get
        {
            return new Dictionary<string, ComponentMetadata>()
            {
                {
                    "RocketLab2",
                    new ComponentMetadata
                    {
                        Name = "Rocket Lab",
                        Parameters =
                            new()
                            {
                                {
                                    "OnClickCallback",
                                    EventCallback.Factory.Create<MouseEventArgs>(
                                        this, ShowDTMessage)
                                }
                            }
                    }
                },
                {
                    "VirginGalactic2",
                    new ComponentMetadata
                    {
                        Name = "Virgin Galactic",
                        Parameters =
                            new()
                            {
                                {
                                    "OnClickCallback",
                                    EventCallback.Factory.Create<MouseEventArgs>(
                                        this, ShowDTMessage)
                                }
                            }
                    }
                },
                {
                    "UnitedLaunchAlliance2",
                    new ComponentMetadata
                    {
                        Name = "ULA",
                        Parameters =
                            new()
                            {
                                {
                                    "OnClickCallback",
                                    EventCallback.Factory.Create<MouseEventArgs>(
                                        this, ShowDTMessage)
                                }
                            }
                    }
                },
                {
                    "SpaceX2",
                    new ComponentMetadata
                    {
                        Name = "SpaceX",
                        Parameters =
                            new()
                            {
                                {
                                    "OnClickCallback",
                                    EventCallback.Factory.Create<MouseEventArgs>(
                                        this, ShowDTMessage)
                                }
                            }
                    }
                }
            };
        }
    }

    private void OnDropdownChange(ChangeEventArgs e)
    {
        selectedType = e.Value?.ToString()?.Length > 0 ?
            Type.GetType($"BlazorSample.Shared.{e.Value}") : null;
    }

    private void ShowDTMessage(MouseEventArgs e) =>
        message = $"The current DT is: {DateTime.Now}.";
}

Avoid catch-all parameters

Avoid the use of catch-all parameters. If catch-all parameters are used, every explicit parameter on DynamicComponent effectively is a reserved word that you can't pass to a dynamic child. Any new parameters passed to DynamicComponent are a breaking change, as they start shadowing child component parameters that happen to have the same name. It's unlikely that the caller always knows a fixed set of parameter names to pass to all possible dynamic children.

Trademarks

Rocket Lab is a registered trademark of Rocket Lab USA Inc. SpaceX is a registered trademark of Space Exploration Technologies Corp. United Launch Alliance and ULA are registered trademarks of United Launch Alliance, LLC. Virgin Galactic is a registered trademark of Galactic Enterprises, LLC.

Additional resources