Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 116 additions & 1 deletion aspnetcore/blazor/components/dynamiccomponent.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
---
title: Dynamically-rendered ASP.NET Core Razor components
ai-usage: ai-assisted
author: guardrex
description: Learn how to use dynamically-rendered Razor components in Blazor apps.
monikerRange: '>= aspnetcore-6.0'
ms.author: wpickett
ms.custom: mvc
ms.date: 11/11/2025
ms.date: 07/10/2026
uid: blazor/components/dynamiccomponent
---
# Dynamically-rendered ASP.NET Core Razor components
Expand Down Expand Up @@ -252,6 +253,120 @@ In the following example:

:::moniker-end

:::moniker range=">= aspnetcore-11.0"

Dictionary values for [C# union-typed parameters](/dotnet/csharp/language-reference/builtin-types/union) must be boxed as the union, not the raw case value, because an implicit conversion is only valid at compile-time.

<!-- UPDATE 11.0 - Remove the NOTE at GA -->

> [!NOTE]
> During the preview of .NET 11, the following example requires the app's project file to specify the "`preview`" C# language version:
>
> ```xml
> <LangVersion>preview</LangVersion>
> ```

Consider the following C# union type, `SlotContent`, that accepts text, a <xref:Microsoft.AspNetCore.Components.MarkupString>, or a <xref:Microsoft.AspNetCore.Components.RenderFragment>:

```csharp
using Microsoft.AspNetCore.Components;

public union SlotContent(string, MarkupString, RenderFragment);
```

The following `Slot` component exposes a component parameter typed to the `SlotContent` union and renders the union's content using two implementations of the [C# `switch` expression](/dotnet/csharp/language-reference/operators/switch-expression), one through an assignment to a <xref:Microsoft.AspNetCore.Components.RenderFragment> (`ContentSwitch`) and one via an inline `switch` expression. In the following example, the [`[EditorRequired]` attribute](xref:Microsoft.AspNetCore.Components.EditorRequiredAttribute) specifies that `Content` is a required component parameter at design-time or build-time.

`Slot.razor`:

```razor
<h2><code>ContentSwitch</code></h2>

<div>
@ContentSwitch()
</div>

<h2>Inline <code>switch</code> expression</h2>

<div>
@switch (Content)
{
case string text:
@text
break;
case MarkupString html:
@html
break;
case RenderFragment fragment:
@fragment
break;
default:
<em>Empty slot!</em>
break;
}
</div>

@code {
[Parameter, EditorRequired]
public SlotContent Content { get; set; }

private RenderFragment ContentSwitch() => Content switch
{
string text => @<span>@text</span>,
MarkupString html => @<span>@html</span>,
RenderFragment fragment => fragment,
_ => @<em>Empty slot!</em>
};
}
```

<span aria-hidden="true">❌</span><span class="visually-hidden">Unsupported:</span> Using the preceding `Slot` component, the following component fails at run time with an <xref:System.InvalidCastException>:

```razor
@page "/slot-test-1"

<DynamicComponent Type="@TargetComponent" Parameters="@BadParameters" />

@code {
[Parameter]
public Type TargetComponent { get; set; } = typeof(Slot);

[Parameter]
public string RawTextPayload { get; set; } = "Hello from dynamic data!";

private Dictionary<string, object> BadParameters => new()
{
{ "Content", RawTextPayload }
};
Comment thread
guardrex marked this conversation as resolved.
}
```

> :::no-loc text="System.InvalidCastException: Unable to cast object of type 'System.String' to type 'SlotContent'.":::

<span aria-hidden="true">βœ”οΈ</span><span class="visually-hidden">Supported:</span> To box the union, wrap the raw value explicitly into the union wrapper type (`new SlotContent(...)`), as the following example demonstrates.

`SlotTest2.razor`:

```razor
@page "/slot-test-2"

<DynamicComponent Type="@TargetComponent" Parameters="@ValidParameters" />

@code {
[Parameter]
public Type TargetComponent { get; set; } = typeof(Slot);

[Parameter]
public string RawTextPayload { get; set; } = "Hello from dynamic data!";

private Dictionary<string, object> ValidParameters => new()
{
{ "Content", new SlotContent(RawTextPayload) }
};
}
```

:::moniker-end

## Event callbacks (`EventCallback`)

Event callbacks (<xref:Microsoft.AspNetCore.Components.EventCallback>) can be passed to a <xref:Microsoft.AspNetCore.Components.DynamicComponent> in its parameter dictionary.
Expand Down
127 changes: 118 additions & 9 deletions aspnetcore/blazor/components/index.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
---
title: ASP.NET Core Razor components
ai-usage: ai-assisted
author: guardrex
description: Learn how to create and use Razor components in Blazor apps, including guidance on Razor syntax, component naming, namespaces, and component parameters.
monikerRange: '>= aspnetcore-3.1'
ms.author: wpickett
ms.custom: mvc
ms.date: 06/24/2026
ms.date: 07/10/2026
uid: blazor/components/index
---
# ASP.NET Core Razor components
Expand Down Expand Up @@ -850,14 +851,6 @@ Assign a C# field, property, or result of a method to a component parameter as a

If the component parameter is of type string, then the attribute value is instead treated as a C# string literal. If you want to specify a C# expression instead, then use the `@` prefix.

:::moniker range=">= aspnetcore-11.0"

<!-- UPDATE 11.0 - Remove the following paragraph per resolution of the PU issue -->

The string-literal shortcut applies only to parameters declared as `string`. A parameter declared as a [C# union type](/dotnet/csharp/whats-new/csharp-14#union-types), even one whose cases include `string`, isn't a `string`-typed parameter, so the attribute value must be a C# expression. Use the `@` prefix, for example `Message="@("Saved.")"`.

:::moniker-end

The following parent component displays four instances of the preceding `ParameterChild` component and sets their `Title` parameter values to:

* The value of the `title` field.
Expand Down Expand Up @@ -1171,6 +1164,122 @@ Quote &copy;2005 [Universal Pictures](https://www.uphe.com): [Serenity](https://

:::moniker-end

:::moniker range=">= aspnetcore-11.0"

Support for [C# union types](/dotnet/csharp/language-reference/builtin-types/union) allows a single component parameter to represent a value that's exactly one of a fixed set of types with compiler-enforced exhaustive pattern matching. A component parameter is set by direct assignment when a component is rendered from Razor markup or through <xref:Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder.AddComponentParameter%2A?displayProperty=nameWithType>.

<!-- UPDATE 11.0 - Remove the NOTE at GA -->

> [!NOTE]
> During the preview of .NET 11, the following example requires the app's project file to specify the "`preview`" C# language version:
>
> ```xml
> <LangVersion>preview</LangVersion>
> ```

The following C# union type, `SlotContent`, accepts text, a <xref:Microsoft.AspNetCore.Components.MarkupString>, or a <xref:Microsoft.AspNetCore.Components.RenderFragment>:

```csharp
using Microsoft.AspNetCore.Components;

public union SlotContent(string, MarkupString, RenderFragment);
```

The following `Slot` component exposes a component parameter typed to the `SlotContent` union and renders the union's content using two implementations of the [C# `switch` expression](/dotnet/csharp/language-reference/operators/switch-expression), one through an assignment to a <xref:Microsoft.AspNetCore.Components.RenderFragment> (`ContentSwitch`) and one via an inline `switch` expression. In the following example, the [`[EditorRequired]` attribute](xref:Microsoft.AspNetCore.Components.EditorRequiredAttribute) specifies that `Content` is a required component parameter at design-time or build-time.

`Slot.razor`:

```razor
<h2><code>ContentSwitch</code></h2>

<div>
@ContentSwitch()
</div>

<h2>Inline <code>switch</code> expression</h2>

<div>
@switch (Content)
{
case string text:
@text
break;
case MarkupString html:
@html
break;
case RenderFragment fragment:
@fragment
break;
default:
<em>Empty slot!</em>
break;
}
</div>

@code {
[Parameter, EditorRequired]
public SlotContent Content { get; set; }

private RenderFragment ContentSwitch() => Content switch
{
string text => @<span>@text</span>,
MarkupString html => @<span>@html</span>,
RenderFragment fragment => fragment,
_ => @<em>Empty slot!</em>
};
}
```

The following `SlotExample` component uses the preceding `SlotContent` type and `Slot` component.

`SlotExample.razor`:

```razor
@page "/slot-example"

<h1>Plain text</h1>

<Slot Content="@("Simple plain text slot")" />

<h1><code>MarkupString</code></h1>

<Slot Content='@((MarkupString)"<strong>Bold HTML slot</strong>")' />

<h1><code>RenderFragment</code></h1>

<Slot Content="@content" />

@code {
private SlotContent content;

protected override void OnInitialized()
{
content = new SlotContent((RenderFragment)(b =>
{
b.OpenElement(0, "button");
b.AddAttribute(1, "onclick", EventCallback.Factory.Create(this, Increment));
b.AddContent(2, "Count: ");
b.AddContent(3, currentCount);
b.CloseElement();
}));
}

private int currentCount = 0;

private void Increment() => currentCount++;
}
```

<!-- UPDATE 11.0 - Remove the following paragraph per resolution of the PU issue -->

The string-literal shortcut applies only to parameters declared as `string`. A parameter declared as a C# union type, even one whose cases include `string`, isn't a `string`-typed parameter, so the attribute value must be a C# expression. Use the `@` prefix, as demonstrated by `Content="@("Simple plain text slot")"` in the preceding example.

<!-- UPDATE 13.0 - Remove the following cross-link when C# unions get some time on them (target removal for .NET 13) -->

For more information, see [Explore union types in C# 15](https://devblogs.microsoft.com/dotnet/csharp-15-union-types/).

:::moniker-end

## Route parameters

Components can specify route parameters in the route template of the [`@page`][9] directive. The [Blazor router](xref:blazor/fundamentals/routing) uses route parameters to populate corresponding component parameters.
Expand Down
15 changes: 14 additions & 1 deletion aspnetcore/blazor/components/virtualization.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Learn how to use component virtualization in ASP.NET Core Blazor ap
monikerRange: '>= aspnetcore-5.0'
ms.author: wpickett
ms.custom: mvc
ms.date: 11/11/2025
ms.date: 07/13/2026
uid: blazor/components/virtualization
---
# ASP.NET Core Razor component virtualization
Expand Down Expand Up @@ -472,3 +472,16 @@ In the preceding example, the document root is used as the scroll container, so
* <xref:blazor/components/control-head-content>

:::moniker-end

:::moniker range=">= aspnetcore-7.0"

## Content Security Policy (CSP) compliance

The `Virtualize` component renders dynamic inline `style` attributes on its spacer and placeholder elements because spacer heights are calculated at runtime based on scroll position, item count, and average item size, which change on every scroll interaction.

To avoid CSP violations, `Virtualize` components:

* Render CSS styles in a `data-blazor-style` attribute instead of a `style` attribute.
* Use a JS [`MutationObserver`](https://developer.mozilla.org/docs/Web/API/MutationObserver) to read the attribute's value and apply each declaration via the [CSS Object Model (CSSOM)](https://developer.mozilla.org/docs/Web/API/CSS_Object_Model): `element.style.setProperty(name, value)`.

:::moniker-end
18 changes: 9 additions & 9 deletions aspnetcore/blazor/fundamentals/environments.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,30 +291,30 @@ App settings from the `appsettings.{ENVIRONMENT}.json` file are loaded by the ap

:::moniker range=">= aspnetcore-11.0"

## `EnvironmentBoundary` component
## `EnvironmentView` component

<!-- UPDATE 11.0 - API Browser cross-links -->

Use the `EnvironmentBoundary` component for conditional rendering based on the hosting environment. This component provides a consistent way to render content based on the current environment across both server-side and client-side hosting models.
Use the `EnvironmentView` component for conditional rendering based on the hosting environment. This component provides a consistent way to render content based on the current environment across both server-side and client-side hosting models.

The `EnvironmentBoundary` component accepts `Include` and `Exclude` parameters for specifying environment names. The component performs case-insensitive matching.
The `EnvironmentView` component accepts `Include` and `Exclude` parameters for specifying environment names. The component performs case-insensitive matching.

```razor
@using Microsoft.AspNetCore.Components.Web

<EnvironmentBoundary Include="Development">
<EnvironmentView Include="Development">
<div class="alert alert-warning">
Debug mode enabled
</div>
</EnvironmentBoundary>
</EnvironmentView>

<EnvironmentBoundary Include="Development,Staging">
<EnvironmentView Include="Development,Staging">
<p>Pre-production environment</p>
</EnvironmentBoundary>
</EnvironmentView>

<EnvironmentBoundary Exclude="Production">
<EnvironmentView Exclude="Production">
<p>@DateTime.Now</p>
</EnvironmentBoundary>
</EnvironmentView>
```

:::moniker-end
Expand Down
8 changes: 4 additions & 4 deletions aspnetcore/blazor/fundamentals/navigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -855,7 +855,7 @@ The correct culture-invariant formatting is applied for the given type (<xref:Sy
:::moniker range=">= aspnetcore-11.0"

> [!NOTE]
> [C# union types](/dotnet/csharp/whats-new/csharp-14#union-types) aren't supported by `[SupplyParameterFromQuery]` or `[SupplyParameterFromForm]`, which bind string or form values without JSON parsing.
> [C# union types](/dotnet/csharp/language-reference/builtin-types/union) aren't supported by `[SupplyParameterFromQuery]` or `[SupplyParameterFromForm]`, which bind string or form values without JSON parsing.

:::moniker-end

Expand Down Expand Up @@ -1182,22 +1182,22 @@ For more information on JavaScript isolation with JavaScript modules, see <xref:

<!-- UPDATE 11.0 - API Browser cross-links and improve the example -->

Use the `GetUriWithHash` extension method to construct a URI with a hash fragment. This helper method provides an efficient, zero-allocation way to append hash fragments to the current URI. The following example demonstrates two use cases:
Use the `GetUriWithFragment` extension method to construct a URI with a hash fragment. This helper method provides an efficient, zero-allocation way to append hash fragments to the current URI. The following example demonstrates two use cases:

* Inline call that jumps to Section 1 (`id="section-1"`) of the rendered page.
* Method call that receives a section identifier (`sectionId`) and navigates to the section of the page.

```razor
@inject NavigationManager Navigation

<a href="@Navigation.GetUriWithHash("section-1")">
<a href="@Navigation.GetUriWithFragment("section-1")">
Jump to Section 1
</a>

@code {
private void NavigateToSection(string sectionId)
{
var uri = Navigation.GetUriWithHash(sectionId);
var uri = Navigation.GetUriWithFragment(sectionId);
Navigation.NavigateTo(uri);
}
}
Expand Down
2 changes: 1 addition & 1 deletion aspnetcore/blazor/fundamentals/routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ When the [`OnInitialized{Async}` lifecycle method](xref:blazor/components/lifecy
:::moniker range=">= aspnetcore-11.0"

> [!NOTE]
> Route parameters can't be typed as a [C# union](/dotnet/csharp/whats-new/csharp-14#union-types). The router parses route segments as strings and converts them to the target type without JSON parsing, so it can't dispatch to a union case.
> Route parameters can't be typed as a [C# union](/dotnet/csharp/language-reference/builtin-types/union). The router parses route segments as strings and converts them to the target type without JSON parsing, so it can't dispatch to a union case.

:::moniker-end

Expand Down
Loading
Loading