From 82d2bb10b4b87f7196c4cbecdcbad48a7769f123 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Mon, 25 Jul 2022 11:45:48 -0500 Subject: [PATCH 1/6] Blazor template and prerendering updates --- aspnetcore/blazor/advanced-scenarios.md | 117 +- aspnetcore/blazor/call-web-api.md | 619 ++++++- .../cascading-values-and-parameters.md | 250 ++- .../blazor/components/class-libraries.md | 4 +- .../blazor/components/control-head-content.md | 64 + aspnetcore/blazor/components/css-isolation.md | 2 +- aspnetcore/blazor/components/data-binding.md | 260 ++- .../blazor/components/dynamiccomponent.md | 238 +++ .../blazor/components/event-handling.md | 333 +++- aspnetcore/blazor/components/index.md | 1497 ++++++++++++++++- aspnetcore/blazor/components/layouts.md | 219 ++- aspnetcore/blazor/components/lifecycle.md | 460 ++++- .../prerendering-and-integration.md | 958 ++++++++++- aspnetcore/blazor/components/rendering.md | 104 +- .../blazor/components/templated-components.md | 53 +- aspnetcore/blazor/debug.md | 394 ++++- aspnetcore/blazor/file-downloads.md | 144 +- aspnetcore/blazor/file-uploads.md | 545 +++++- aspnetcore/blazor/forms-validation.md | 1141 ++++++++++++- .../fundamentals/dependency-injection.md | 383 ++++- .../blazor/fundamentals/environments.md | 140 +- .../blazor/fundamentals/handle-errors.md | 490 +++++- aspnetcore/blazor/fundamentals/logging.md | 547 +++++- aspnetcore/blazor/fundamentals/routing.md | 652 ++++++- aspnetcore/blazor/fundamentals/signalr.md | 322 +++- aspnetcore/blazor/fundamentals/startup.md | 184 +- .../blazor/globalization-localization.md | 884 +++++++++- aspnetcore/blazor/host-and-deploy/index.md | 8 +- aspnetcore/blazor/host-and-deploy/server.md | 197 ++- aspnetcore/blazor/hosting-models.md | 188 ++- .../blazor/hybrid/tutorials/windows-forms.md | 14 + aspnetcore/blazor/hybrid/tutorials/wpf.md | 14 + aspnetcore/blazor/images.md | 110 +- aspnetcore/blazor/includes/prerendering.md | 4 +- .../call-dotnet-from-javascript.md | 748 +++++++- .../call-javascript-from-dotnet.md | 849 +++++++++- .../javascript-interoperability/index.md | 231 ++- aspnetcore/blazor/performance.md | 732 +++++++- aspnetcore/blazor/project-structure.md | 137 +- .../security/content-security-policy.md | 4 +- aspnetcore/blazor/state-management.md | 2 +- .../blazor/tutorials/build-a-blazor-app.md | 183 +- 42 files changed, 14302 insertions(+), 123 deletions(-) diff --git a/aspnetcore/blazor/advanced-scenarios.md b/aspnetcore/blazor/advanced-scenarios.md index 683766e680db..bdefb0851015 100644 --- a/aspnetcore/blazor/advanced-scenarios.md +++ b/aspnetcore/blazor/advanced-scenarios.md @@ -15,7 +15,7 @@ This article describes the advanced scenario for building Blazor render trees ma > [!WARNING] > Use of to create components is an *advanced scenario*. A malformed component (for example, an unclosed markup tag) can result in undefined behavior. Undefined behavior includes broken content rendering, loss of app features, and ***compromised security***. -:::moniker range=">= aspnetcore-6.0" +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" ## Manually build a render tree (`RenderTreeBuilder`) @@ -359,3 +359,118 @@ This is a trivial example. In more realistic cases with complex and deeply neste * Blazor uses sequence numbers, while other tree-diffing UI frameworks don't use them. Diffing is far faster when sequence numbers are used, and Blazor has the advantage of a compile step that deals with sequence numbers automatically for developers authoring `.razor` files. :::moniker-end + +:::moniker range=">= aspnetcore-7.0" + +## Manually build a render tree (`RenderTreeBuilder`) + + provides methods for manipulating components and elements, including building components manually in C# code. + +Consider the following `PetDetails` component, which can be manually rendered in another component. + +`Shared/PetDetails.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/advanced-scenarios/PetDetails.razor"::: + +In the following `BuiltContent` component, the loop in the `CreateComponent` method generates three `PetDetails` components. + +In methods with a sequence number, sequence numbers are source code line numbers. The Blazor difference algorithm relies on the sequence numbers corresponding to distinct lines of code, not distinct call invocations. When creating a component with methods, hardcode the arguments for sequence numbers. **Using a calculation or counter to generate the sequence number can lead to poor performance.** For more information, see the [Sequence numbers relate to code line numbers and not execution order](#sequence-numbers-relate-to-code-line-numbers-and-not-execution-order) section. + +`Pages/BuiltContent.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/advanced-scenarios/BuiltContent.razor" highlight="6,16-24,28"::: + +> [!WARNING] +> The types in allow processing of the *results* of rendering operations. These are internal details of the Blazor framework implementation. These types should be considered *unstable* and subject to change in future releases. + +### Sequence numbers relate to code line numbers and not execution order + +Razor component files (`.razor`) are always compiled. Executing compiled code has a potential advantage over interpreting code because the compile step that yields the compiled code can be used to inject information that improves app performance at runtime. + +A key example of these improvements involves *sequence numbers*. Sequence numbers indicate to the runtime which outputs came from which distinct and ordered lines of code. The runtime uses this information to generate efficient tree diffs in linear time, which is far faster than is normally possible for a general tree diff algorithm. + +Consider the following Razor component file (`.razor`): + +```razor +@if (someFlag) +{ + First +} + +Second +``` + +The preceding Razor markup and text content compiles into C# code similar to the following: + +```csharp +if (someFlag) +{ + builder.AddContent(0, "First"); +} + +builder.AddContent(1, "Second"); +``` + +When the code executes for the first time and `someFlag` is `true`, the builder receives the sequence in the following table. + +| Sequence | Type | Data | +| :------: | --------- | :----: | +| 0 | Text node | First | +| 1 | Text node | Second | + +Imagine that `someFlag` becomes `false` and the markup is rendered again. This time, the builder receives the sequence in the following table. + +| Sequence | Type | Data | +| :------: | ---------- | :----: | +| 1 | Text node | Second | + +When the runtime performs a diff, it sees that the item at sequence `0` was removed, so it generates the following trivial *edit script* with a single step: + +* Remove the first text node. + +### The problem with generating sequence numbers programmatically + +Imagine instead that you wrote the following render tree builder logic: + +```csharp +var seq = 0; + +if (someFlag) +{ + builder.AddContent(seq++, "First"); +} + +builder.AddContent(seq++, "Second"); +``` + +The first output is reflected in the following table. + +| Sequence | Type | Data | +| :------: | --------- | :----: | +| 0 | Text node | First | +| 1 | Text node | Second | + +This outcome is identical to the prior case, so no negative issues exist. `someFlag` is `false` on the second rendering, and the output is seen in the following table. + +| Sequence | Type | Data | +| :------: | --------- | ------ | +| 0 | Text node | Second | + +This time, the diff algorithm sees that *two* changes have occurred. The algorithm generates the following edit script: + +* Change the value of the first text node to `Second`. +* Remove the second text node. + +Generating the sequence numbers has lost all the useful information about where the `if/else` branches and loops were present in the original code. This results in a diff **twice as long** as before. + +This is a trivial example. In more realistic cases with complex and deeply nested structures, and especially with loops, the performance cost is usually higher. Instead of immediately identifying which loop blocks or branches have been inserted or removed, the diff algorithm must recurse deeply into the render trees. This usually results in building longer edit scripts because the diff algorithm is misinformed about how the old and new structures relate to each other. + +### Guidance and conclusions + +* App performance suffers if sequence numbers are generated dynamically. +* The framework can't create its own sequence numbers automatically at runtime because the necessary information doesn't exist unless it's captured at compile time. +* Don't write long blocks of manually-implemented logic. Prefer `.razor` files and allow the compiler to deal with the sequence numbers. If you're unable to avoid manual logic, split long blocks of code into smaller pieces wrapped in / calls. Each region has its own separate space of sequence numbers, so you can restart from zero (or any other arbitrary number) inside each region. +* If sequence numbers are hardcoded, the diff algorithm only requires that sequence numbers increase in value. The initial value and gaps are irrelevant. One legitimate option is to use the code line number as the sequence number, or start from zero and increase by ones or hundreds (or any preferred interval). +* Blazor uses sequence numbers, while other tree-diffing UI frameworks don't use them. Diffing is far faster when sequence numbers are used, and Blazor has the advantage of a compile step that deals with sequence numbers automatically for developers authoring `.razor` files. + +:::moniker-end diff --git a/aspnetcore/blazor/call-web-api.md b/aspnetcore/blazor/call-web-api.md index ccd0b17588c3..7d3890bd9d6b 100644 --- a/aspnetcore/blazor/call-web-api.md +++ b/aspnetcore/blazor/call-web-api.md @@ -13,7 +13,7 @@ zone_pivot_groups: blazor-hosting-models This article describes how to call a web API from a Blazor app. -:::moniker range=">= aspnetcore-6.0" +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" :::zone pivot="webassembly" @@ -1867,3 +1867,620 @@ Various network tools are publicly available for testing web API backend apps di :::zone-end :::moniker-end + +:::moniker range=">= aspnetcore-7.0" + +:::zone pivot="webassembly" + +> [!NOTE] +> This article has loaded **Blazor WebAssembly** coverage for calling web APIs. The [Blazor Server coverage](?pivots=server) addresses the following subjects: +> +> * Use of the `HttpClient` factory infrastructure to provide an `HttpClient` to the app. +> * Cross-origin resource sharing (CORS) pertaining to Blazor Server apps. +> * Blazor framework component examples for testing web API access. +> * Additional resources for developing Blazor Server apps that call a web API. + +[Blazor WebAssembly](xref:blazor/hosting-models#blazor-webassembly) apps call web APIs using a preconfigured service, which is focused on making requests back to the server of origin. Additional service configurations for other web APIs can be created in developer code. Requests are composed using Blazor JSON helpers or with . Requests can include [Fetch API](https://developer.mozilla.org/docs/Web/API/Fetch_API) option configuration. + +## Examples in this article + +In this article's component examples, a hypothetical todo list web API is used to create, read, update, and delete (CRUD) todo items on a server. The examples are based on a `TodoItem` class that stores the following todo item data: + +* ID (`Id`, `long`): Unique ID of the item. +* Name (`Name`, `string`): Name of the item. +* Status (`IsComplete`, `bool`): Indication if the todo item is finished. + +Use the following `TodoItem` class with this article's examples if you build the examples into a test app: + +```csharp +public class TodoItem +{ + public long Id { get; set; } + public string? Name { get; set; } + public bool IsComplete { get; set; } +} +``` + +For guidance on how to create a server-side web API, see . For information on Cross-origin resource sharing (CORS), see the [CORS guidance](#cross-origin-resource-sharing-cors) later in this article. + +## Packages + +Add a package reference for [`System.Net.Http.Json`](https://www.nuget.org/packages/System.Net.Http.Json). + +[!INCLUDE[](~/includes/package-reference.md)] + +## Add the `HttpClient` service + +In `Program.cs`, add an service if it isn't already present from a Blazor project template used to create the app: + +```csharp +builder.Services.AddScoped(sp => + new HttpClient + { + BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) + }); +``` + +## `HttpClient` and JSON helpers + + is available as a preconfigured service for making requests back to the origin server. + + and JSON helpers () are also used to call third-party web API endpoints. is implemented using the browser's [Fetch API](https://developer.mozilla.org/docs/Web/API/Fetch_API) and is subject to its limitations, including enforcement of the [same-origin policy (discussed later in this article)](#cross-origin-resource-sharing-cors). + +The client's base address is set to the originating server's address. Inject an instance into a component using the [`@inject`](xref:mvc/views/razor#inject) directive: + +```razor +@using System.Net.Http +@inject HttpClient Http +``` + +Use the namespace for access to , including , , and : + +```razor +@using System.Net.Http.Json +``` + +### GET from JSON (`GetFromJsonAsync`) + + sends an HTTP GET request and parses the JSON response body to create an object. + +In the following component code, the `todoItems` are displayed by the component. is called when the component is finished initializing ([`OnInitializedAsync`](xref:blazor/components/lifecycle#component-initialization-oninitializedasync)). + +```razor +@using System.Net.Http +@using System.Net.Http.Json +@using System.Threading.Tasks +@inject HttpClient Http + +@if (todoItems == null) +{ +

No Todo Items found.

+} +else +{ +
    + @foreach (var item in todoItems) + { +
  • @item.Name
  • + } +
+} + +@code { + private TodoItem[]? todoItems; + + protected override async Task OnInitializedAsync() => + todoItems = await Http.GetFromJsonAsync("api/TodoItems"); +} +``` + +### POST as JSON (`PostAsJsonAsync`) + + sends a POST request to the specified URI containing the value serialized as JSON in the request body. + +In the following component code, `newItemName` is provided by a bound element of the component. The `AddItem` method is triggered by selecting a ` + +@code { + private string? newItemName; + + private async Task AddItem() + { + var addItem = new TodoItem { Name = newItemName, IsComplete = false }; + await Http.PostAsJsonAsync("api/TodoItems", addItem); + } +} +``` + +Calls to return an . To deserialize the JSON content from the response message, use the extension method. The following example reads JSON weather data: + +```csharp +var content = await response.Content.ReadFromJsonAsync(); +``` + +### PUT as JSON (`PutAsJsonAsync`) + + sends an HTTP PUT request with JSON-encoded content. + +In the following component code, `editItem` values for `Name` and `IsCompleted` are provided by bound elements of the component. The item's `Id` is set when the item is selected in another part of the UI (not shown) and `EditItem` is called. The `SaveItem` method is triggered by selecting the ` + +@code { + private string? id; + private TodoItem editItem = new TodoItem(); + + private void EditItem(long id) + { + editItem = todoItems.Single(i => i.Id == id); + } + + private async Task SaveItem() => + await Http.PutAsJsonAsync($"api/TodoItems/{editItem.Id}", editItem); +} +``` + +Calls to return an . To deserialize the JSON content from the response message, use the extension method. The following example reads JSON weather data: + +```csharp +var content = await response.Content.ReadFromJsonAsync(); +``` + +### Additional extension methods + + includes additional extension methods for sending HTTP requests and receiving HTTP responses. is used to send an HTTP DELETE request to a web API. + +In the following component code, the ` + +@code { + private long id; + + private async Task DeleteItem() => + await Http.DeleteAsync($"api/TodoItems/{id}"); +} +``` + +## Named `HttpClient` with `IHttpClientFactory` + + services and the configuration of a named are supported. + +Add the [`Microsoft.Extensions.Http`](https://www.nuget.org/packages/Microsoft.Extensions.Http) NuGet package to the app. + +[!INCLUDE[](~/includes/package-reference.md)] + +In `Program.cs`: + +```csharp +builder.Services.AddHttpClient("WebAPI", client => + client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)); +``` + +In the following component code: + +* An instance of creates a named . +* The named is used to issue a GET request for JSON weather forecast data from the web API. + +`Pages/FetchDataViaFactory.razor`: + +```razor +@page "/fetch-data-via-factory" +@using System.Net.Http +@using System.Net.Http.Json +@using System.Threading.Tasks +@inject IHttpClientFactory ClientFactory + +

Fetch data via IHttpClientFactory

+ +@if (forecasts == null) +{ +

Loading...

+} +else +{ +

Temperatures by Date

+ +
    + @foreach (var forecast in forecasts) + { +
  • + @forecast.Date.ToShortDateString(): + @forecast.TemperatureC ℃ + @forecast.TemperatureF ℉ +
  • + } +
+} + +@code { + private WeatherForecast[]? forecasts; + + protected override async Task OnInitializedAsync() + { + var client = ClientFactory.CreateClient("WebAPI"); + + forecasts = await client.GetFromJsonAsync( + "WeatherForecast"); + } +} +``` + +## Typed `HttpClient` + +Typed uses one or more of the app's instances, default or named, to return data from one or more web API endpoints. + +`WeatherForecastClient.cs`: + +```csharp +using System.Net.Http; +using System.Net.Http.Json; +using System.Threading.Tasks; + +public class WeatherForecastHttpClient +{ + private readonly HttpClient http; + + public WeatherForecastHttpClient(HttpClient http) + { + this.http = http; + } + + public async Task GetForecastAsync() + { + try + { + return await http.GetFromJsonAsync( + "WeatherForecast"); + } + catch + { + ... + + return new WeatherForecast[0]; + } + } +} +``` + +In `Program.cs`: + +```csharp +builder.Services.AddHttpClient(client => + client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)); +``` + +Components inject the typed to call the web API. + +In the following component code: + +* An instance of the preceding `WeatherForecastHttpClient` is injected, which creates a typed . +* The typed is used to issue a GET request for JSON weather forecast data from the web API. + +`Pages/FetchDataViaTypedHttpClient.razor`: + +```razor +@page "/fetch-data-via-typed-httpclient" +@using System.Threading.Tasks +@inject WeatherForecastHttpClient Http + +

Fetch data via typed HttpClient

+ +@if (forecasts == null) +{ +

Loading...

+} +else +{ +

Temperatures by Date

+ +
    + @foreach (var forecast in forecasts) + { +
  • + @forecast.Date.ToShortDateString(): + @forecast.TemperatureC ℃ + @forecast.TemperatureF ℉ +
  • + } +
+} + +@code { + private WeatherForecast[]? forecasts; + + protected override async Task OnInitializedAsync() + { + forecasts = await Http.GetForecastAsync(); + } +} +``` + +## `HttpClient` and `HttpRequestMessage` with Fetch API request options + +[`HttpClient`](xref:fundamentals/http-requests) ([API documentation](xref:System.Net.Http.HttpClient)) and can be used to customize requests. For example, you can specify the HTTP method and request headers. The following component makes a `POST` request to a web API endpoint and shows the response body. + +`Pages/TodoRequest.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/call-web-api/TodoRequest.razor"::: + +Blazor WebAssembly's implementation of uses [Fetch API](https://developer.mozilla.org/docs/Web/API/fetch). Fetch API allows the configuration of several [request-specific options](https://developer.mozilla.org/docs/Web/API/fetch#Parameters). Options can be configured with extension methods shown in the following table. + +| Extension method | Fetch API request property | +| --- | --- | +| | [`cache`](https://developer.mozilla.org/docs/Web/API/Request/cache) | +| | [`credentials`](https://developer.mozilla.org/docs/Web/API/Request/credentials) | +| | [`integrity`](https://developer.mozilla.org/docs/Web/API/Request/integrity) | +| | [`mode`](https://developer.mozilla.org/docs/Web/API/Request/mode) | + +Set additional options using the generic extension method. + +The HTTP response is typically buffered to enable support for synchronous reads on the response content. To enable support for response streaming, use the extension method on the request. + +To include credentials in a cross-origin request, use the extension method: + +```csharp +requestMessage.SetBrowserRequestCredentials(BrowserRequestCredentials.Include); +``` + +For more information on Fetch API options, see [MDN web docs: WindowOrWorkerGlobalScope.fetch(): Parameters](https://developer.mozilla.org/docs/Web/API/fetch#Parameters). + +## Call web API example + +The following example calls a web API. The example requires a running web API based on the sample app described by the article. This example makes requests to the web API at `https://localhost:10000/api/TodoItems`. If a different web API address is used, update the `ServiceEndpoint` constant value in the component's `@code` block. + +The following example makes a [cross-origin resource sharing (CORS)](xref:security/cors) request from `http://localhost:5000` or `https://localhost:5001` to the web API. Add the following CORS middleware configuration to the web API's service's `Program.cs` file: + +```csharp +app.UseCors(policy => + policy.WithOrigins("http://localhost:5000", "https://localhost:5001") + .AllowAnyMethod() + .WithHeaders(HeaderNames.ContentType)); +``` + +Adjust the domains and ports of `WithOrigins` as needed for the Blazor app. For more information, see . + +By default, ASP.NET Core apps use ports 5000 (HTTP) and 5001 (HTTPS). To run both apps on the same machine at the same time for testing, use a different port for the web API app (for example, port 10000). For more information on setting the port, see . + +`Pages/CallWebAPI.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/call-web-api/CallWebAPI.razor"::: + +## Handle errors + +Handle web API response errors in developer code when they occur. For example, expects a JSON response from the web API with a `Content-Type` of `application/json`. If the response isn't in JSON format, content validation throws a . + +In the following example, the URI endpoint for the weather forecast data request is misspelled. The URI should be to `WeatherForecast` but appears in the call as `WeatherForcast`, which is missing the letter `e` in `Forecast`. + +The call expects JSON to be returned, but the web API returns HTML for an unhandled exception with a `Content-Type` of `text/html`. The unhandled exception occurs because the path to `/WeatherForcast` isn't found and middleware can't serve a page or view for the request. + +In on the client, is thrown when the response content is validated as non-JSON. The exception is caught in the `catch` block, where custom logic could log the error or present a friendly error message to the user. + +`Pages/FetchDataReturnsHTMLOnException.razor`: + +```razor +@page "/fetch-data-returns-html-on-exception" +@using System.Net.Http +@using System.Net.Http.Json +@using System.Threading.Tasks +@inject HttpClient Http + +

Fetch data but receive HTML on unhandled exception

+ +@if (forecasts == null) +{ +

Loading...

+} +else +{ +

Temperatures by Date

+ +
    + @foreach (var forecast in forecasts) + { +
  • + @forecast.Date.ToShortDateString(): + @forecast.TemperatureC ℃ + @forecast.TemperatureF ℉ +
  • + } +
+} + +

+ @exceptionMessage +

+ +@code { + private WeatherForecast[]? forecasts; + private string? exceptionMessage; + + protected override async Task OnInitializedAsync() + { + try + { + // The URI endpoint "WeatherForecast" is misspelled on purpose on the + // next line. See the preceding text for more information. + forecasts = await Http.GetFromJsonAsync("WeatherForcast"); + } + catch (NotSupportedException exception) + { + exceptionMessage = exception.Message; + } + } +} +``` + +> [!NOTE] +> The preceding example is for demonstration purposes. A web API can be configured to return JSON even when an endpoint doesn't exist or an unhandled exception occurs on the server. + +For more information, see . + +:::zone-end + +:::zone pivot="server" + +> [!NOTE] +> This article has loaded **Blazor Server** coverage for calling web APIs. The [Blazor WebAssembly coverage](?pivots=webassembly) addresses the following subjects: +> +> * Blazor WebAssembly examples based on an client-side WebAssembly app that calls a web API to create, read, update, and delete todo list items. +> * `System.Net.Http.Json` package. +> * `HttpClient` service configuration. +> * `HttpClient` and JSON helpers (`GetFromJsonAsync`, `PostAsJsonAsync`, `PutAsJsonAsync`, `DeleteAsync`). +> * `IHttpClientFactory` services and the configuration of a named `HttpClient`. +> * Typed `HttpClient`. +> * `HttpClient` and `HttpRequestMessage` to customize requests. +> * Call web API example with cross-origin resource sharing (CORS) and how CORS pertains to Blazor WebAssembly apps. +> * How to handle web API response errors in developer code. +> * Blazor framework component examples for testing web API access. +> * Additional resources for developing Blazor WebAssembly apps that call a web API. + +[Blazor Server](xref:blazor/hosting-models#blazor-server) apps call web APIs using instances, typically created using . For guidance that applies to Blazor Server, see . + +A Blazor Server app doesn't include an service by default. Provide an to the app using the [`HttpClient` factory infrastructure](xref:fundamentals/http-requests). + +In `Program.cs`: + +```csharp +builder.Services.AddHttpClient(); +``` + +The following Blazor Server Razor component makes a request to a web API for GitHub branches similar to the *Basic Usage* example in the article. + +`Pages/CallWebAPI.razor`: + +```razor +@page "/call-web-api" +@using System.Text.Json +@using System.Text.Json.Serialization; +@inject IHttpClientFactory ClientFactory + +

Call web API from a Blazor Server Razor component

+ +@if (getBranchesError) +{ +

Unable to get branches from GitHub. Please try again later.

+} +else +{ +
    + @foreach (var branch in branches) + { +
  • @branch.Name
  • + } +
+} + +@code { + private IEnumerable branches = Array.Empty(); + private bool getBranchesError; + private bool shouldRender; + + protected override bool ShouldRender() => shouldRender; + + protected override async Task OnInitializedAsync() + { + var request = new HttpRequestMessage(HttpMethod.Get, + "https://api.github.com/repos/dotnet/AspNetCore.Docs/branches"); + request.Headers.Add("Accept", "application/vnd.github.v3+json"); + request.Headers.Add("User-Agent", "HttpClientFactory-Sample"); + + var client = ClientFactory.CreateClient(); + + var response = await client.SendAsync(request); + + if (response.IsSuccessStatusCode) + { + using var responseStream = await response.Content.ReadAsStreamAsync(); + branches = await JsonSerializer.DeserializeAsync + >(responseStream); + } + else + { + getBranchesError = true; + } + + shouldRender = true; + } + + public class GitHubBranch + { + [JsonPropertyName("name")] + public string Name { get; set; } + } +} +``` + +For an additional working example, see the Blazor Server file upload example that uploads files to a web API controller in the article. + +:::zone-end + +## Cross-origin resource sharing (CORS) + +Browser security restricts a webpage from making requests to a different domain than the one that served the webpage. This restriction is called the *same-origin policy*. The same-origin policy restricts (but doesn't prevent) a malicious site from reading sensitive data from another site. To make requests from the browser to an endpoint with a different origin, the *endpoint* must enable [cross-origin resource sharing (CORS)](https://www.w3.org/TR/cors/). + +:::zone pivot="webassembly" + +For information on CORS requests in Blazor WebAssembly apps, see . + +For information on CORS, see . The article's examples don't pertain directly to Blazor WebAssembly apps, but the article is useful for learning general CORS concepts. + +:::zone-end + +:::zone pivot="server" + +For more information, see . + +:::zone-end + +## Blazor framework component examples for testing web API access + +Various network tools are publicly available for testing web API backend apps directly, such as [Firefox Browser Developer](https://www.mozilla.org/firefox/developer/) and [Postman](https://www.postman.com). Blazor framework's reference source includes test assets that are useful for testing: + +[`HttpClientTest` assets in the `dotnet/aspnetcore` GitHub repository](https://github.com/dotnet/aspnetcore/tree/main/src/Components/test/testassets/BasicTestApp/HttpClientTest) + +## Additional resources + +:::zone pivot="webassembly" + +* : Includes coverage on using to make secure web API requests. +* : Although the content applies to ASP.NET Core apps, not Blazor WebAssembly apps, the article covers general CORS concepts. +* [Cross Origin Resource Sharing (CORS) at W3C](https://www.w3.org/TR/cors/) +* [Fetch API](https://developer.mozilla.org/docs/Web/API/fetch) + +:::zone-end + +:::zone pivot="server" + +* : Includes coverage on using to make secure web API requests. +* +* +* +* [Kestrel HTTPS endpoint configuration](xref:fundamentals/servers/kestrel/endpoints) +* [Cross Origin Resource Sharing (CORS) at W3C](https://www.w3.org/TR/cors/) + +:::zone-end + +:::moniker-end diff --git a/aspnetcore/blazor/components/cascading-values-and-parameters.md b/aspnetcore/blazor/components/cascading-values-and-parameters.md index e451376f5dbf..963d8c07c1dd 100644 --- a/aspnetcore/blazor/components/cascading-values-and-parameters.md +++ b/aspnetcore/blazor/components/cascading-values-and-parameters.md @@ -12,7 +12,7 @@ uid: blazor/components/cascading-values-and-parameters This article explains how to flow data from an ancestor Razor component to descendent components. -:::moniker range=">= aspnetcore-6.0" +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" *Cascading values and parameters* provide a convenient way to flow data down a component hierarchy from an ancestor component to any number of descendent components. Unlike [Component parameters](xref:blazor/components/index#component-parameters), cascading values and parameters don't require an attribute assignment for each descendent component where the data is consumed. Cascading values and parameters also allow components to coordinate with each other across a component hierarchy. @@ -699,3 +699,251 @@ The following `ExampleTabSet` component uses the `TabSet` component, which conta ``` :::moniker-end + +:::moniker range=">= aspnetcore-7.0" + +*Cascading values and parameters* provide a convenient way to flow data down a component hierarchy from an ancestor component to any number of descendent components. Unlike [Component parameters](xref:blazor/components/index#component-parameters), cascading values and parameters don't require an attribute assignment for each descendent component where the data is consumed. Cascading values and parameters also allow components to coordinate with each other across a component hierarchy. + +## `CascadingValue` component + +An ancestor component provides a cascading value using the Blazor framework's [`CascadingValue`](xref:Microsoft.AspNetCore.Components.CascadingValue%601) component, which wraps a subtree of a component hierarchy and supplies a single value to all of the components within its subtree. + +The following example demonstrates the flow of theme information down the component hierarchy of a layout component to provide a CSS style class to buttons in child components. + +The following `ThemeInfo` C# class is placed in a folder named `UIThemeClasses` and specifies the theme information. + +> [!NOTE] +> For the examples in this section, the app's namespace is `BlazorSample`. When experimenting with the code in your own sample app, change the app's namespace to your sample app's namespace. + +`UIThemeClasses/ThemeInfo.cs`: + +:::code language="csharp" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/UIThemeClasses/ThemeInfo.cs"::: + +The following [layout component](xref:blazor/components/layouts) specifies theme information (`ThemeInfo`) as a cascading value for all components that make up the layout body of the property. `ButtonClass` is assigned a value of [`btn-success`](https://getbootstrap.com/docs/5.0/components/buttons/), which is a Bootstrap button style. Any descendent component in the component hierarchy can use the `ButtonClass` property through the `ThemeInfo` cascading value. + +`Shared/MainLayout.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/MainLayout.razor"::: + +## `[CascadingParameter]` attribute + +To make use of cascading values, descendent components declare cascading parameters using the [`[CascadingParameter]` attribute](xref:Microsoft.AspNetCore.Components.CascadingParameterAttribute). Cascading values are bound to cascading parameters **by type**. Cascading multiple values of the same type is covered in the [Cascade multiple values](#cascade-multiple-values) section later in this article. + +The following component binds the `ThemeInfo` cascading value to a cascading parameter, optionally using the same name of `ThemeInfo`. The parameter is used to set the CSS class for the **`Increment Counter (Themed)`** button. + +`Pages/ThemedCounter.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/ThemedCounter.razor"::: + +Similar to a regular component parameter, components accepting a cascading parameter are rerendered when the cascading value is changed. For instance, configuring a different theme instance causes the `ThemedCounter` component from the [`CascadingValue` component](#cascadingvalue-component) section to rerender: + +`Shared/MainLayout.razor`: + +```razor +
+
+ About +
+ + +
+ @Body +
+
+ +
+ +@code { + private ThemeInfo theme = new() { ButtonClass = "btn-success" }; + + private void ChangeToDarkTheme() + { + theme = new() { ButtonClass = "btn-darkmode-success" }; + } +} +``` + + can be used to indicate that a cascading parameter doesn't change after initialization. + +## Cascade multiple values + +To cascade multiple values of the same type within the same subtree, provide a unique string to each [`CascadingValue`](xref:Microsoft.AspNetCore.Components.CascadingValue%601) component and their corresponding [`[CascadingParameter]` attributes](xref:Microsoft.AspNetCore.Components.CascadingParameterAttribute). + +In the following example, two [`CascadingValue`](xref:Microsoft.AspNetCore.Components.CascadingValue%601) components cascade different instances of `CascadingType`: + +```razor + + + ... + + + +@code { + private CascadingType? parentCascadeParameter1; + + [Parameter] + public CascadingType? ParentCascadeParameter2 { get; set; } +} +``` + +In a descendant component, the cascaded parameters receive their cascaded values from the ancestor component by : + +```razor +@code { + [CascadingParameter(Name = "CascadeParam1")] + protected CascadingType? ChildCascadeParameter1 { get; set; } + + [CascadingParameter(Name = "CascadeParam2")] + protected CascadingType? ChildCascadeParameter2 { get; set; } +} +``` + +## Pass data across a component hierarchy + +Cascading parameters also enable components to pass data across a component hierarchy. Consider the following UI tab set example, where a tab set component maintains a series of individual tabs. + +> [!NOTE] +> For the examples in this section, the app's namespace is `BlazorSample`. When experimenting with the code in your own sample app, change the namespace to your sample app's namespace. + +Create an `ITab` interface that tabs implement in a folder named `UIInterfaces`. + +`UIInterfaces/ITab.cs`: + +```csharp +using Microsoft.AspNetCore.Components; + +namespace BlazorSample.UIInterfaces +{ + public interface ITab + { + RenderFragment ChildContent { get; } + } +} +``` + +> [!NOTE] +> For more information on , see . + +The following `TabSet` component maintains a set of tabs. The tab set's `Tab` components, which are created later in this section, supply the list items (`
  • ...
  • `) for the list (`
      ...
    `). + +Child `Tab` components aren't explicitly passed as parameters to the `TabSet`. Instead, the child `Tab` components are part of the child content of the `TabSet`. However, the `TabSet` still needs a reference each `Tab` component so that it can render the headers and the active tab. To enable this coordination without requiring additional code, the `TabSet` component *can provide itself as a cascading value* that is then picked up by the descendent `Tab` components. + +`Shared/TabSet.razor`: + +```razor +@using BlazorSample.UIInterfaces + + + + + + + + + + + +@code { + [Parameter] + public RenderFragment? ChildContent { get; set; } + + public ITab? ActiveTab { get; private set; } + + public void AddTab(ITab tab) + { + if (ActiveTab is null) + { + SetActiveTab(tab); + } + } + + public void SetActiveTab(ITab tab) + { + if (ActiveTab != tab) + { + ActiveTab = tab; + StateHasChanged(); + } + } +} +``` + +Descendent `Tab` components capture the containing `TabSet` as a cascading parameter. The `Tab` components add themselves to the `TabSet` and coordinate to set the active tab. + +`Shared/Tab.razor`: + +```razor +@using BlazorSample.UIInterfaces +@implements ITab + +
  • + + @Title + +
  • + +@code { + [CascadingParameter] + public TabSet? ContainerTabSet { get; set; } + + [Parameter] + public string? Title { get; set; } + + [Parameter] + public RenderFragment? ChildContent { get; set; } + + private string? TitleCssClass => + ContainerTabSet?.ActiveTab == this ? "active" : null; + + protected override void OnInitialized() + { + ContainerTabSet?.AddTab(this); + } + + private void ActivateTab() + { + ContainerTabSet?.SetActiveTab(this); + } +} +``` + +The following `ExampleTabSet` component uses the `TabSet` component, which contains three `Tab` components. + +`Pages/ExampleTabSet.razor`: + +```razor +@page "/example-tab-set" + + + +

    Greetings from the first tab!

    + + +
    + + +

    Hello from the second tab!

    +
    + + @if (showThirdTab) + { + +

    Welcome to the disappearing third tab!

    +

    Toggle this tab from the first tab.

    +
    + } +
    + +@code { + private bool showThirdTab; +} +``` + +:::moniker-end diff --git a/aspnetcore/blazor/components/class-libraries.md b/aspnetcore/blazor/components/class-libraries.md index 7c5418e9ff88..30c516027b3e 100644 --- a/aspnetcore/blazor/components/class-libraries.md +++ b/aspnetcore/blazor/components/class-libraries.md @@ -243,9 +243,7 @@ Add a page to the app that uses the `ExtraStyles` component from the RCL. ``` -Link to the library's stylesheet in the app's `` markup. - -`wwwroot/index.html` file (Blazor WebAssembly) or `Pages/_Layout.cshtml` file (Blazor Server): +Link to the library's stylesheet in the app's `` markup ([location of `` content](xref:blazor/project-structure#location-of-head-content)). ```html diff --git a/aspnetcore/blazor/components/control-head-content.md b/aspnetcore/blazor/components/control-head-content.md index 088480df2401..a18d8661c616 100644 --- a/aspnetcore/blazor/components/control-head-content.md +++ b/aspnetcore/blazor/components/control-head-content.md @@ -12,6 +12,8 @@ uid: blazor/components/control-head-content Razor components can modify the HTML `` element content of a page, including setting the page's title (`` element) and modifying metadata (`<meta>` elements). +:::moniker range="< aspnetcore-7.0" + ## Control `<head>` content in a Razor component Specify the page's title with the <xref:Microsoft.AspNetCore.Components.Web.PageTitle> component, which enables rendering an HTML `<title>` element to a [`HeadOutlet` component](#headoutlet-component). @@ -86,3 +88,65 @@ Mozilla MDN Web Docs documentation: * [`<head>`: The Document Metadata (Header) element](https://developer.mozilla.org/docs/Web/HTML/Element/head) * [`<title>`: The Document Title element](https://developer.mozilla.org/docs/Web/HTML/Element/title) * [`<meta>`: The metadata element](https://developer.mozilla.org/docs/Web/HTML/Element/meta) + +:::moniker-end + +:::moniker range=">= aspnetcore-7.0" + +## Control `<head>` content in a Razor component + +Specify the page's title with the <xref:Microsoft.AspNetCore.Components.Web.PageTitle> component, which enables rendering an HTML `<title>` element to a [`HeadOutlet` component](#headoutlet-component). + +Specify `<head>` element content with the <xref:Microsoft.AspNetCore.Components.Web.HeadContent> component, which provides content to a [`HeadOutlet` component](#headoutlet-component). + +The following example sets the page's title and description using Razor. + +`Pages/ControlHeadContent.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/control-head-content/ControlHeadContent.razor" highlight="13,15-17"::: + +## `HeadOutlet` component + +The <xref:Microsoft.AspNetCore.Components.Web.HeadOutlet> component renders content provided by <xref:Microsoft.AspNetCore.Components.Web.PageTitle> and <xref:Microsoft.AspNetCore.Components.Web.HeadContent> components. + +In an app created from the Blazor WebAssembly project template, the <xref:Microsoft.AspNetCore.Components.Web.HeadOutlet> component is added to the <xref:Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHostBuilder.RootComponents> collection of the <xref:Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHostBuilder> in `Program.cs`: + +```csharp +builder.RootComponents.Add<HeadOutlet>("head::after"); +``` + +When the [`::after` pseudo-selector](https://developer.mozilla.org/docs/Web/CSS/::after) is specified, the contents of the root component are appended to the existing head contents instead of replacing the content. This allows the app to retain static head content in `wwwroot/index.html` without having to repeat the content in the app's Razor components. + +In Blazor Server apps created from the Blazor Server project template, a [Component Tag Helper](xref:mvc/views/tag-helpers/builtin-th/component-tag-helper) renders `<head>` content for the <xref:Microsoft.AspNetCore.Components.Web.HeadOutlet> component in `Pages/_Host.cshtml`: + +```cshtml +<head> + ... + <component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" /> +</head> +``` + +## Not found page title + +In Blazor apps created from Blazor project templates, the `NotFound` component template in the `App` component (`App.razor`) sets the page title to `Not found`. + +`App.razor`: + +```razor +<PageTitle>Not found</PageTitle> +``` + +## Additional resources + +* [Control headers in C# code at startup](xref:blazor/fundamentals/startup#control-headers-in-c-code) +* [Blazor samples GitHub repository (`dotnet/blazor-samples`)](https://github.com/dotnet/blazor-samples) + +Mozilla MDN Web Docs documentation: + +* [What's in the head? Metadata in HTML](https://developer.mozilla.org/docs/Learn/HTML/Introduction_to_HTML/The_head_metadata_in_HTML) +* [`<head>`: The Document Metadata (Header) element](https://developer.mozilla.org/docs/Web/HTML/Element/head) +* [`<title>`: The Document Title element](https://developer.mozilla.org/docs/Web/HTML/Element/title) +* [`<meta>`: The metadata element](https://developer.mozilla.org/docs/Web/HTML/Element/meta) + + +:::moniker-end diff --git a/aspnetcore/blazor/components/css-isolation.md b/aspnetcore/blazor/components/css-isolation.md index 341e8139b455..2d732b4b96a3 100644 --- a/aspnetcore/blazor/components/css-isolation.md +++ b/aspnetcore/blazor/components/css-isolation.md @@ -51,7 +51,7 @@ h1 { ## CSS isolation bundling -CSS isolation occurs at build time. Blazor rewrites CSS selectors to match markup rendered by the component. The rewritten CSS styles are bundled and produced as a static asset. The stylesheet is referenced inside the `<head>` tag of `wwwroot/index.html` (Blazor WebAssembly) or `Pages/_Layout.cshtml` (Blazor Server). The following `<link>` element is added by default to an app created from the Blazor project templates, where the placeholder `{ASSEMBLY NAME}` is the project's assembly name: +CSS isolation occurs at build time. Blazor rewrites CSS selectors to match markup rendered by the component. The rewritten CSS styles are bundled and produced as a static asset. The stylesheet is referenced inside the `<head>` tag ([location of `<head>` content](xref:blazor/project-structure#location-of-head-content)). The following `<link>` element is added by default to an app created from the Blazor project templates, where the placeholder `{ASSEMBLY NAME}` is the project's assembly name: ```html <link href="{ASSEMBLY NAME}.styles.css" rel="stylesheet"> diff --git a/aspnetcore/blazor/components/data-binding.md b/aspnetcore/blazor/components/data-binding.md index 3b8737cf62e0..7c56e8ecde68 100644 --- a/aspnetcore/blazor/components/data-binding.md +++ b/aspnetcore/blazor/components/data-binding.md @@ -12,7 +12,7 @@ uid: blazor/components/data-binding This article explains data binding features for Razor components and Document Object Model (DOM) elements in Blazor apps. -:::moniker range=">= aspnetcore-6.0" +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" Razor components provide data binding features with the [`@bind`](xref:mvc/views/razor#bind) Razor directive attribute with a field, property, or Razor expression value. @@ -663,3 +663,261 @@ For an alternative approach suited to sharing data in memory and across componen * [Blazor samples GitHub repository (`dotnet/blazor-samples`)](https://github.com/dotnet/blazor-samples) :::moniker-end + +:::moniker range=">= aspnetcore-7.0" + +Razor components provide data binding features with the [`@bind`](xref:mvc/views/razor#bind) Razor directive attribute with a field, property, or Razor expression value. + +The following example binds: + +* An `<input>` element value to the C# `inputValue` field. +* A second `<input>` element value to the C# `InputValue` property. + +When an `<input>` element loses focus, its bound field or property is updated. + +`Pages/Bind.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/data-binding/Bind.razor" highlight="4,8"::: + +The text box is updated in the UI only when the component is rendered, not in response to changing the field's or property's value. Since components render themselves after event handler code executes, field and property updates are usually reflected in the UI immediately after an event handler is triggered. + +As a demonstration of how data binding composes in HTML, the following example binds the `InputValue` property to the second `<input>` element's `value` and [`onchange`](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/onchange) attributes. *The second `<input>` element in the following example is a concept demonstration and isn't meant to suggest how you should bind data in Razor components.* + +`Pages/BindTheory.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/data-binding/BindTheory.razor" highlight="12-14"::: + +When the `BindTheory` component is rendered, the `value` of the HTML demonstration `<input>` element comes from the `InputValue` property. When the user enters a value in the text box and changes element focus, the `onchange` event is fired and the `InputValue` property is set to the changed value. In reality, code execution is more complex because [`@bind`](xref:mvc/views/razor#bind) handles cases where type conversions are performed. In general, [`@bind`](xref:mvc/views/razor#bind) associates the current value of an expression with a `value` attribute and handles changes using the registered handler. + +Bind a property or field on other [Document Object Model (DOM)](https://developer.mozilla.org/docs/Web/API/Document_Object_Model/Introduction) events by including an `@bind:event="{EVENT}"` attribute with a DOM event for the `{EVENT}` placeholder. The following example binds the `InputValue` property to the `<input>` element's value when the element's [`oninput` event](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/oninput) is triggered. Unlike the [`onchange` event](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/onchange), which fires when the element loses focus, [`oninput`](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/oninput) fires when the value of the text box changes. + +`Page/BindEvent.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/data-binding/BindEvent.razor" highlight="4"::: + +Razor attribute binding is case sensitive: + +* `@bind` and `@bind:event` are valid. +* `@Bind`/`@Bind:Event` (capital letters `B` and `E`) or `@BIND`/`@BIND:EVENT` (all capital letters) **are invalid**. + +## Multiple option selection with `<input>` elements + +Binding supports [`multiple`](https://developer.mozilla.org/docs/Web/HTML/Attributes/multiple) option selection with `<input>` elements. The [`@onchange`](xref:mvc/views/razor#onevent) event provides an array of the selected elements via [event arguments (`ChangeEventArgs`)](xref:blazor/components/event-handling#event-arguments). The value must be bound to an array type. + +`Pages/BindMultipleInput.razor`: + +```razor +@page "/bind-multiple-input" + +<h1>Bind Multiple <code>input</code>Example</h1> + +<p> + <label> + Select one or more cars: + <select @onchange="SelectedCarsChanged" multiple> + <option value="audi">Audi</option> + <option value="jeep">Jeep</option> + <option value="opel">Opel</option> + <option value="saab">Saab</option> + <option value="volvo">Volvo</option> + </select> + </label> +</p> + +<p> + Selected Cars: @string.Join(", ", SelectedCars) +</p> + +<p> + <label> + Select one or more cities: + <select @bind="SelectedCities" multiple> + <option value="bal">Baltimore</option> + <option value="la">Los Angeles</option> + <option value="pdx">Portland</option> + <option value="sf">San Francisco</option> + <option value="sea">Seattle</option> + </select> + </label> +</p> + +<span> + Selected Cities: @string.Join(", ", SelectedCities) +</span> + +@code { + public string[] SelectedCars { get; set; } = new string[] { }; + public string[] SelectedCities { get; set; } = new[] { "bal", "sea" }; + + void SelectedCarsChanged(ChangeEventArgs e) + { + if (e.Value is not null) + { + SelectedCars = (string[])e.Value; + } + } +} +``` + +For information on how empty strings and `null` values are handled in data binding, see the [Binding `<select>` element options to C# object `null` values](#binding-select-element-options-to-c-object-null-values) section. + +## Binding `<select>` element options to C# object `null` values + +There's no sensible way to represent a `<select>` element option value as a C# object `null` value, because: + +* HTML attributes can't have `null` values. The closest equivalent to `null` in HTML is absence of the HTML `value` attribute from the `<option>` element. +* When selecting an `<option>` with no `value` attribute, the browser treats the value as the *text content* of that `<option>`'s element. + +The Blazor framework doesn't attempt to suppress the default behavior because it would involve: + +* Creating a chain of special-case workarounds in the framework. +* Breaking changes to current framework behavior. + +The most plausible `null` equivalent in HTML is an *empty string* `value`. The Blazor framework handles `null` to empty string conversions for two-way binding to a `<select>`'s value. + +## Unparsable values + +When a user provides an unparsable value to a databound element, the unparsable value is automatically reverted to its previous value when the bind event is triggered. + +Consider the following component, where an `<input>` element is bound to an `int` type with an initial value of `123`. + +`Pages/UnparsableValues.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/data-binding/UnparsableValues.razor" highlight="4,12"::: + +By default, binding applies to the element's `onchange` event. If the user updates the value of the text box's entry to `123.45` and changes the focus, the element's value is reverted to `123` when `onchange` fires. When the value `123.45` is rejected in favor of the original value of `123`, the user understands that their value wasn't accepted. + +For the `oninput` event (`@bind:event="oninput"`), a value reversion occurs after any keystroke that introduces an unparsable value. When targeting the `oninput` event with an `int`-bound type, a user is prevented from typing a dot (`.`) character. A dot (`.`) character is immediately removed, so the user receives immediate feedback that only whole numbers are permitted. There are scenarios where reverting the value on the `oninput` event isn't ideal, such as when the user should be allowed to clear an unparsable `<input>` value. Alternatives include: + +* Don't use the `oninput` event. Use the default `onchange` event, where an invalid value isn't reverted until the element loses focus. +* Bind to a nullable type, such as `int?` or `string` and provide [custom `get` and `set` accessor logic](#custom-binding-formats) to handle invalid entries. +* Use a [form validation component](xref:blazor/forms-validation), such as <xref:Microsoft.AspNetCore.Components.Forms.InputNumber%601> or <xref:Microsoft.AspNetCore.Components.Forms.InputDate%601>. Form validation components provide built-in support to manage invalid inputs. Form validation components: + * Permit the user to provide invalid input and receive validation errors on the associated <xref:Microsoft.AspNetCore.Components.Forms.EditContext>. + * Display validation errors in the UI without interfering with the user entering additional webform data. + +## Format strings + +Data binding works with a single <xref:System.DateTime> format string using `@bind:format="{FORMAT STRING}"`, where the `{FORMAT STRING}` placeholder is the format string. Other format expressions, such as currency or number formats, aren't available at this time but might be added in a future release. + +`Pages/DateBinding.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/data-binding/DateBinding.razor" highlight="6"::: + +In the preceding code, the `<input>` element's field type (`type` attribute) defaults to `text`. + +Nullable <xref:System.DateTime?displayProperty=fullName> and <xref:System.DateTimeOffset?displayProperty=fullName> are supported: + +```csharp +private DateTime? date; +private DateTimeOffset? dateOffset; +``` + +Specifying a format for the `date` field type isn't recommended because Blazor has built-in support to format dates. In spite of the recommendation, only use the `yyyy-MM-dd` date format for binding to function correctly if a format is supplied with the `date` field type: + +```razor +<input type="date" @bind="startDate" @bind:format="yyyy-MM-dd"> +``` + +## Custom binding formats + +[C# `get` and `set` accessors](/dotnet/csharp/programming-guide/classes-and-structs/using-properties) can be used to create custom binding format behavior, as the following `DecimalBinding` component demonstrates. The component binds a positive or negative decimal with up to three decimal places to an `<input>` element by way of a `string` property (`DecimalValue`). + +`Pages/DecimalBinding.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/data-binding/DecimalBinding.razor" highlight="7,21-31"::: + +## Binding with component parameters + +A common scenario is binding a property of a child component to a property in its parent component. This scenario is called a *chained bind* because multiple levels of binding occur simultaneously. + +[Component parameters](xref:blazor/components/index#component-parameters) permit binding properties of a parent component with `@bind-{PROPERTY}` syntax, where the `{PROPERTY}` placeholder is the property to bind. + +You can't implement chained binds with [`@bind`](xref:mvc/views/razor#bind) syntax in the child component. An event handler and value must be specified separately to support updating the property in the parent from the child component. + +The parent component still leverages the [`@bind`](xref:mvc/views/razor#bind) syntax to set up the databinding with the child component. + +The following `ChildBind` component has a `Year` component parameter and an <xref:Microsoft.AspNetCore.Components.EventCallback%601>. By convention, the <xref:Microsoft.AspNetCore.Components.EventCallback%601> for the parameter must be named as the component parameter name with a "`Changed`" suffix. The naming syntax is `{PARAMETER NAME}Changed`, where the `{PARAMETER NAME}` placeholder is the parameter name. In the following example, the <xref:Microsoft.AspNetCore.Components.EventCallback%601> is named `YearChanged`. + +<xref:Microsoft.AspNetCore.Components.EventCallback.InvokeAsync%2A?displayProperty=nameWithType> invokes the delegate associated with the binding with the provided argument and dispatches an event notification for the changed property. + +`Shared/ChildBind.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/data-binding/ChildBind.razor" highlight="14-15,17-18,22"::: + +For more information on events and <xref:Microsoft.AspNetCore.Components.EventCallback%601>, see the *EventCallback* section of the <xref:blazor/components/event-handling#eventcallback> article. + +In the following `Parent` component, the `year` field is bound to the `Year` parameter of the child component. The `Year` parameter is bindable because it has a companion `YearChanged` event that matches the type of the `Year` parameter. + +`Pages/Parent.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/data-binding/Parent1.razor" highlight="9"::: + +By convention, a property can be bound to a corresponding event handler by including an `@bind-{PROPERTY}:event` attribute assigned to the handler, where the `{PROPERTY}` placeholder is the property. `<ChildBind @bind-Year="year" />` is equivalent to writing: + +```razor +<ChildBind @bind-Year="year" @bind-Year:event="YearChanged" /> +``` + +In a more sophisticated and real-world example, the following `PasswordEntry` component: + +* Sets an `<input>` element's value to a `password` field. +* Exposes changes of a `Password` property to a parent component with an [`EventCallback`](xref:blazor/components/event-handling#eventcallback) that passes in the current value of the child's `password` field as its argument. +* Uses the `onclick` event to trigger the `ToggleShowPassword` method. For more information, see <xref:blazor/components/event-handling>. + +`Shared/PasswordEntry.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/data-binding/PasswordEntry.razor" highlight="7-10,13,23-24,26-27,36-39"::: + +The `PasswordEntry` component is used in another component, such as the following `PasswordBinding` component example. + +`Pages/PasswordBinding.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/data-binding/PasswordBinding.razor" highlight="5"::: + +When the `PasswordBinding` component is initially rendered, the `password` value of `Not set` is displayed in the UI. After initial rendering, the value of `password` reflects changes made to the `Password` component parameter value in the `PasswordEntry` component. + +> [!NOTE] +> The preceding example binds the password one-way from the child `PasswordEntry` component to the parent `PasswordBinding` component. Two-way binding isn't a requirement in this scenario if the goal is for the app to have a shared password entry component for reuse around the app that merely passes the password to the parent. For an approach that permits two-way binding without [writing directly to the child component's parameter](xref:blazor/components/index#overwritten-parameters), see the `NestedChild` component example in the [Bind across more than two components](#bind-across-more-than-two-components) section of this article. + +Perform checks or trap errors in the handler. The following revised `PasswordEntry` component provides immediate feedback to the user if a space is used in the password's value. + +`Shared/PasswordEntry.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/data-binding/PasswordEntry2.razor" highlight="35-46"::: + +## Bind across more than two components + +You can bind parameters through any number of nested components, but you must respect the one-way flow of data: + +* Change notifications *flow up the hierarchy*. +* New parameter values *flow down the hierarchy*. + +A common and recommended approach is to only store the underlying data in the parent component to avoid any confusion about what state must be updated, as shown in the following example. + +`Pages/Parent.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/data-binding/Parent2.razor"::: + +`Shared/NestedChild.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/data-binding/NestedChild.razor"::: + +> [!WARNING] +> Generally, avoid creating components that write directly to their own component parameters. The preceding `NestedChild` component makes use of a `BoundValue` property instead of writing directly to its `ChildMessage` parameter. For more information, see <xref:blazor/components/index#overwritten-parameters>. + +`Shared/NestedGrandchild.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/data-binding/NestedGrandchild.razor"::: + +For an alternative approach suited to sharing data in memory and across components that aren't necessarily nested, see <xref:blazor/state-management>. + +## Additional resources + +* [Parameter change detection and additional guidance on Razor component rendering](xref:blazor/components/rendering) +* <xref:blazor/forms-validation> +* [Binding to radio buttons in a form](xref:blazor/forms-validation#radio-buttons) +* [Binding `InputSelect` options to C# object `null` values](xref:blazor/forms-validation#binding-inputselect-options-to-c-object-null-values) +* [ASP.NET Core Blazor event handling: `EventCallback` section](xref:blazor/components/event-handling#eventcallback) +* [Blazor samples GitHub repository (`dotnet/blazor-samples`)](https://github.com/dotnet/blazor-samples) + +:::moniker-end diff --git a/aspnetcore/blazor/components/dynamiccomponent.md b/aspnetcore/blazor/components/dynamiccomponent.md index f4e73161a8dc..7827db230485 100644 --- a/aspnetcore/blazor/components/dynamiccomponent.md +++ b/aspnetcore/blazor/components/dynamiccomponent.md @@ -14,6 +14,8 @@ By [Dave Brock](https://twitter.com/daveabrock) Use the built-in <xref:Microsoft.AspNetCore.Components.DynamicComponent> component to render components by type. +:::moniker range="< aspnetcore-7.0" + A <xref:Microsoft.AspNetCore.Components.DynamicComponent> is useful for rendering components without iterating through possible types or using conditional logic. For example, <xref:Microsoft.AspNetCore.Components.DynamicComponent> can render a component based on a user selection from a dropdown list. In the following example: @@ -243,3 +245,239 @@ Rocket Lab is a registered trademark of [Rocket Lab USA Inc.](https://www.rocket ## Additional resources * <xref:blazor/components/event-handling#eventcallback> + +:::moniker-end + +:::moniker range=">= aspnetcore-7.0" + +A <xref:Microsoft.AspNetCore.Components.DynamicComponent> is useful for rendering components without iterating through possible types or using conditional logic. For example, <xref:Microsoft.AspNetCore.Components.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. + +```razor +<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](#pass-parameters) section later in this article. + +Use the <xref:Microsoft.AspNetCore.Components.DynamicComponent.Instance> property to access the dynamically-created component instance: + +```razor +<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`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/dynamiccomponent/RocketLab.razor"::: + +`Shared/SpaceX.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/dynamiccomponent/SpaceX.razor"::: + +`Shared/UnitedLaunchAlliance.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/dynamiccomponent/UnitedLaunchAlliance.razor"::: + +`Shared/VirginGalactic.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/dynamiccomponent/VirginGalactic.razor"::: + +`Pages/DynamicComponentExample1.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/dynamiccomponent/DynamicComponentExample1.razor"::: + +In the preceding example: + +* Component names are used as the option values using the [`nameof` operator](/dotnet/csharp/language-reference/operators/nameof), which returns component names as constant strings. +* The namespace of the app is `BlazorSample`. + +## Pass parameters + +If dynamically-rendered components have [component parameters](xref:blazor/components/index#component-parameters), pass them into the <xref:Microsoft.AspNetCore.Components.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`: + +```csharp +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`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/dynamiccomponent/RocketLabWithWindowSeat.razor" highlight="13-14"::: + +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`. +* 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](#example) section earlier in this article: + * `SpaceX` (`Shared/SpaceX.razor`) + * `UnitedLaunchAlliance` (`Shared/UnitedLaunchAlliance.razor`) + * `VirginGalactic` (`Shared/VirginGalactic.razor`) + +`Pages/DynamicComponentExample2.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/dynamiccomponent/DynamicComponentExample2.razor"::: + +## Event callbacks (`EventCallback`) + +Event callbacks (<xref:Microsoft.AspNetCore.Components.EventCallback>) can be passed to a <xref:Microsoft.AspNetCore.Components.DynamicComponent> in its parameter dictionary. + +> [!NOTE] +> The example in this section is an extension of the full example shown in the *Pass parameters* section of this article. + +Implement an event callback parameter (<xref:Microsoft.AspNetCore.Components.EventCallback>) within each dynamically-rendered component: + +```razor +<button @onclick="OnClickCallback"> + Trigger a Parent component method +</button> + +@code { + [Parameter] + public EventCallback<MouseEventArgs> OnClickCallback { get; set; } +} +``` + +In the parent component's `@code` block, implement the callback method. The following example, the `ShowDTMessage` method assigns a string with the current time to `message`, and the value of `message` is rendered: + +```razor +... + +<p>@message</p> + +@code { + ... + private string? message; + ... + private void ShowDTMessage(MouseEventArgs e) => + message = $"The current DT is: {DateTime.Now}."; +} +``` + +The parent component passes the parameter with: + +* A `string` key equal to the callback method name, `OnClickCallback` in the following example. +* An `object` value created by <xref:Microsoft.AspNetCore.Components.EventCallbackFactory.Create%2A?displayProperty=nameWithType> for the parent callback method, `ShowDTMessage` in the following example. + +```csharp +private Dictionary<string, ComponentMetadata> components = + new() + { + { + "RocketLabWithWindowSeat", + new ComponentMetadata + { + Name = "Rocket Lab with Window Seat", + Parameters = new() + { + { "WindowSeat", false }, + { "OnClickCallback", + EventCallback.Factory.Create<MouseEventArgs>( + this, ShowDTMessage) } + } + } + }, + { + "VirginGalactic", + new ComponentMetadata + { + Name = "Virgin Galactic", + Parameters = new() + { + { "WindowSeat", true }, + { "OnClickCallback", + EventCallback.Factory.Create<MouseEventArgs>( + this, ShowDTMessage) } + } + } + }, + { + "UnitedLaunchAlliance", + new ComponentMetadata + { + Name = "ULA", + Parameters = new() + { + { "WindowSeat", true }, + { "OnClickCallback", + EventCallback.Factory.Create<MouseEventArgs>( + this, ShowDTMessage) } + } + } + }, + { + "SpaceX", + new ComponentMetadata + { + Name = "SpaceX", + Parameters = new() + { + { "WindowSeat", true }, + { "OnClickCallback", + EventCallback.Factory.Create<MouseEventArgs>( + this, ShowDTMessage) } + } + } + } + }; +``` + +## Avoid catch-all parameters + +Avoid the use of [catch-all parameters](xref:blazor/fundamentals/routing#catch-all-route-parameters). If catch-all parameters are used, every explicit parameter on <xref:Microsoft.AspNetCore.Components.DynamicComponent> effectively is a reserved word that you can't pass to a dynamic child. Any new parameters passed to <xref:Microsoft.AspNetCore.Components.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.](https://www.rocketlabusa.com/) SpaceX is a registered trademark of [Space Exploration Technologies Corp.](https://www.spacex.com/) United Launch Alliance and ULA are registered trademarks of [United Launch Alliance, LLC](https://www.ulalaunch.com/). Virgin Galactic is a registered trademark of [Galactic Enterprises, LLC](https://www.virgingalactic.com/). + +## Additional resources + +* <xref:blazor/components/event-handling#eventcallback> + +:::moniker-end diff --git a/aspnetcore/blazor/components/event-handling.md b/aspnetcore/blazor/components/event-handling.md index a7b568c08d13..7abef100e9e1 100644 --- a/aspnetcore/blazor/components/event-handling.md +++ b/aspnetcore/blazor/components/event-handling.md @@ -12,7 +12,7 @@ uid: blazor/components/event-handling This article explains Blazor's event handling features, including event argument types, event callbacks, and managing default browser events. -:::moniker range=">= aspnetcore-6.0" +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" Specify delegate event handlers in Razor component markup with [`@on{DOM EVENT}="{DELEGATE}"`](xref:mvc/views/razor#onevent) Razor syntax: @@ -684,3 +684,334 @@ In the following example, selecting the checkbox prevents click events from the :::code language="razor" source="~/../blazor-samples/3.1/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample7.razor" highlight="4,15-16"::: :::moniker-end + +:::moniker range=">= aspnetcore-7.0" + +Specify delegate event handlers in Razor component markup with [`@on{DOM EVENT}="{DELEGATE}"`](xref:mvc/views/razor#onevent) Razor syntax: + +* The `{DOM EVENT}` placeholder is a [Document Object Model (DOM) event](https://developer.mozilla.org/docs/Web/Events) (for example, `click`). +* The `{DELEGATE}` placeholder is the C# delegate event handler. + +For event handling: + +* Asynchronous delegate event handlers that return a <xref:System.Threading.Tasks.Task> are supported. +* Delegate event handlers automatically trigger a UI render, so there's no need to manually call [`StateHasChanged`](xref:blazor/components/lifecycle#state-changes-statehaschanged). +* Exceptions are logged. + +The following code: + +* Calls the `UpdateHeading` method when the button is selected in the UI. +* Calls the `CheckChanged` method when the checkbox is changed in the UI. + +`Pages/EventHandlerExample1.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample1.razor" highlight="10,17,27-30,32-35"::: + +In the following example, `UpdateHeading`: + +* Is called asynchronously when the button is selected. +* Waits two seconds before updating the heading. + +`Pages/EventHandlerExample2.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample2.razor" highlight="10,19-24"::: + +## Event arguments + +### Built-in event arguments + +For events that support an event argument type, specifying an event parameter in the event method definition is only necessary if the event type is used in the method. In the following example, <xref:Microsoft.AspNetCore.Components.Web.MouseEventArgs> is used in the `ReportPointerLocation` method to set message text that reports the mouse coordinates when the user selects a button in the UI. + +`Pages/EventHandlerExample3.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample3.razor" highlight="17-20"::: + +Supported <xref:System.EventArgs> are shown in the following table. + +| Event | Class | [Document Object Model (DOM)](https://developer.mozilla.org/docs/Web/API/Document_Object_Model/Introduction) notes | +| ---------------- | ------ | --- | +| Clipboard | <xref:Microsoft.AspNetCore.Components.Web.ClipboardEventArgs> | | +| Drag | <xref:Microsoft.AspNetCore.Components.Web.DragEventArgs> | <xref:Microsoft.AspNetCore.Components.Web.DataTransfer> and <xref:Microsoft.AspNetCore.Components.Web.DataTransferItem> hold dragged item data.<br><br>Implement drag and drop in Blazor apps using [JS interop](xref:blazor/js-interop/call-javascript-from-dotnet) with [HTML Drag and Drop API](https://developer.mozilla.org/docs/Web/API/HTML_Drag_and_Drop_API). | +| Error | <xref:Microsoft.AspNetCore.Components.Web.ErrorEventArgs> | | +| Event | <xref:System.EventArgs> | <xref:Microsoft.AspNetCore.Components.Web.EventHandlers> holds attributes to configure the mappings between event names and event argument types. | +| Focus | <xref:Microsoft.AspNetCore.Components.Web.FocusEventArgs> | Doesn't include support for `relatedTarget`. | +| Input | <xref:Microsoft.AspNetCore.Components.ChangeEventArgs> | | +| Keyboard | <xref:Microsoft.AspNetCore.Components.Web.KeyboardEventArgs> | | +| Mouse | <xref:Microsoft.AspNetCore.Components.Web.MouseEventArgs> | | +| Mouse pointer | <xref:Microsoft.AspNetCore.Components.Web.PointerEventArgs> | | +| Mouse wheel | <xref:Microsoft.AspNetCore.Components.Web.WheelEventArgs> | | +| Progress | <xref:Microsoft.AspNetCore.Components.Web.ProgressEventArgs> | | +| Touch | <xref:Microsoft.AspNetCore.Components.Web.TouchEventArgs> | <xref:Microsoft.AspNetCore.Components.Web.TouchPoint> represents a single contact point on a touch-sensitive device. | + +For more information, see the following resources: + +* [`EventArgs` classes in the ASP.NET Core reference source (dotnet/aspnetcore `main` branch)](https://github.com/dotnet/aspnetcore/tree/main/src/Components/Web/src/Web) + + [!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)] + +* <xref:Microsoft.AspNetCore.Components.Web.EventHandlers> holds attributes to configure the mappings between event names and event argument types. + +* [MDN web docs: GlobalEventHandlers](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers): Includes information on which HTML elements support each DOM event. + +### Custom event arguments + +Blazor supports custom event arguments, which enable you to pass arbitrary data to .NET event handlers with custom events. + +#### General configuration + +Custom events with custom event arguments are generally enabled with the following steps. + +1. In JavaScript, define a function for building the custom event argument object from the source event: + + ```javascript + function eventArgsCreator(event) { + return { + customProperty1: 'any value for property 1', + customProperty2: event.srcElement.value + }; + } + ``` + +1. Register the custom event with the preceding handler in `wwwroot/index.html` (Blazor WebAssembly) or `Pages/_Host.cshtml` (Blazor Server) immediately after the Blazor `<script>`: + + ```html + <script> + Blazor.registerCustomEventType('customevent', { + createEventArgs: eventArgsCreator; + }); + </script> + ``` + + > [!NOTE] + > The call to `registerCustomEventType` is performed in a script only once per event. + +1. Define a class for the event arguments: + + ```csharp + public class CustomEventArgs : EventArgs + { + public string? CustomProperty1 {get; set;} + public string? CustomProperty2 {get; set;} + } + ``` + +1. Wire up the custom event with the event arguments by adding an <xref:Microsoft.AspNetCore.Components.EventHandlerAttribute> attribute annotation for the custom event. The class doesn't require members. Note that the class *must* be called `EventHandlers` in order to be found by the Razor compiler, but you should put it in a namespace specific to your app: + + ```csharp + [EventHandler("oncustomevent", typeof(CustomEventArgs), enableStopPropagation: true, enablePreventDefault: true)] + static class EventHandlers + { + } + ``` + +1. Register the event handler on one or more HTML elements. Access the data that was passed in from JavaScript in the delegate handler method: + + ```razor + <button @oncustomevent="HandleCustomEvent">Handle</button> + + @code + { + void HandleCustomEvent(CustomEventArgs eventArgs) + { + // eventArgs.CustomProperty1 + // eventArgs.CustomProperty2 + } + } + ``` + +If the `@oncustomevent` attribute isn't recognized by [IntelliSense](/visualstudio/ide/using-intellisense), make sure that the component or the `_Imports.razor` file contains an `@using` statement for the namespace containing the `EventHandler` class. + +Whenever the custom event is fired on the DOM, the event handler is called with the data passed from the JavaScript. + +If you're attempting to fire a custom event, [`bubbles`](https://developer.mozilla.org/docs/Web/API/Event/bubbles) must be enabled by setting its value to `true`. Otherwise, the event doesn't reach the Blazor handler for processing into the C# custom <xref:Microsoft.AspNetCore.Components.EventHandlerAttribute> method. For more information, see [MDN Web Docs: Event bubbling](https://developer.mozilla.org/docs/Web/Guide/Events/Creating_and_triggering_events#event_bubbling). + +#### Custom clipboard paste event example + +The following example receives a custom clipboard paste event that includes the time of the paste and the user's pasted text. + +Declare a custom name (`oncustompaste`) for the event and a .NET class (`CustomPasteEventArgs`) to hold the event arguments for this event: + +`CustomEvents.cs`: + +```csharp +[EventHandler("oncustompaste", typeof(CustomPasteEventArgs), + enableStopPropagation: true, enablePreventDefault: true)] +public static class EventHandlers +{ +} + +public class CustomPasteEventArgs : EventArgs +{ + public DateTime EventTimestamp { get; set; } + public string? PastedData { get; set; } +} +``` + +Add JavaScript code to supply data for the <xref:System.EventArgs> subclass. In the `wwwroot/index.html` or `Pages/_Host.cshtml` file, add the following `<script>` tag and content immediately after the Blazor script. The following example only handles pasting text, but you could use arbitrary JavaScript APIs to deal with users pasting other types of data, such as images. + +`wwwroot/index.html` (Blazor WebAssembly) or `Pages/_Host.cshtml` (Blazor Server) immediately after the Blazor script: + +```html +<script> + Blazor.registerCustomEventType('custompaste', { + browserEventName: 'paste', + createEventArgs: event => { + return { + eventTimestamp: new Date(), + pastedData: event.clipboardData.getData('text') + }; + } + }); +</script> +``` + +The preceding code tells the browser that when a native [`paste`](https://developer.mozilla.org/docs/Web/API/Element/paste_event) event occurs: + +* Raise a `custompaste` event. +* Supply the event arguments data using the custom logic stated: + * For the `eventTimestamp`, create a new date. + * For the `pastedData`, get the clipboard data as text. For more information, see [MDN Web Docs: ClipboardEvent.clipboardData](https://developer.mozilla.org/docs/Web/API/ClipboardEvent/clipboardData). + +Event name conventions differ between .NET and JavaScript: + +* In .NET, event names are prefixed with "`on`". +* In JavaScript, event names don't have a prefix. + +In a Razor component, attach the custom handler to an element. + +`Pages/CustomPasteArguments.razor`: + +```razor +@page "/custom-paste-arguments" + +<label> + Try pasting into the following text box: + <input @oncustompaste="HandleCustomPaste" /> +</label> + +<p> + @message +</p> + +@code { + private string? message; + + private void HandleCustomPaste(CustomPasteEventArgs eventArgs) + { + message = $"At {eventArgs.EventTimestamp.ToShortTimeString()}, " + + $"you pasted: {eventArgs.PastedData}"; + } +} +``` + +## Lambda expressions + +[Lambda expressions](/dotnet/csharp/programming-guide/statements-expressions-operators/lambda-expressions) are supported as the delegate event handler. + +`Pages/EventHandlerExample4.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample4.razor" highlight="6"::: + +It's often convenient to close over additional values using C# method parameters, such as when iterating over a set of elements. The following example creates three buttons, each of which calls `UpdateHeading` and passes the following data: + +* An event argument (<xref:Microsoft.AspNetCore.Components.Web.MouseEventArgs>) in `e`. +* The button number in `buttonNumber`. + +`Pages/EventHandlerExample5.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample5.razor" highlight="10,19"::: + +> [!NOTE] +> Do **not** use a loop variable directly in a lambda expression, such as `i` in the preceding `for` loop example. Otherwise, the same variable is used by all lambda expressions, which results in use of the same value in all lambdas. Always capture the variable's value in a local variable and then use it. In the preceding example: +> +> * The loop variable `i` is assigned to `buttonNumber`. +> * `buttonNumber` is used in the lambda expression. + +Creating a large number of event delegates in a loop may cause poor rendering performance. For more information, see <xref:blazor/performance#avoid-recreating-delegates-for-many-repeated-elements-or-components>. + +## EventCallback + +A common scenario with nested components executes a parent component's method when a child component event occurs. An `onclick` event occurring in the child component is a common use case. To expose events across components, use an <xref:Microsoft.AspNetCore.Components.EventCallback>. A parent component can assign a callback method to a child component's <xref:Microsoft.AspNetCore.Components.EventCallback>. + +The following `Child` component demonstrates how a button's `onclick` handler is set up to receive an <xref:Microsoft.AspNetCore.Components.EventCallback> delegate from the sample's `ParentComponent`. The <xref:Microsoft.AspNetCore.Components.EventCallback> is typed with `MouseEventArgs`, which is appropriate for an `onclick` event from a peripheral device. + +`Shared/Child.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/event-handling/Child.razor"::: + +The `Parent` component sets the child's <xref:Microsoft.AspNetCore.Components.EventCallback%601> (`OnClickCallback`) to its `ShowMessage` method. + +`Pages/Parent.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/event-handling/Parent.razor"::: + +When the button is selected in the `ChildComponent`: + +* The `Parent` component's `ShowMessage` method is called. `message` is updated and displayed in the `Parent` component. +* A call to [`StateHasChanged`](xref:blazor/components/lifecycle#state-changes-statehaschanged) isn't required in the callback's method (`ShowMessage`). <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> is called automatically to rerender the `Parent` component, just as child events trigger component rerendering in event handlers that execute within the child. For more information, see <xref:blazor/components/rendering>. + +<xref:Microsoft.AspNetCore.Components.EventCallback> and <xref:Microsoft.AspNetCore.Components.EventCallback%601> permit asynchronous delegates. <xref:Microsoft.AspNetCore.Components.EventCallback> is weakly typed and allows passing any type argument in `InvokeAsync(Object)`. <xref:Microsoft.AspNetCore.Components.EventCallback%601> is strongly typed and requires passing a `T` argument in `InvokeAsync(T)` that's assignable to `TValue`. + +```razor +<ChildComponent + OnClickCallback="@(async () => { await Task.Yield(); messageText = "Blaze It!"; })" /> +``` + +Invoke an <xref:Microsoft.AspNetCore.Components.EventCallback> or <xref:Microsoft.AspNetCore.Components.EventCallback%601> with <xref:Microsoft.AspNetCore.Components.EventCallback.InvokeAsync%2A> and await the <xref:System.Threading.Tasks.Task>: + +```csharp +await OnClickCallback.InvokeAsync(arg); +``` + +Use <xref:Microsoft.AspNetCore.Components.EventCallback> and <xref:Microsoft.AspNetCore.Components.EventCallback%601> for event handling and binding component parameters. + +Prefer the strongly typed <xref:Microsoft.AspNetCore.Components.EventCallback%601> over <xref:Microsoft.AspNetCore.Components.EventCallback>. <xref:Microsoft.AspNetCore.Components.EventCallback%601> provides enhanced error feedback to users of the component. Similar to other UI event handlers, specifying the event parameter is optional. Use <xref:Microsoft.AspNetCore.Components.EventCallback> when there's no value passed to the callback. + +## Prevent default actions + +Use the [`@on{DOM EVENT}:preventDefault`](xref:mvc/views/razor#oneventpreventdefault) directive attribute to prevent the default action for an event, where the `{DOM EVENT}` placeholder is a [Document Object Model (DOM) event](https://developer.mozilla.org/docs/Web/Events). + +When a key is selected on an input device and the element focus is on a text box, a browser normally displays the key's character in the text box. In the following example, the default behavior is prevented by specifying the `@onkeydown:preventDefault` directive attribute. When the focus is on the `<input>` element, the counter increments with the key sequence <kbd>Shift</kbd>+<kbd>+</kbd>. The `+` character isn't assigned to the `<input>` element's value. For more information on `keydown`, see [`MDN Web Docs: Document: keydown` event](https://developer.mozilla.org/docs/Web/API/Document/keydown_event). + +`Pages/EventHandlerExample6.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample6.razor" highlight="4"::: + +Specifying the `@on{DOM EVENT}:preventDefault` attribute without a value is equivalent to `@on{DOM EVENT}:preventDefault="true"`. + +An expression is also a permitted value of the attribute. In the following example, `shouldPreventDefault` is a `bool` field set to either `true` or `false`: + +```razor +<input @onkeydown:preventDefault="shouldPreventDefault" /> + +... + +@code { + private bool shouldPreventDefault = true; +} +``` + +## Stop event propagation + +Use the [`@on{DOM EVENT}:stopPropagation`](xref:mvc/views/razor#oneventstoppropagation) directive attribute to stop event propagation within the Blazor scope. `{DOM EVENT}` is a placeholder for a [Document Object Model (DOM) event](https://developer.mozilla.org/docs/Web/Events). + +The `stopPropagation` directive attribute's effect is limited to the Blazor scope and doesn't extend to the HTML DOM. Events must propagate to the HTML DOM root before Blazor can act upon them. For a mechanism to prevent HTML DOM event propagation, consider the following approach: + +* Obtain the event's path by calling [`Event.composedPath()`](https://developer.mozilla.org/docs/Web/API/Event/composedPath). +* Filter events based on the composed [event targets (`EventTarget`)](https://developer.mozilla.org/docs/Web/API/EventTarget). + +In the following example, selecting the checkbox prevents click events from the second child `<div>` from propagating to the parent `<div>`. Since propagated click events normally fire the `OnSelectParentDiv` method, selecting the second child `<div>` results in the parent `<div>` message appearing unless the checkbox is selected. + +`Pages/EventHandlerExample7.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample7.razor" highlight="4,15-16"::: + +## Focus an element + +Call <xref:Microsoft.AspNetCore.Components.ElementReferenceExtensions.FocusAsync%2A> on an [element reference](xref:blazor/js-interop/call-javascript-from-dotnet#capture-references-to-elements) to focus an element in code. In the following example, select the button to focus the `<input>` element. + +`Pages/EventHandlerExample8.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/event-handling/EventHandlerExample8.razor" highlight="16"::: + +:::moniker-end diff --git a/aspnetcore/blazor/components/index.md b/aspnetcore/blazor/components/index.md index 17bab103196a..ca890673bb63 100644 --- a/aspnetcore/blazor/components/index.md +++ b/aspnetcore/blazor/components/index.md @@ -12,7 +12,7 @@ uid: blazor/components/index This article explains how to create and use Razor components in Blazor apps, including guidance on Razor syntax, component naming, namespaces, and component parameters. -:::moniker range=">= aspnetcore-6.0" +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" Blazor apps are built using *Razor components*, informally known as *Blazor components*. A component is a self-contained portion of user interface (UI) with processing logic to enable dynamic behavior. Components can be nested, reused, shared among projects, and [used in MVC and Razor Pages apps](xref:blazor/components/prerendering-and-integration). @@ -3535,6 +3535,1501 @@ For more information, see the following articles: :::moniker-end +:::moniker range=">= aspnetcore-7.0" + +Blazor apps are built using *Razor components*, informally known as *Blazor components*. A component is a self-contained portion of user interface (UI) with processing logic to enable dynamic behavior. Components can be nested, reused, shared among projects, and [used in MVC and Razor Pages apps](xref:blazor/components/prerendering-and-integration). + +## Component classes + +Components are implemented using a combination of C# and HTML markup in [Razor](xref:mvc/views/razor) component files with the `.razor` file extension. + +### Razor syntax + +Components use [Razor syntax](xref:mvc/views/razor). Two Razor features are extensively used by components, *directives* and *directive attributes*. These are reserved keywords prefixed with `@` that appear in Razor markup: + +* [Directives](xref:mvc/views/razor#directives): Change the way component markup is parsed or functions. For example, the [`@page`][9] directive specifies a routable component with a route template and can be reached directly by a user's request in the browser at a specific URL. +* [Directive attributes](xref:mvc/views/razor#directive-attributes): Change the way a component element is parsed or functions. For example, the [`@bind`][10] directive attribute for an `<input>` element binds data to the element's value. + +Directives and directive attributes used in components are explained further in this article and other articles of the Blazor documentation set. For general information on Razor syntax, see <xref:mvc/views/razor>. + +### Names + +A component's name must start with an uppercase character: + +* `ProductDetail.razor` is valid. +* `productDetail.razor` is invalid. + +Common Blazor naming conventions used throughout the Blazor documentation include: + +* Component file paths use Pascal case† and appear before showing component code examples. Paths indicate typical folder locations. For example, `Pages/ProductDetail.razor` indicates that the `ProductDetail` component has a file name of `ProductDetail.razor` and resides in the `Pages` folder of the app. +* Component file paths for routable components match their URLs with hyphens appearing for spaces between words in a component's route template. For example, a `ProductDetail` component with a route template of `/product-detail` (`@page "/product-detail"`) is requested in a browser at the relative URL `/product-detail`. + +†Pascal case (upper camel case) is a naming convention without spaces and punctuation and with the first letter of each word capitalized, including the first word. + +### Routing + +Routing in Blazor is achieved by providing a route template to each accessible component in the app with an [`@page`][9] directive. When a Razor file with an [`@page`][9] directive is compiled, the generated class is given a <xref:Microsoft.AspNetCore.Mvc.RouteAttribute> specifying the route template. At runtime, the router searches for component classes with a <xref:Microsoft.AspNetCore.Mvc.RouteAttribute> and renders whichever component has a route template that matches the requested URL. + +The following `HelloWorld` component uses a route template of `/hello-world`. The rendered webpage for the component is reached at the relative URL `/hello-world`. When running a Blazor app locally with the default protocol, host, and port, the `HelloWorld` component is requested in the browser at `https://localhost:5001/hello-world`. Components that produce webpages usually reside in the `Pages` folder, but you can use any folder to hold components, including within nested folders. + +`Pages/HelloWorld.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/HelloWorld.razor"::: + +The preceding component loads in the browser at `/hello-world` regardless of whether or not you add the component to the app's UI navigation. Optionally, components can be added to the `NavMenu` component so that a link to the component appears in the app's UI-based navigation. + +For the preceding `HelloWorld` component, you can add a `NavLink` component to the `NavMenu` component in the `Shared` folder. For more information, including descriptions of the `NavLink` and `NavMenu` components, see <xref:blazor/fundamentals/routing>. + +### Markup + +A component's UI is defined using [Razor syntax](xref:mvc/views/razor), which consists of Razor markup, C#, and HTML. When an app is compiled, the HTML markup and C# rendering logic are converted into a component class. The name of the generated class matches the name of the file. + +Members of the component class are defined in one or more [`@code`][1] blocks. In [`@code`][1] blocks, component state is specified and processed with C#: + +* Property and field initializers. +* Parameter values from arguments passed by parent components and route parameters. +* Methods for user event handling, lifecycle events, and custom component logic. + +Component members are used in rendering logic using C# expressions that start with the `@` symbol. For example, a C# field is rendered by prefixing `@` to the field name. The following `Markup` component evaluates and renders: + +* `headingFontStyle` for the CSS property value `font-style` of the heading element. +* `headingText` for the content of the heading element. + +`Pages/Markup.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/Markup.razor"::: + +> [!NOTE] +> Examples throughout the Blazor documentation specify the [`private` access modifier](/dotnet/csharp/language-reference/keywords/private) for private members. Private members are scoped to a component's class. However, C# assumes the `private` access modifier when no access modifier is present, so explicitly marking members "`private`" in your own code is optional. For more information on access modifiers, see [Access Modifiers (C# Programming Guide)](/dotnet/csharp/programming-guide/classes-and-structs/access-modifiers). + +The Blazor framework processes a component internally as a [*render tree*](https://developer.mozilla.org/docs/Web/Performance/How_browsers_work#render), which is the combination of a component's [Document Object Model (DOM)](https://developer.mozilla.org/docs/Web/API/Document_Object_Model/Introduction) and [Cascading Style Sheet Object Model (CSSOM)](https://developer.mozilla.org/docs/Web/API/CSS_Object_Model). After the component is initially rendered, the component's render tree is regenerated in response to events. Blazor compares the new render tree against the previous render tree and applies any modifications to the browser's DOM for display. For more information, see <xref:blazor/components/rendering>. + +Components are ordinary [C# classes](/dotnet/csharp/programming-guide/classes-and-structs/classes) and can be placed anywhere within a project. Components that produce webpages usually reside in the `Pages` folder. Non-page components are frequently placed in the `Shared` folder or a custom folder added to the project. + +Razor syntax for C# control structures, directives, and directive attributes are lowercase (examples: [`@if`](xref:mvc/views/razor#conditionals-if-else-if-else-and-switch), [`@code`](xref:mvc/views/razor#code), [`@bind`](xref:mvc/views/razor#bind)). Property names are uppercase (example: `@Body` for <xref:Microsoft.AspNetCore.Components.LayoutComponentBase.Body?displayProperty=nameWithType>). + +### Asynchronous methods (`async`) don't support returning `void` + +The Blazor framework doesn't track `void`-returning asynchronous methods (`async`). As a result, exceptions aren't caught if `void` is returned. Always return a <xref:System.Threading.Tasks.Task> from asynchronous methods. + +### Nested components + +Components can include other components by declaring them using HTML syntax. The markup for using a component looks like an HTML tag where the name of the tag is the component type. + +Consider the following `Heading` component, which can be used by other components to display a heading. + +`Shared/Heading.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/index/Heading.razor"::: + +The following markup in the `HeadingExample` component renders the preceding `Heading` component at the location where the `<Heading />` tag appears. + +`Pages/HeadingExample.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/HeadingExample.razor"::: + +If a component contains an HTML element with an uppercase first letter that doesn't match a component name within the same namespace, a warning is emitted indicating that the element has an unexpected name. Adding an [`@using`][2] directive for the component's namespace makes the component available, which resolves the warning. For more information, see the [Namespaces](#namespaces) section. + +The `Heading` component example shown in this section doesn't have an [`@page`][9] directive, so the `Heading` component isn't directly accessible to a user via a direct request in the browser. However, any component with an [`@page`][9] directive can be nested in another component. If the `Heading` component was directly accessible by including `@page "/heading"` at the top of its Razor file, then the component would be rendered for browser requests at both `/heading` and `/heading-example`. + +### Namespaces + +Typically, a component's namespace is derived from the app's root namespace and the component's location (folder) within the app. If the app's root namespace is `BlazorSample` and the `Counter` component resides in the `Pages` folder: + +* The `Counter` component's namespace is `BlazorSample.Pages`. +* The fully qualified type name of the component is `BlazorSample.Pages.Counter`. + +For custom folders that hold components, add an [`@using`][2] directive to the parent component or to the app's `_Imports.razor` file. The following example makes components in the `Components` folder available: + +```razor +@using BlazorSample.Components +``` + +> [!NOTE] +> [`@using`][2] directives in the `_Imports.razor` file are only applied to Razor files (`.razor`), not C# files (`.cs`). + +Components can also be referenced using their fully qualified names, which doesn't require an [`@using`][2] directive. The following example directly references the `ProductDetail` component in the `Components` folder of the app: + +```razor +<BlazorSample.Components.ProductDetail /> +``` + +The namespace of a component authored with Razor is based on the following (in priority order): + +* The [`@namespace`][8] directive in the Razor file's markup (for example, `@namespace BlazorSample.CustomNamespace`). +* The project's `RootNamespace` in the project file (for example, `<RootNamespace>BlazorSample</RootNamespace>`). +* The project name, taken from the project file's file name (`.csproj`), and the path from the project root to the component. For example, the framework resolves `{PROJECT ROOT}/Pages/Index.razor` with a project namespace of `BlazorSample` (`BlazorSample.csproj`) to the namespace `BlazorSample.Pages` for the `Index` component. `{PROJECT ROOT}` is the project root path. Components follow C# name binding rules. For the `Index` component in this example, the components in scope are all of the components: + * In the same folder, `Pages`. + * The components in the project's root that don't explicitly specify a different namespace. + +The following are **not** supported: + +* The [`global::`](/dotnet/csharp/language-reference/operators/namespace-alias-qualifier) qualification. +* Importing components with aliased [`using`](/dotnet/csharp/language-reference/keywords/using-statement) statements. For example, `@using Foo = Bar` isn't supported. +* Partially-qualified names. For example, you can't add `@using BlazorSample` to a component and then reference the `NavMenu` component in the app's `Shared` folder (`Shared/NavMenu.razor`) with `<Shared.NavMenu></Shared.NavMenu>`. + +### Partial class support + +Components are generated as [C# partial classes](/dotnet/csharp/programming-guide/classes-and-structs/partial-classes-and-methods) and are authored using either of the following approaches: + +* A single file contains C# code defined in one or more [`@code`][1] blocks, HTML markup, and Razor markup. Blazor project templates define their components using this single-file approach. +* HTML and Razor markup are placed in a Razor file (`.razor`). C# code is placed in a code-behind file defined as a partial class (`.cs`). + +> [!NOTE] +> A component stylesheet that defines component-specific styles is a separate file (`.css`). Blazor CSS isolation is described later in <xref:blazor/components/css-isolation>. + +The following example shows the default `Counter` component with an [`@code`][1] block in an app generated from a Blazor project template. Markup and C# code are in the same file. This is the most common approach taken in component authoring. + +`Pages/Counter.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/Counter.razor"::: + +The following `Counter` component splits HTML and Razor markup from C# code using a code-behind file with a partial class: + +`Pages/CounterPartialClass.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/CounterPartialClass.razor"::: + +`Pages/CounterPartialClass.razor.cs`: + +```csharp +namespace BlazorSample.Pages +{ + public partial class CounterPartialClass + { + private int currentCount = 0; + + void IncrementCount() + { + currentCount++; + } + } +} +``` + +[`@using`][2] directives in the `_Imports.razor` file are only applied to Razor files (`.razor`), not C# files (`.cs`). Add namespaces to a partial class file as needed. + +Typical namespaces used by components: + +```csharp +using System.Net.Http; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Components.Forms; +using Microsoft.AspNetCore.Components.Routing; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.Components.Web.Virtualization; +using Microsoft.JSInterop; +``` + +Typical namespaces also include the namespace of the app and the namespace corresponding to the app's `Shared` folder: + +```csharp +using BlazorSample; +using BlazorSample.Shared; +``` + +### Specify a base class + +The [`@inherits`][6] directive is used to specify a base class for a component. The following example shows how a component can inherit a base class to provide the component's properties and methods. The `BlazorRocksBase` base class derives from <xref:Microsoft.AspNetCore.Components.ComponentBase>. + +`Pages/BlazorRocks.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/BlazorRocks.razor"::: + +`BlazorRocksBase.cs`: + +:::code language="csharp" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/BlazorRocksBase.cs"::: + +## Component parameters + +*Component parameters* pass data to components and are defined using public [C# properties](/dotnet/csharp/programming-guide/classes-and-structs/properties) on the component class with the [`[Parameter]` attribute](xref:Microsoft.AspNetCore.Components.ParameterAttribute). In the following example, a built-in reference type (<xref:System.String?displayProperty=fullName>) and a user-defined reference type (`PanelBody`) are passed as component parameters. + +`PanelBody.cs`: + +:::code language="csharp" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/PanelBody.cs"::: + +`Shared/ParameterChild.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/index/ParameterChild.razor"::: + +> [!WARNING] +> Providing initial values for component parameters is supported, but don't create a component that writes to its own parameters after the component is rendered for the first time. For more information, see the [Overwritten parameters](#overwritten-parameters) section of this article. + +The `Title` and `Body` component parameters of the `ParameterChild` component are set by arguments in the HTML tag that renders the instance of the component. The following `ParameterParent` component renders two `ParameterChild` components: + +* The first `ParameterChild` component is rendered without supplying parameter arguments. +* The second `ParameterChild` component receives values for `Title` and `Body` from the `ParameterParent` component, which uses an [explicit C# expression](xref:mvc/views/razor#explicit-razor-expressions) to set the values of the `PanelBody`'s properties. + +`Pages/ParameterParent.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/ParameterParent.razor"::: + +The following rendered HTML markup from the `ParameterParent` component shows `ParameterChild` component default values when the `ParameterParent` component doesn't supply component parameter values. When the `ParameterParent` component provides component parameter values, they replace the `ParameterChild` component's default values. + +> [!NOTE] +> For clarity, rendered CSS style classes aren't shown in the following rendered HTML markup. + +```html +<h1>Child component (without attribute values)</h1> + +<div> + <div>Set By Child</div> + <div>Set by child.</div> +</div> + +<h1>Child component (with attribute values)</h1> + +<div> + <div>Set by Parent</div> + <div>Set by parent.</div> +</div> +``` + +Assign a C# field, property, or result of a method to a component parameter as an HTML attribute value using [Razor's reserved `@` symbol](xref:mvc/views/razor#razor-syntax). The following `ParameterParent2` component displays four instances of the preceding `ParameterChild` component and sets their `Title` parameter values to: + +* The value of the `title` field. +* The result of the `GetTitle` C# method. +* The current local date in long format with <xref:System.DateTime.ToLongDateString%2A>, which uses an [implicit C# expression](xref:mvc/views/razor#implicit-razor-expressions). +* The `panelData` object's `Title` property. + +The `@` prefix is required for string parameters. Otherwise, the framework assumes that a string literal is set. + +Outside of string parameters, we recommend use the use of the `@` prefix for nonliterals, even when they aren't strictly required. + +We don't recommend the use of the `@` prefix for literals (for example, boolean values), keywords (for example, `this`), or `null`, but you can choose to use them if you wish. For example, `IsFixed="@true"` is uncommon but supported. + +Quotes around parameter attribute values are optional in most cases per the HTML5 specification. For example, `Value=this` is supported, instead of `Value="this"`. However, we recommend using quotes because it's easier to remember and widely adopted across web-based technologies. + +Throughout the documentation, code examples: + +* Always use quotes. Example: `Value="this"`. +* Nonliterals always use the `@` prefix, even when it's optional. Examples: `Title="@title"`, where `title` is a string-typed variable. `Count="@ct"`, where `ct` is a number-typed variable. +* Literals, outside of Razor expressions, always avoid `@`. Example: `IsFixed="true"`. + +`Pages/ParameterParent2.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/ParameterParent2.razor"::: + +> [!NOTE] +> When assigning a C# member to a component parameter, prefix the member with the `@` symbol and never prefix the parameter's HTML attribute. +> +> Correct: +> +> ```razor +> <ParameterChild Title="@title" /> +> ``` +> +> Incorrect: +> +> ```razor +> <ParameterChild @Title="title" /> +> ``` + +Unlike in Razor pages (`.cshtml`), Blazor can't perform asynchronous work in a Razor expression while rendering a component. This is because Blazor is designed for rendering interactive UIs. In an interactive UI, the screen must always display something, so it doesn't make sense to block the rendering flow. Instead, asynchronous work is performed during one of the [asynchronous lifecycle events](xref:blazor/components/lifecycle). After each asynchronous lifecycle event, the component may render again. The following Razor syntax is **not** supported: + +```razor +<ParameterChild Title="@await ..." /> +``` + +The code in the preceding example generates a *compiler error* when the app is built: + +> The 'await' operator can only be used within an async method. Consider marking this method with the 'async' modifier and changing its return type to 'Task'. + +To obtain a value for the `Title` parameter in the preceding example asynchronously, the component can use the [`OnInitializedAsync` lifecycle event](xref:blazor/components/lifecycle#component-initialization-oninitializedasync), as the following example demonstrates: + +```razor +<ParameterChild Title="@title" /> + +@code { + private string? title; + + protected override async Task OnInitializedAsync() + { + title = await ...; + } +} +``` + +For more information, see <xref:blazor/components/lifecycle>. + +Use of an explicit Razor expression to concatenate text with an expression result for assignment to a parameter is **not** supported. The following example seeks to concatenate the text "`Set by `" with an object's property value. Although this syntax is supported in a Razor page (`.cshtml`), it isn't valid for assignment to the child's `Title` parameter in a component. The following Razor syntax is **not** supported: + +```razor +<ParameterChild Title="Set by @(panelData.Title)" /> +``` + +The code in the preceding example generates a *compiler error* when the app is built: + +> Component attributes do not support complex content (mixed C# and markup). + +To support the assignment of a composed value, use a method, field, or property. The following example performs the concatenation of "`Set by `" and an object's property value in the C# method `GetTitle`: + +`Pages/ParameterParent3.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/ParameterParent3.razor"::: + +For more information, see <xref:mvc/views/razor>. + +> [!WARNING] +> Providing initial values for component parameters is supported, but don't create a component that writes to its own parameters after the component is rendered for the first time. For more information, see the [Overwritten parameters](#overwritten-parameters) section of this article. + +Component parameters should be declared as *auto-properties*, meaning that they shouldn't contain custom logic in their `get` or `set` accessors. For example, the following `StartData` property is an auto-property: + +```csharp +[Parameter] +public DateTime StartData { get; set; } +``` + +Don't place custom logic in the `get` or `set` accessor because component parameters are purely intended for use as a channel for a parent component to flow information to a child component. If a `set` accessor of a child component property contains logic that causes rerendering of the parent component, an infinite rendering loop results. + +To transform a received parameter value: + +* Leave the parameter property as an auto-property to represent the supplied raw data. +* Create a different property or method to supply the transformed data based on the parameter property. + +Override [`OnParametersSetAsync`](xref:blazor/components/lifecycle#after-parameters-are-set-onparameterssetasync) to transform a received parameter each time new data is received. + +Writing an initial value to a component parameter is supported because initial value assignments don't interfere with the Blazor's automatic component rendering. The following assignment of the current local <xref:System.DateTime> with <xref:System.DateTime.Now?displayProperty=nameWithType> to `StartData` is valid syntax in a component: + +```csharp +[Parameter] +public DateTime StartData { get; set; } = DateTime.Now; +``` + +After the initial assignment of <xref:System.DateTime.Now?displayProperty=nameWithType>, do **not** assign a value to `StartData` in developer code. For more information, see the [Overwritten parameters](#overwritten-parameters) section of this article. + +Apply the [`[EditorRequired]` attribute](xref:Microsoft.AspNetCore.Components.EditorRequiredAttribute) to specify a required component parameter. If a parameter value isn't provided, editors or build tools may display warnings to the user. This attribute is only valid on properties also marked with the [`[Parameter]` attribute](xref:Microsoft.AspNetCore.Components.ParameterAttribute). The <xref:Microsoft.AspNetCore.Components.EditorRequiredAttribute> is enforced at design-time and when the app is built. The attribute isn't enforced at runtime, and it doesn't guarantee a non-`null` parameter value. + +```csharp +[Parameter] +[EditorRequired] +public string? Title { get; set; } +``` + +Single-line attribute lists are also supported: + +```csharp +[Parameter, EditorRequired] +public string? Title { get; set; } +``` + +[`Tuples`](/dotnet/csharp/language-reference/builtin-types/value-tuples) ([API documentation](xref:System.Tuple)) are supported for component parameters and [`RenderFragment`](#child-content-render-fragments) types. The following component parameter example passes three values in a `Tuple`: + +`Shared/RenderTupleChild.razor`: + +```csharp +<div class="card w-50" style="margin-bottom:15px"> + <div class="card-header font-weight-bold"><code>Tuple</code> Card</div> + <div class="card-body"> + <ul> + <li>Integer: @Data?.Item1</li> + <li>String: @Data?.Item2</li> + <li>Boolean: @Data?.Item3</li> + </ul> + </div> +</div> + +@code { + [Parameter] + public Tuple<int, string, bool>? Data { get; set; } +} +``` + +`Pages/RenderTupleParent.razor`: + +```csharp +@page "/render-tuple-parent" + +<h1>Render <code>Tuple</code> Parent</h1> + +<RenderTupleChild Data="@data" /> + +@code { + private Tuple<int, string, bool> data = new(999, "I aim to misbehave.", true); +} +``` + +Only ***unnamed tuples*** are supported for C# 7.0 or later in Razor components. [Named tuples](/dotnet/csharp/language-reference/builtin-types/value-tuples#tuple-field-names) support in Razor components is planned for a future ASP.NET Core release. For more information, see [Blazor Transpiler issue with named Tuples (dotnet/aspnetcore #28982)](https://github.com/dotnet/aspnetcore/issues/28982). + +Quote ©2005 [Universal Pictures](https://www.uphe.com): [Serenity](https://www.uphe.com/movies/serenity-2005) ([Nathan Fillion](https://www.imdb.com/name/nm0277213/)) + +## 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. + +Optional route parameters are supported. In the following example, the `text` optional parameter assigns the value of the route segment to the component's `Text` property. If the segment isn't present, the value of `Text` is set to "`fantastic`" in the [`OnInitialized` lifecycle method](xref:blazor/components/lifecycle#component-initialization-oninitializedasync). + +`Pages/RouteParameter.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/RouteParameter.razor" highlight="1,6-7"::: + +For information on catch-all route parameters (`{*pageRoute}`), which capture paths across multiple folder boundaries, see <xref:blazor/fundamentals/routing#catch-all-route-parameters>. + +## Child content render fragments + +Components can set the content of another component. The assigning component provides the content between the child component's opening and closing tags. + +In the following example, the `RenderFragmentChild` component has a `ChildContent` component parameter that represents a segment of the UI to render as a <xref:Microsoft.AspNetCore.Components.RenderFragment>. The position of `ChildContent` in the component's Razor markup is where the content is rendered in the final HTML output. + +`Shared/RenderFragmentChild.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/index/RenderFragmentChild.razor"::: + +> [!IMPORTANT] +> The property receiving the <xref:Microsoft.AspNetCore.Components.RenderFragment> content must be named `ChildContent` by convention. +> +> [Event callbacks](xref:blazor/components/event-handling#eventcallback) aren't supported for <xref:Microsoft.AspNetCore.Components.RenderFragment>. + +The following `RenderFragmentParent` component provides content for rendering the `RenderFragmentChild` by placing the content inside the child component's opening and closing tags. + +`Pages/RenderFragmentParent.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/RenderFragmentParent.razor"::: + +Due to the way that Blazor renders child content, rendering components inside a [`for`](/dotnet/csharp/language-reference/keywords/for) loop requires a local index variable if the incrementing loop variable is used in the `RenderFragmentChild` component's content. The following example can be added to the preceding `RenderFragmentParent` component: + +```razor +<h1>Three children with an index variable</h1> + +@for (int c = 0; c < 3; c++) +{ + var current = c; + + <RenderFragmentChild> + Count: @current + </RenderFragmentChild> +} +``` + +Alternatively, use a [`foreach`](/dotnet/csharp/language-reference/keywords/foreach-in) loop with <xref:System.Linq.Enumerable.Range%2A?displayProperty=nameWithType> instead of a [`for`](/dotnet/csharp/language-reference/keywords/for) loop. The following example can be added to the preceding `RenderFragmentParent` component: + +```razor +<h1>Second example of three children with an index variable</h1> + +@foreach (var c in Enumerable.Range(0,3)) +{ + <RenderFragmentChild> + Count: @c + </RenderFragmentChild> +} +``` + +Render fragments are used to render child content throughout Blazor apps and are described with examples in the following articles and article sections: + +* [Blazor layouts](xref:blazor/components/layouts) +* [Pass data across a component hierarchy](xref:blazor/components/cascading-values-and-parameters#pass-data-across-a-component-hierarchy) +* [Templated components](xref:blazor/components/templated-components) +* [Global exception handling](xref:blazor/fundamentals/handle-errors#global-exception-handling) + +> [!NOTE] +> Blazor framework's [built-in Razor components](xref:blazor/components/built-in-components) use the same `ChildContent` component parameter convention to set their content. You can see the components that set child content by searching for the component parameter property name `ChildContent` in the [API documentation (filters API with the search term "ChildContent")](/dotnet/api/?term=ChildContent). + +## Render fragments for reusable rendering logic + +You can factor out child components purely as a way of reusing rendering logic. In any component's `@code` block, define a <xref:Microsoft.AspNetCore.Components.RenderFragment> and render the fragment from any location as many times as needed: + +```razor +<h1>Hello, world!</h1> + +@RenderWelcomeInfo + +<p>Render the welcome info a second time:</p> + +@RenderWelcomeInfo + +@code { + private RenderFragment RenderWelcomeInfo = __builder => + { + <p>Welcome to your new app!</p> + }; +} +``` + +For more information, see [Reuse rendering logic](xref:blazor/performance#define-reusable-renderfragments-in-code). + +## Overwritten parameters + +The Blazor framework generally imposes safe parent-to-child parameter assignment: + +* Parameters aren't overwritten unexpectedly. +* Side effects are minimized. For example, additional renders are avoided because they may create infinite rendering loops. + +A child component receives new parameter values that possibly overwrite existing values when the parent component rerenders. Accidentally overwriting parameter values in a child component often occurs when developing the component with one or more data-bound parameters and the developer writes directly to a parameter in the child: + +* The child component is rendered with one or more parameter values from the parent component. +* The child writes directly to the value of a parameter. +* The parent component rerenders and overwrites the value of the child's parameter. + +The potential for overwriting parameter values extends into the child component's property `set` accessors, too. + +> [!IMPORTANT] +> Our general guidance is not to create components that directly write to their own parameters after the component is rendered for the first time. + +Consider the following `Expander` component that: + +* Renders child content. +* Toggles showing child content with a component parameter (`Expanded`). + +After the following `Expander` component demonstrates an overwritten parameter, a modified `Expander` component is shown to demonstrate the correct approach for this scenario. The following examples can be placed in a local sample app to experience the behaviors described. + +`Shared/Expander.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/index/BadExpander.razor"::: + +The `Expander` component is added to the following `ExpanderExample` parent component that may call <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A>: + +* Calling <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> in developer code notifies a component that its state has changed and typically triggers component rerendering to update the UI. <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> is covered in more detail later in <xref:blazor/components/lifecycle> and <xref:blazor/components/rendering>. +* The button's `@onclick` directive attribute attaches an event handler to the button's `onclick` event. Event handling is covered in more detail later in <xref:blazor/components/event-handling>. + +`Pages/ExpanderExample.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/ExpanderExample.razor"::: + +Initially, the `Expander` components behave independently when their `Expanded` properties are toggled. The child components maintain their states as expected. + +If <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> is called in a parent component, the Blazor framework rerenders child components if their parameters might have changed: + +* For a group of parameter types that Blazor explicitly checks, Blazor rerenders a child component if it detects that any of the parameters have changed. +* For unchecked parameter types, Blazor rerenders the child component *regardless of whether or not the parameters have changed*. Child content falls into this category of parameter types because child content is of type <xref:Microsoft.AspNetCore.Components.RenderFragment>, which is a delegate that refers to other mutable objects. + +For the `ExpanderExample` component: + +* The first `Expander` component sets child content in a potentially mutable <xref:Microsoft.AspNetCore.Components.RenderFragment>, so a call to <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> in the parent component automatically rerenders the component and potentially overwrites the value of `Expanded` to its intitial value of `true`. +* The second `Expander` component doesn't set child content. Therefore, a potentially mutable <xref:Microsoft.AspNetCore.Components.RenderFragment> doesn't exist. A call to <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> in the parent component doesn't automatically rerender the child component, so the component's `Expanded` value isn't overwritten. + +To maintain state in the preceding scenario, use a *private field* in the `Expander` component to maintain its toggled state. + +The following revised `Expander` component: + +* Accepts the `Expanded` component parameter value from the parent. +* Assigns the component parameter value to a *private field* (`expanded`) in the [`OnInitialized` event](xref:blazor/components/lifecycle#component-initialization-oninitializedasync). +* Uses the private field to maintain its internal toggle state, which demonstrates how to avoid writing directly to a parameter. + +> [!NOTE] +> The advice in this section extends to similar logic in component parameter `set` accessors, which can result in similar undesirable side effects. + +`Shared/Expander.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/index/Expander.razor"::: + +For two-way parent-child binding examples, see <xref:blazor/components/data-binding#binding-with-component-parameters>. For additional information, see [Blazor Two Way Binding Error (dotnet/aspnetcore #24599)](https://github.com/dotnet/aspnetcore/issues/24599). + +For more information on change detection, inlcuding information on the exact types that Blazor checks, see <xref:blazor/components/rendering#rendering-conventions-for-componentbase>. + +## Attribute splatting and arbitrary parameters + +Components can capture and render additional attributes in addition to the component's declared parameters. Additional attributes can be captured in a dictionary and then *splatted* onto an element when the component is rendered using the [`@attributes`][3] Razor directive attribute. This scenario is useful for defining a component that produces a markup element that supports a variety of customizations. For example, it can be tedious to define attributes separately for an `<input>` that supports many parameters. + +In the following `Splat` component: + +* The first `<input>` element (`id="useIndividualParams"`) uses individual component parameters. +* The second `<input>` element (`id="useAttributesDict"`) uses attribute splatting. + +`Pages/Splat.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/Splat.razor"::: + +The rendered `<input>` elements in the webpage are identical: + +```html +<input id="useIndividualParams" + maxlength="10" + placeholder="Input placeholder text" + required="required" + size="50"> + +<input id="useAttributesDict" + maxlength="10" + placeholder="Input placeholder text" + required="required" + size="50"> +``` + +To accept arbitrary attributes, define a [component parameter](#component-parameters) with the <xref:Microsoft.AspNetCore.Components.ParameterAttribute.CaptureUnmatchedValues> property set to `true`: + +```razor +@code { + [Parameter(CaptureUnmatchedValues = true)] + public Dictionary<string, object>? InputAttributes { get; set; } +} +``` + +The <xref:Microsoft.AspNetCore.Components.ParameterAttribute.CaptureUnmatchedValues> property on [`[Parameter]`](xref:Microsoft.AspNetCore.Components.ParameterAttribute) allows the parameter to match all attributes that don't match any other parameter. A component can only define a single parameter with <xref:Microsoft.AspNetCore.Components.ParameterAttribute.CaptureUnmatchedValues>. The property type used with <xref:Microsoft.AspNetCore.Components.ParameterAttribute.CaptureUnmatchedValues> must be assignable from [`Dictionary<string, object>`](xref:System.Collections.Generic.Dictionary%602) with string keys. Use of [`IEnumerable<KeyValuePair<string, object>>`](xref:System.Collections.Generic.IEnumerable%601) or [`IReadOnlyDictionary<string, object>`](xref:System.Collections.Generic.IReadOnlyDictionary%602) are also options in this scenario. + +The position of [`@attributes`][3] relative to the position of element attributes is important. When [`@attributes`][3] are splatted on the element, the attributes are processed from right to left (last to first). Consider the following example of a parent component that consumes a child component: + +`Shared/AttributeOrderChild1.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/index/AttributeOrderChild1.razor"::: + +`Pages/AttributeOrderParent1.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/AttributeOrderParent1.razor"::: + +The `AttributeOrderChild1` component's `extra` attribute is set to the right of [`@attributes`][3]. The `AttributeOrderParent1` component's rendered `<div>` contains `extra="5"` when passed through the additional attribute because the attributes are processed right to left (last to first): + +```html +<div extra="5" /> +``` + +In the following example, the order of `extra` and [`@attributes`][3] is reversed in the child component's `<div>`: + +`Shared/AttributeOrderChild2.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/index/AttributeOrderChild2.razor"::: + +`Pages/AttributeOrderParent2.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/AttributeOrderParent2.razor"::: + +The `<div>` in the parent component's rendered webpage contains `extra="10"` when passed through the additional attribute: + +```html +<div extra="10" /> +``` + +## Capture references to components + +Component references provide a way to reference a component instance for issuing commands. To capture a component reference: + +* Add an [`@ref`][4] attribute to the child component. +* Define a field with the same type as the child component. + +When the component is rendered, the field is populated with the component instance. You can then invoke .NET methods on the instance. + +Consider the following `ReferenceChild` component that logs a message when its `ChildMethod` is called. + +`Shared/ReferenceChild.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/index/ReferenceChild.razor"::: + +A component reference is only populated after the component is rendered and its output includes `ReferenceChild`'s element. Until the component is rendered, there's nothing to reference. + +To manipulate component references after the component has finished rendering, use the [`OnAfterRender` or `OnAfterRenderAsync` methods](xref:blazor/components/lifecycle#after-component-render-onafterrenderasync). + +To use a reference variable with an event handler, use a lambda expression or assign the event handler delegate in the [`OnAfterRender` or `OnAfterRenderAsync` methods](xref:blazor/components/lifecycle#after-component-render-onafterrenderasync). This ensures that the reference variable is assigned before the event handler is assigned. + +The following lambda approach uses the preceding `ReferenceChild` component. + +`Pages/ReferenceParent1.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/ReferenceParent1.razor"::: + +The following delegate approach uses the preceding `ReferenceChild` component. + +`Pages/ReferenceParent2.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/ReferenceParent2.razor"::: + +While capturing component references use a similar syntax to [capturing element references](xref:blazor/js-interop/call-javascript-from-dotnet#capture-references-to-elements), capturing component references isn't a JavaScript interop feature. Component references aren't passed to JavaScript code. Component references are only used in .NET code. + +> [!IMPORTANT] +> Do **not** use component references to mutate the state of child components. Instead, use normal declarative component parameters to pass data to child components. Use of component parameters result in child components that rerender at the correct times automatically. For more information, see the [component parameters](#component-parameters) section and the <xref:blazor/components/data-binding> article. + +## Synchronization context + +Blazor uses a synchronization context (<xref:System.Threading.SynchronizationContext>) to enforce a single logical thread of execution. A component's [lifecycle methods](xref:blazor/components/lifecycle) and event callbacks raised by Blazor are executed on the synchronization context. + +Blazor Server's synchronization context attempts to emulate a single-threaded environment so that it closely matches the WebAssembly model in the browser, which is single threaded. At any given point in time, work is performed on exactly one thread, which yields the impression of a single logical thread. No two operations execute concurrently. + +### Avoid thread-blocking calls + +Generally, don't call the following methods in components. The following methods block the execution thread and thus block the app from resuming work until the underlying <xref:System.Threading.Tasks.Task> is complete: + +* <xref:System.Threading.Tasks.Task%601.Result%2A> +* <xref:System.Threading.Tasks.Task.Wait%2A> +* <xref:System.Threading.Tasks.Task.WaitAny%2A> +* <xref:System.Threading.Tasks.Task.WaitAll%2A> +* <xref:System.Threading.Thread.Sleep%2A> +* <xref:System.Runtime.CompilerServices.TaskAwaiter.GetResult%2A> + +> [!NOTE] +> Blazor documentation examples that use the thread-blocking methods mentioned in this section are only using the methods for demonstration purposes, not as recommended coding guidance. For example, a few component code demonstrations simulate a long-running process by calling <xref:System.Threading.Thread.Sleep%2A?displayProperty=nameWithType>. + +### Invoke component methods externally to update state + +In the event a component must be updated based on an external event, such as a timer or other notification, use the `InvokeAsync` method, which dispatches code execution to Blazor's synchronization context. For example, consider the following *notifier service* that can notify any listening component about updated state. The `Update` method can be called from anywhere in the app. + +`TimerService.cs`: + +:::code language="csharp" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/TimerService.cs"::: + +`NotifierService.cs`: + +:::code language="csharp" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/NotifierService.cs"::: + +Register the services: + +* In a Blazor WebAssembly app, register the services as singletons in `Program.cs`: + + ```csharp + builder.Services.AddSingleton<NotifierService>(); + builder.Services.AddSingleton<TimerService>(); + ``` + +* In a Blazor Server app, register the services as scoped in `Program.cs`: + + ```csharp + builder.Services.AddScoped<NotifierService>(); + builder.Services.AddScoped<TimerService>(); + ``` + +Use the `NotifierService` to update a component. + +`Pages/ReceiveNotifications.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/ReceiveNotifications.razor"::: + +In the preceding example: + +* `NotifierService` invokes the component's `OnNotify` method outside of Blazor's synchronization context. `InvokeAsync` is used to switch to the correct context and queue a render. For more information, see <xref:blazor/components/rendering>. +* The component implements <xref:System.IDisposable>. The `OnNotify` delegate is unsubscribed in the `Dispose` method, which is called by the framework when the component is disposed. For more information, see <xref:blazor/components/lifecycle#component-disposal-with-idisposable-and-iasyncdisposable>. + +## Use `@key` to control the preservation of elements and components + +When rendering a list of elements or components and the elements or components subsequently change, Blazor must decide which of the previous elements or components can be retained and how model objects should map to them. Normally, this process is automatic and can be ignored, but there are cases where you may want to control the process. + +Consider the following `Details` and `People` components: + +* The `Details` component receives data (`Data`) from the parent `People` component, which is displayed in an `<input>` element. Any given displayed `<input>` element can receive the focus of the page from the user when they select one of the `<input>` elements. +* The `People` component creates a list of person objects for display using the `Details` component. Every three seconds, a new person is added to the collection. + +This demonstration allows you to: + +* Select an `<input>` from among several rendered `Details` components. +* Study the behavior of the page's focus as the people collection automatically grows. + +`Shared/Details.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/index/Details.razor"::: + +In the following `People` component, each iteration of adding a person in `OnTimerCallback` results in Blazor rebuilding the entire collection. The page's focus remains on the *same index* position of `<input>` elements, so the focus shifts each time a person is added. *Shifting the focus away from what the user selected isn't desirable behavior.* After demonstrating the poor behavior with the following component, the [`@key`][5] directive attribute is used to improve the user's experience. + +`Pages/People.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/People.razor"::: + +The contents of the `people` collection changes with inserted, deleted, or re-ordered entries. Rerendering can lead to visible behavior differences. Each time a person is inserted into the `people` collection, the *preceding element* of the currently focused element receives the focus. The user's focus is lost. + +The mapping process of elements or components to a collection can be controlled with the [`@key`][5] directive attribute. Use of [`@key`][5] guarantees the preservation of elements or components based on the key's value. If the `Details` component in the preceding example is keyed on the `person` item, Blazor ignores rerendering `Details` components that haven't changed. + +To modify the `People` component to use the [`@key`][5] directive attribute with the `people` collection, update the `<Details>` element to the following: + +```razor +<Details @key="person" Data="@person.Data" /> +``` + +When the `people` collection changes, the association between `Details` instances and `person` instances is retained. When a `Person` is inserted at the beginning of the collection, one new `Details` instance is inserted at that corresponding position. Other instances are left unchanged. Therefore, the user's focus isn't lost as people are added to the collection. + +Other collection updates exhibit the same behavior when the [`@key`][5] directive attribute is used: + +* If an instance is deleted from the collection, only the corresponding component instance is removed from the UI. Other instances are left unchanged. +* If collection entries are re-ordered, the corresponding component instances are preserved and re-ordered in the UI. + +> [!IMPORTANT] +> Keys are local to each container element or component. Keys aren't compared globally across the document. + +### When to use `@key` + +Typically, it makes sense to use [`@key`][5] whenever a list is rendered (for example, in a [`foreach`](/dotnet/csharp/language-reference/keywords/foreach-in) block) and a suitable value exists to define the [`@key`][5]. + +You can also use [`@key`][5] to preserve an element or component subtree when an object doesn't change, as the following examples show. + +Example 1: + +```razor +<li @key="person"> + <input value="@person.Data" /> +</li> +``` + +Example 2: + +```razor +<div @key="person"> + @* other HTML elements *@ +</div> +``` + +If an `person` instance changes, the [`@key`][5] attribute directive forces Blazor to: + +* Discard the entire `<li>` or `<div>` and their descendants. +* Rebuild the subtree within the UI with new elements and components. + +This is useful to guarantee that no UI state is preserved when the collection changes within a subtree. + +### Scope of `@key` + +The [`@key`][5] attribute directive is scoped to its own siblings within its parent. + +Consider the following example. The `first` and `second` keys are compared against each other within the same scope of the outer `<div>` element: + +```razor +<div> + <div @key="first">...</div> + <div @key="second">...</div> +</div> +``` + +The following example demonstrates `first` and `second` keys in their own scopes, unrelated to each other and without influence on each other. Each [`@key`][5] scope only applies to its parent `<div>` element, not across the parent `<div>` elements: + +```razor +<div> + <div @key="first">...</div> +</div> +<div> + <div @key="second">...</div> +</div> +``` + +For the `Details` component shown earlier, the following examples render `person` data within the same [`@key`][5] scope and demonstrate typical use cases for [`@key`][5]: + +```razor +<div> + @foreach (var person in people) + { + <Details @key="person" Data="@person.Data" /> + } +</div> +``` + +```razor +@foreach (var person in people) +{ + <div @key="person"> + <Details Data="@person.Data" /> + </div> +} +``` + +```razor +<ol> + @foreach (var person in people) + { + <li @key="person"> + <Details Data="@person.Data" /> + </li> + } +</ol> +``` + +The following examples only scope [`@key`][5] to the `<div>` or `<li>` element that surrounds each `Details` component instance. Therefore, `person` data for each member of the `people` collection is **not** keyed on each `person` instance across the rendered `Details` components. Avoid the following patterns when using [`@key`][5]: + +```razor +@foreach (var person in people) +{ + <div> + <Details @key="person" Data="@person.Data" /> + </div> +} +``` + +```razor +<ol> + @foreach (var person in people) + { + <li> + <Details @key="person" Data="@person.Data" /> + </li> + } +</ol> +``` + +### When not to use `@key` + +There's a performance cost when rendering with [`@key`][5]. The performance cost isn't large, but only specify [`@key`][5] if preserving the element or component benefits the app. + +Even if [`@key`][5] isn't used, Blazor preserves child element and component instances as much as possible. The only advantage to using [`@key`][5] is control over *how* model instances are mapped to the preserved component instances, instead of Blazor selecting the mapping. + +### Values to use for `@key` + +Generally, it makes sense to supply one of the following values for [`@key`][5]: + +* Model object instances. For example, the `Person` instance (`person`) was used in the earlier example. This ensures preservation based on object reference equality. +* Unique identifiers. For example, unique identifiers can be based on primary key values of type `int`, `string`, or `Guid`. + +Ensure that values used for [`@key`][5] don't clash. If clashing values are detected within the same parent element, Blazor throws an exception because it can't deterministically map old elements or components to new elements or components. Only use distinct values, such as object instances or primary key values. + +## Apply an attribute + +Attributes can be applied to components with the [`@attribute`][7] directive. The following example applies the [`[Authorize]` attribute](xref:Microsoft.AspNetCore.Authorization.AuthorizeAttribute) to the component's class: + +```razor +@page "/" +@attribute [Authorize] +``` + +## Conditional HTML element attributes + +HTML element attribute properties are conditionally set based on the .NET value. If the value is `false` or `null`, the property isn't set. If the value is `true`, the property is set. + +In the following example, `IsCompleted` determines if the `<input>` element's `checked` property is set. + +`Pages/ConditionalAttribute.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/ConditionalAttribute.razor"::: + +For more information, see <xref:mvc/views/razor>. + +> [!WARNING] +> Some HTML attributes, such as [`aria-pressed`](https://developer.mozilla.org/docs/Web/Accessibility/ARIA/Roles/button_role#Toggle_buttons), don't function properly when the .NET type is a `bool`. In those cases, use a `string` type instead of a `bool`. + +## Raw HTML + +Strings are normally rendered using DOM text nodes, which means that any markup they may contain is ignored and treated as literal text. To render raw HTML, wrap the HTML content in a <xref:Microsoft.AspNetCore.Components.MarkupString> value. The value is parsed as HTML or SVG and inserted into the DOM. + +> [!WARNING] +> Rendering raw HTML constructed from any untrusted source is a **security risk** and should **always** be avoided. + +The following example shows using the <xref:Microsoft.AspNetCore.Components.MarkupString> type to add a block of static HTML content to the rendered output of a component. + +`Pages/MarkupStringExample.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/MarkupStringExample.razor"::: + +## Razor templates + +Render fragments can be defined using Razor template syntax to define a UI snippet. Razor templates use the following format: + +```razor +@<{HTML tag}>...</{HTML tag}> +``` + +The following example illustrates how to specify <xref:Microsoft.AspNetCore.Components.RenderFragment> and <xref:Microsoft.AspNetCore.Components.RenderFragment%601> values and render templates directly in a component. Render fragments can also be passed as arguments to [templated components](xref:blazor/components/templated-components). + +`Pages/RazorTemplate.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/RazorTemplate.razor"::: + +Rendered output of the preceding code: + +```html +<p>The time is 4/19/2021 8:54:46 AM.</p> +<p>Pet: Nutty Rex</p> +``` + +## Static assets + +Blazor follows the convention of ASP.NET Core apps for static assets. Static assets are located in the project's [`web root` (`wwwroot`) folder](xref:fundamentals/index#web-root) or folders under the `wwwroot` folder. + +Use a base-relative path (`/`) to refer to the web root for a static asset. In the following example, `logo.png` is physically located in the `{PROJECT ROOT}/wwwroot/images` folder. `{PROJECT ROOT}` is the app's project root. + +```razor +<img alt="Company logo" src="/images/logo.png" /> +``` + +Components do **not** support tilde-slash notation (`~/`). + +For information on setting an app's base path, see <xref:blazor/host-and-deploy/index#app-base-path>. + +## Tag Helpers aren't supported in components + +[`Tag Helpers`](xref:mvc/views/tag-helpers/intro) aren't supported in components. To provide Tag Helper-like functionality in Blazor, create a component with the same functionality as the Tag Helper and use the component instead. + +## Scalable Vector Graphics (SVG) images + +Since Blazor renders HTML, browser-supported images, including [Scalable Vector Graphics (SVG) images (`.svg`)](https://developer.mozilla.org/docs/Web/SVG), are supported via the `<img>` tag: + +```html +<img alt="Example image" src="image.svg" /> +``` + +Similarly, SVG images are supported in the CSS rules of a stylesheet file (`.css`): + +```css +.element-class { + background-image: url("image.svg"); +} +``` + +Blazor supports the [`<foreignObject>`](https://developer.mozilla.org/docs/Web/SVG/Element/foreignObject) element to display arbitrary HTML within an SVG. The markup can represent arbitrary HTML, a <xref:Microsoft.AspNetCore.Components.RenderFragment>, or a Razor component. + +The following example demonstrates: + +* Display of a `string` (`@message`). +* Two-way binding with an `<input>` element and a `value` field. +* A `Robot` component. + +```razor +<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg"> + <rect x="0" y="0" rx="10" ry="10" width="200" height="200" stroke="black" + fill="none" /> + <foreignObject x="20" y="20" width="160" height="160"> + <p>@message</p> + </foreignObject> +</svg> + +<svg xmlns="http://www.w3.org/2000/svg"> + <foreignObject width="200" height="200"> + <label> + Two-way binding: + <input @bind="value" @bind:event="oninput" /> + </label> + </foreignObject> +</svg> + +<svg xmlns="http://www.w3.org/2000/svg"> + <foreignObject> + <Robot /> + </foreignObject> +</svg> + +@code { + private string message = "Lorem ipsum dolor sit amet, consectetur adipiscing " + + "elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; + + private string? value; +} +``` + +## Whitespace rendering behavior + +Unless the [`@preservewhitespace`](xref:mvc/views/razor#preservewhitespace) directive is used with a value of `true`, extra whitespace is removed by default if: + +* Leading or trailing within an element. +* Leading or trailing within a <xref:Microsoft.AspNetCore.Components.RenderFragment>/<xref:Microsoft.AspNetCore.Components.RenderFragment%601> parameter (for example, child content passed to another component). +* It precedes or follows a C# code block, such as `@if` or `@foreach`. + +Whitespace removal might affect the rendered output when using a CSS rule, such as `white-space: pre`. To disable this performance optimization and preserve the whitespace, take one of the following actions: + +* Add the `@preservewhitespace true` directive at the top of the Razor file (`.razor`) to apply the preference to a specific component. +* Add the `@preservewhitespace true` directive inside an `_Imports.razor` file to apply the preference to a subdirectory or to the entire project. + +In most cases, no action is required, as apps typically continue to behave normally (but faster). If stripping whitespace causes a rendering problem for a particular component, use `@preservewhitespace true` in that component to disable this optimization. + +## Generic type parameter support + +The [`@typeparam`][11] directive declares a [generic type parameter](/dotnet/csharp/programming-guide/generics/generic-type-parameters) for the generated component class: + +```razor +@typeparam TItem +``` + +C# syntax with [`where`](/dotnet/csharp/language-reference/keywords/where-generic-type-constraint) type constraints is supported: + +```razor +@typeparam TEntity where TEntity : IEntity +``` + +In the following example, the `ListGenericTypeItems1` component is generically typed as `TExample`. + +`Shared/ListGenericTypeItems1.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/index/ListGenericTypeItems1.razor"::: + +The following `GenericTypeExample1` component renders two `ListGenericTypeItems1` components: + +* String or integer data is assigned to the `ExampleList` parameter of each component. +* Type `string` or `int` that matches the type of the assigned data is set for the type parameter (`TExample`) of each component. + +`Pages/GenericTypeExample1.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/index/GenericTypeExample1.razor"::: + +For more information, see <xref:mvc/views/razor#typeparam>. For an example of generic typing with templated components, see <xref:blazor/components/templated-components>. + +## Cascaded generic type support + +An ancestor component can cascade a type parameter by name to descendants using the [`[CascadingTypeParameter]` attribute](xref:Microsoft.AspNetCore.Components.CascadingTypeParameterAttribute). This attribute allows a generic type inference to use the specified type parameter automatically with descendants that have a type parameter with the same name. + +By adding `@attribute [CascadingTypeParameter(...)]` to a component, the specified generic type argument is automatically used by descendants that: + +* Are nested as child content for the component in the same `.razor` document. +* Also declare a [`@typeparam`](xref:mvc/views/razor#typeparam) with the exact same name. +* Don't have another value explicitly supplied or implicitly inferred for the type parameter. If another value is supplied or inferred, it takes precedence over the cascaded generic type. + +When receiving a cascaded type parameter, components obtain the parameter value from the closest ancestor that has a <xref:Microsoft.AspNetCore.Components.CascadingTypeParameterAttribute> with a matching name. Cascaded generic type parameters are overridden within a particular subtree. + +Matching is only performed by name. Therefore, we recommend avoiding a cascaded generic type parameter with a generic name, for example `T` or `TItem`. If a developer opts into cascading a type parameter, they're implicitly promising that its name is unique enough not to clash with other cascaded type parameters from unrelated components. + +Generic types can be cascaded to child components in either of the following approaches with ancestor (parent) components, which are demonstrated in the following two sub-sections: + +* Explicitly set the cascaded generic type. +* Infer the cascaded generic type. + +The following subsections provide examples of the preceding approaches using the following two `ListDisplay` components. The components receive and render list data and are generically typed as `TExample`. These components are for demonstration purposes and only differ in the color of text that the list is rendered. If you wish to experiment with the components in the following sub-sections in a local test app, add the following two components to the app first. + +`Shared/ListDisplay1.razor`: + +```razor +@typeparam TExample + +@if (ExampleList is not null) +{ + <ul style="color:blue"> + @foreach (var item in ExampleList) + { + <li>@item</li> + } + </ul> +} + +@code { + [Parameter] + public IEnumerable<TExample>? ExampleList { get; set; } +} +``` + +`Shared/ListDisplay2.razor`: + +```razor +@typeparam TExample + +@if (ExampleList is not null) +{ + <ul style="color:red"> + @foreach (var item in ExampleList) + { + <li>@item</li> + } + </ul> +} + +@code { + [Parameter] + public IEnumerable<TExample>? ExampleList { get; set; } +} +``` + +### Explicit generic types based on ancestor components + +The demonstration in this section cascades a type explicitly for `TExample`. + +> [!NOTE] +> This section uses the two `ListDisplay` components in the [Cascaded generic type support](#cascaded-generic-type-support) section. + +The following `ListGenericTypeItems2` component receives data and cascades a generic type parameter named `TExample` to its descendent components. In the upcoming parent component, the `ListGenericTypeItems2` component is used to display list data with the preceding `ListDisplay` component. + +`Shared/ListGenericTypeItems2.razor`: + +```razor +@attribute [CascadingTypeParameter(nameof(TExample))] +@typeparam TExample + +<h2>List Generic Type Items 2</h2> + +@ChildContent + +@code { + [Parameter] + public RenderFragment? ChildContent { get; set; } +} +``` + +The following `GenericTypeExample2` parent component sets the child content (<xref:Microsoft.AspNetCore.Components.RenderFragment>) of two `ListGenericTypeItems2` components specifying the `ListGenericTypeItems2` types (`TExample`), which are cascaded to child components. `ListDisplay` components are rendered with the list item data shown in the example. String data is used with the first `ListGenericTypeItems2` component, and integer data is used with the second `ListGenericTypeItems2` component. + +`Pages/GenericTypeExample2.razor`: + +```razor +@page "/generic-type-example-2" + +<h1>Generic Type Example 2</h1> + +<ListGenericTypeItems2 TExample="string"> + <ListDisplay1 ExampleList="@(new List<string> { "Item 1", "Item 2" })" /> + <ListDisplay2 ExampleList="@(new List<string> { "Item 3", "Item 4" })" /> +</ListGenericTypeItems2> + +<ListGenericTypeItems2 TExample="int"> + <ListDisplay1 ExampleList="@(new List<int> { 1, 2, 3 })" /> + <ListDisplay2 ExampleList="@(new List<int> { 4, 5, 6 })" /> +</ListGenericTypeItems2> +``` + +Specifying the type explicitly also allows the use of [cascading values and parameters](xref:blazor/components/cascading-values-and-parameters) to provide data to child components, as the following demonstration shows. + +`Shared/ListDisplay3.razor`: + +```razor +@typeparam TExample + +@if (ExampleList is not null) +{ + <ul style="color:blue"> + @foreach (var item in ExampleList) + { + <li>@item</li> + } + </ul> +} + +@code { + [CascadingParameter] + protected IEnumerable<TExample>? ExampleList { get; set; } +} +``` + +`Shared/ListDisplay4.razor`: + +```razor +@typeparam TExample + +@if (ExampleList is not null) +{ + <ul style="color:red"> + @foreach (var item in ExampleList) + { + <li>@item</li> + } + </ul> +} + +@code { + [CascadingParameter] + protected IEnumerable<TExample>? ExampleList { get; set; } +} +``` + +`Shared/ListGenericTypeItems3.razor`: + +```razor +@attribute [CascadingTypeParameter(nameof(TExample))] +@typeparam TExample + +<h2>List Generic Type Items 3</h2> + +@ChildContent + +@if (ExampleList is not null) +{ + <ul style="color:green"> + @foreach(var item in ExampleList) + { + <li>@item</li> + } + </ul> + + <p> + Type of <code>TExample</code>: @typeof(TExample) + </p> +} + +@code { + [CascadingParameter] + protected IEnumerable<TExample>? ExampleList { get; set; } + + [Parameter] + public RenderFragment? ChildContent { get; set; } +} +``` + +When cascading the data in the following example, the type must be provided to the `ListGenericTypeItems3` component. + +`Pages/GenericTypeExample3.razor`: + +```razor +@page "/generic-type-example-3" + +<h1>Generic Type Example 3</h1> + +<CascadingValue Value="@stringData"> + <ListGenericTypeItems3 TExample="string"> + <ListDisplay3 /> + <ListDisplay4 /> + </ListGenericTypeItems3> +</CascadingValue> + +<CascadingValue Value="@integerData"> + <ListGenericTypeItems3 TExample="int"> + <ListDisplay3 /> + <ListDisplay4 /> + </ListGenericTypeItems3> +</CascadingValue> + +@code { + private List<string> stringData = new() { "Item 1", "Item 2" }; + private List<int> integerData = new() { 1, 2, 3 }; +} +``` + +When multiple generic types are cascaded, values for all generic types in the set must be passed. In the following example, `TItem`, `TValue`, and `TEdit` are `GridColumn` generic types, but the parent component that places `GridColumn` doesn't specify the `TItem` type: + +```razor +<GridColumn TValue="string" TEdit="@TextEdit" /> +``` + +The preceding example generates a compile-time error that the `GridColumn` component is missing the `TItem` type parameter. Valid code specifies all of the types: + +```razor +<GridColumn TValue="string" TEdit="@TextEdit" TItem="@User" /> +``` + +### Infer generic types based on ancestor components + +The demonstration in this section cascades a type inferred for `TExample`. + +> [!NOTE] +> This section uses the two `ListDisplay` components in the [Cascaded generic type support](#cascaded-generic-type-support) section. + +`Shared/ListGenericTypeItems4.razor`: + +```razor +@attribute [CascadingTypeParameter(nameof(TExample))] +@typeparam TExample + +<h2>List Generic Type Items 4</h2> + +@ChildContent + +@if (ExampleList is not null) +{ + <ul style="color:green"> + @foreach(var item in ExampleList) + { + <li>@item</li> + } + </ul> + + <p> + Type of <code>TExample</code>: @typeof(TExample) + </p> +} + +@code { + [Parameter] + public IEnumerable<TExample>? ExampleList { get; set; } + + [Parameter] + public RenderFragment? ChildContent { get; set; } +} +``` + +The following `GenericTypeExample4` component with inferred cascaded types provides different data for display. + +`Pages/GenericTypeExample4.razor`: + +```razor +@page "/generic-type-example-4" + +<h1>Generic Type Example 4</h1> + +<ListGenericTypeItems4 ExampleList="@(new List<string> { "Item 5", "Item 6" })"> + <ListDisplay1 ExampleList="@(new List<string> { "Item 1", "Item 2" })" /> + <ListDisplay2 ExampleList="@(new List<string> { "Item 3", "Item 4" })" /> +</ListGenericTypeItems4> + +<ListGenericTypeItems4 ExampleList="@(new List<int> { 7, 8, 9 })"> + <ListDisplay1 ExampleList="@(new List<int> { 1, 2, 3 })" /> + <ListDisplay2 ExampleList="@(new List<int> { 4, 5, 6 })" /> +</ListGenericTypeItems4> +``` + +The following `GenericTypeExample5` component with inferred cascaded types provides the same data for display. The following example directly assigns the data to the components. + +`Pages/GenericTypeExample5.razor`: + +```razor +@page "/generic-type-example-5" + +<h1>Generic Type Example 5</h1> + +<ListGenericTypeItems4 ExampleList="@stringData"> + <ListDisplay1 ExampleList="@stringData" /> + <ListDisplay2 ExampleList="@stringData" /> +</ListGenericTypeItems4> + +<ListGenericTypeItems4 ExampleList="@integerData"> + <ListDisplay1 ExampleList="@integerData" /> + <ListDisplay2 ExampleList="@integerData" /> +</ListGenericTypeItems4> + +@code { + private List<string> stringData = new() { "Item 1", "Item 2" }; + private List<int> integerData = new() { 1, 2, 3 }; +} +``` + +## Render Razor components from JavaScript + +Razor components can be dynamically-rendered from JavaScript (JS) for existing JS apps. + +To render a Razor component from JS, register the component as a root component for JS rendering and assign the component an identifier: + +* In a Blazor Server app, modify the call to <xref:Microsoft.Extensions.DependencyInjection.ComponentServiceCollectionExtensions.AddServerSideBlazor%2A> in `Program.cs`: + + ```csharp + builder.Services.AddServerSideBlazor(options => + { + options.RootComponents.RegisterForJavaScript<Counter>(identifier: "counter"); + }); + ``` + + > [!NOTE] + > The preceding code example requires a namespace for the app's components (for example, `using BlazorSample.Pages;`) in the `Program.cs` file. + +* In a Blazor WebAssembly app, call <xref:Microsoft.AspNetCore.Components.Web.JSComponentConfigurationExtensions.RegisterForJavaScript%2A> on <xref:Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHostBuilder.RootComponents> in `Program.cs`: + + ```csharp + builder.RootComponents.RegisterForJavaScript<Counter>(identifier: "counter"); + ``` + + > [!NOTE] + > The preceding code example requires a namespace for the app's components (for example, `using BlazorSample.Pages;`) in the `Program.cs` file. + +Load Blazor into the JS app (`blazor.server.js` or `blazor.webassembly.js`). Render the component from JS into a container element using the registered identifier, passing component parameters as needed: + +```javascript +let containerElement = document.getElementById('my-counter'); +await Blazor.rootComponents.add(containerElement, 'counter', { incrementAmount: 10 }); +``` + +## Blazor custom elements + +*Experimental* support is available for building custom elements using the [`Microsoft.AspNetCore.Components.CustomElements` NuGet package](https://www.nuget.org/packages/microsoft.aspnetcore.components.customelements). Custom elements use standard HTML interfaces to implement custom HTML elements. + +> [!WARNING] +> Experimental features are provided for the purpose of exploring feature viability and may not ship in a stable version. + +Register a root component as a custom element: + +* In a Blazor Server app, modify the call to <xref:Microsoft.Extensions.DependencyInjection.ComponentServiceCollectionExtensions.AddServerSideBlazor%2A> in `Program.cs`: + + ```csharp + builder.Services.AddServerSideBlazor(options => + { + options.RootComponents.RegisterAsCustomElement<Counter>("my-counter"); + }); + ``` + + > [!NOTE] + > The preceding code example requires a namespace for the app's components (for example, `using BlazorSample.Pages;`) in the `Program.cs` file. + +* In a Blazor WebAssembly app, call `RegisterAsCustomElement` on <xref:Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHostBuilder.RootComponents> in `Program.cs`: + + ```csharp + builder.RootComponents.RegisterAsCustomElement<Counter>("my-counter"); + ``` + + > [!NOTE] + > The preceding code example requires a namespace for the app's components (for example, `using BlazorSample.Pages;`) in the `Program.cs` file. + +Include the following `<script>` tag in the app's HTML ***before*** the Blazor script tag: + +```html +<script src="/_content/Microsoft.AspNetCore.Components.CustomElements/BlazorCustomElements.js"></script> +``` + +Use the custom element with any web framework. For example, the preceding counter custom element is used in a React app with the following markup: + +```html +<my-counter increment-amount={incrementAmount}></my-counter> +``` + +For a complete example of how to create custom elements with Blazor, see the [Blazor Custom Elements sample project](https://github.com/aspnet/AspLabs/tree/main/src/BlazorCustomElements). + +> [!WARNING] +> The custom elements feature is currently **experimental, unsupported, and subject to change or be removed at any time**. We welcome your feedback on how well this particular approach meets your requirements. + +## Generate Angular and React components + +Generate framework-specific JavaScript (JS) components from Razor components for web frameworks, such as Angular or React. This capability isn't included with .NET 6, but is enabled by the new support for rendering Razor components from JS. The [JS component generation sample on GitHub](https://github.com/aspnet/samples/tree/main/samples/aspnetcore/blazor/JSComponentGeneration) demonstrates how to generate Angular and React components from Razor components. See the GitHub sample app's `README.md` file for additional information. + +> [!WARNING] +> The Angular and React component features are currently **experimental, unsupported, and subject to change or be removed at any time**. We welcome your feedback on how well this particular approach meets your requirements. + +:::moniker-end + <!--Reference links in article--> [1]: <xref:mvc/views/razor#code> [2]: <xref:mvc/views/razor#using> diff --git a/aspnetcore/blazor/components/layouts.md b/aspnetcore/blazor/components/layouts.md index 600d1c530148..717071ef24b3 100644 --- a/aspnetcore/blazor/components/layouts.md +++ b/aspnetcore/blazor/components/layouts.md @@ -12,7 +12,7 @@ uid: blazor/components/layouts This article explains how to create reusable layout components for Blazor apps. -:::moniker range=">= aspnetcore-6.0" +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" Some app elements, such as menus, copyright messages, and company logos, are usually part of app's overall presentation. Placing a copy of the markup for these elements into all of the components of an app isn't efficient. Every time that one of these elements is updated, every component that uses the element must be updated. This approach is costly to maintain and can lead to inconsistent content if an update is missed. *Layouts* solve these problems. @@ -661,3 +661,220 @@ When routable components are integrated into a Razor Pages app, the app's shared * [Blazor samples GitHub repository (`dotnet/blazor-samples`)](https://github.com/dotnet/blazor-samples) :::moniker-end + +:::moniker range=">= aspnetcore-7.0" + +Some app elements, such as menus, copyright messages, and company logos, are usually part of app's overall presentation. Placing a copy of the markup for these elements into all of the components of an app isn't efficient. Every time that one of these elements is updated, every component that uses the element must be updated. This approach is costly to maintain and can lead to inconsistent content if an update is missed. *Layouts* solve these problems. + +A Blazor layout is a Razor component that shares markup with components that reference it. Layouts can use [data binding](xref:blazor/components/data-binding), [dependency injection](xref:blazor/fundamentals/dependency-injection), and other features of components. + +## Layout components + +### Create a layout component + +To create a layout component: + +* Create a Razor component defined by a Razor template or C# code. Layout components based on a Razor template use the `.razor` file extension just like ordinary Razor components. Because layout components are shared across an app's components, they're usually placed in the app's `Shared` folder. However, layouts can be placed in any location accessible to the components that use it. For example, a layout can be placed in the same folder as the components that use it. +* Inherit the component from <xref:Microsoft.AspNetCore.Components.LayoutComponentBase>. The <xref:Microsoft.AspNetCore.Components.LayoutComponentBase> defines a <xref:Microsoft.AspNetCore.Components.LayoutComponentBase.Body> property (<xref:Microsoft.AspNetCore.Components.RenderFragment> type) for the rendered content inside the layout. +* Use the Razor syntax `@Body` to specify the location in the layout markup where the content is rendered. + +> [!NOTE] +> For more information on <xref:Microsoft.AspNetCore.Components.RenderFragment>, see <xref:blazor/components/index#child-content-render-fragments>. + +The following `DoctorWhoLayout` component shows the Razor template of a layout component. The layout inherits <xref:Microsoft.AspNetCore.Components.LayoutComponentBase> and sets the `@Body` between the navigation bar (`<nav>...</nav>`) and the footer (`<footer>...</footer>`). + +`Shared/DoctorWhoLayout.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/layouts/DoctorWhoLayout.razor" highlight="1,13"::: + +### `MainLayout` component + +In an app created from a [Blazor project template](xref:blazor/project-structure), the `MainLayout` component is the app's [default layout](#apply-a-default-layout-to-an-app). + +`Shared/MainLayout.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/layouts/MainLayout.razor"::: + +[Blazor's CSS isolation feature](xref:blazor/components/css-isolation) applies isolated CSS styles to the `MainLayout` component. By convention, the styles are provided by the accompanying stylesheet of the same name, `Shared/MainLayout.razor.css`. The ASP.NET Core framework implementation of the stylesheet is available for inspection in the [ASP.NET Core reference source (dotnet/aspnetcore GitHub repository)](https://github.com/dotnet/aspnetcore/blob/main/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/Shared/MainLayout.razor.css). + +[!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)] + +## Apply a layout + +### Apply a layout to a component + +Use the [`@layout`](xref:mvc/views/razor#layout) Razor directive to apply a layout to a routable Razor component that has an [`@page`](xref:mvc/views/razor#page) directive. The compiler converts `@layout` into a <xref:Microsoft.AspNetCore.Components.LayoutAttribute> and applies the attribute to the component class. + +The content of the following `Episodes` component is inserted into the `DoctorWhoLayout` at the position of `@Body`. + +`Pages/Episodes.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/layouts/Episodes.razor" highlight="2"::: + +The following rendered HTML markup is produced by the preceding `DoctorWhoLayout` and `Episodes` component. Extraneous markup doesn't appear in order to focus on the content provided by the two components involved: + +* The **Doctor Who™ Episode Database** heading (`<h1>...</h1>`) in the header (`<header>...</header>`), navigation bar (`<nav>...</nav>`), and trademark information element (`<div>...</div>`) in the footer (`<footer>...</footer>`) come from the `DoctorWhoLayout` component. +* The **Episodes** heading (`<h2>...</h2>`) and episode list (`<ul>...</ul>`) come from the `Episodes` component. + +```html +<body> + <div id="app"> + <header> + <h1>Doctor Who™ Episode Database</h1> + </header> + + <nav> + <a href="main-list">Main Episode List</a> + <a href="search">Search</a> + <a href="new">Add Episode</a> + </nav> + + <h2>Episodes</h2> + + <ul> + <li>...</li> + <li>...</li> + <li>...</li> + </ul> + + <footer> + Doctor Who is a registered trademark of the BBC. + https://www.doctorwho.tv/ + </footer> + </div> +</body> +``` + +Specifying the layout directly in a component overrides a *default layout*: + +* Set by an `@layout` directive imported from an `_Imports` component (`_Imports.razor`), as described in the following [Apply a layout to a folder of components](#apply-a-layout-to-a-folder-of-components) section. +* Set as the app's default layout, as described in the [Apply a default layout to an app](#apply-a-default-layout-to-an-app) section later in this article. + +### Apply a layout to a folder of components + +Every folder of an app can optionally contain a template file named `_Imports.razor`. The compiler includes the directives specified in the imports file in all of the Razor templates in the same folder and recursively in all of its subfolders. Therefore, an `_Imports.razor` file containing `@layout DoctorWhoLayout` ensures that all of the components in a folder use the `DoctorWhoLayout` component. There's no need to repeatedly add `@layout DoctorWhoLayout` to all of the Razor components (`.razor`) within the folder and subfolders. + +`_Imports.razor`: + +```razor +@layout DoctorWhoLayout +... +``` + +The `_Imports.razor` file is similar to the [_ViewImports.cshtml file for Razor views and pages](xref:mvc/views/layout#importing-shared-directives) but applied specifically to Razor component files. + +Specifying a layout in `_Imports.razor` overrides a layout specified as the router's [default app layout](#apply-a-default-layout-to-an-app), which is described in the following section. + +> [!WARNING] +> Do **not** add a Razor `@layout` directive to the root `_Imports.razor` file, which results in an infinite loop of layouts. To control the default app layout, specify the layout in the `Router` component. For more information, see the following [Apply a default layout to an app](#apply-a-default-layout-to-an-app) section. + +> [!NOTE] +> The [`@layout`](xref:mvc/views/razor#layout) Razor directive only applies a layout to routable Razor components with an [`@page`](xref:mvc/views/razor#page) directive. + +### Apply a default layout to an app + +Specify the default app layout in the `App` component's <xref:Microsoft.AspNetCore.Components.Routing.Router> component. The following example from an app based on a [Blazor project template](xref:blazor/project-structure) sets the default layout to the `MainLayout` component. + +`App.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/layouts/App1.razor" highlight="3"::: + +For more information on the <xref:Microsoft.AspNetCore.Components.Routing.Router> component, see <xref:blazor/fundamentals/routing>. + +Specifying the layout as a default layout in the `Router` component is a useful practice because you can override the layout on a per-component or per-folder basis, as described in the preceding sections of this article. We recommend using the `Router` component to set the app's default layout because it's the most general and flexible approach for using layouts. + +### Apply a layout to arbitrary content (`LayoutView` component) + +To set a layout for arbitrary Razor template content, specify the layout with a <xref:Microsoft.AspNetCore.Components.LayoutView> component. You can use a <xref:Microsoft.AspNetCore.Components.LayoutView> in any Razor component. The following example sets a layout component named `ErrorLayout` for the `MainLayout` component's <xref:Microsoft.AspNetCore.Components.Routing.Router.NotFound> template (`<NotFound>...</NotFound>`). + +`App.razor`: + +```razor +<Router AppAssembly="@typeof(Program).Assembly"> + <Found Context="routeData"> + <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /> + </Found> + <NotFound> + <LayoutView Layout="@typeof(ErrorLayout)"> + <h1>Page not found</h1> + <p>Sorry, there's nothing at this address.</p> + </LayoutView> + </NotFound> +</Router> +``` + +## Nested layouts + +A component can reference a layout that in turn references another layout. For example, nested layouts are used to create a multi-level menu structures. + +The following example shows how to use nested layouts. The `Episodes` component shown in the [Apply a layout to a component](#apply-a-layout-to-a-component) section is the component to display. The component references the `DoctorWhoLayout` component. + +The following `DoctorWhoLayout` component is a modified version of the example shown earlier in this article. The header and footer elements are removed, and the layout references another layout, `ProductionsLayout`. The `Episodes` component is rendered where `@Body` appears in the `DoctorWhoLayout`. + +`Shared/DoctorWhoLayout.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/layouts/DoctorWhoLayout2.razor" highlight="2,12"::: + +The `ProductionsLayout` component contains the top-level layout elements, where the header (`<header>...</header>`) and footer (`<footer>...</footer>`) elements now reside. The `DoctorWhoLayout` with the `Episodes` component is rendered where `@Body` appears. + +`Shared/ProductionsLayout.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Shared/layouts/ProductionsLayout.razor" highlight="13"::: + +The following rendered HTML markup is produced by the preceding nested layout. Extraneous markup doesn't appear in order to focus on the nested content provided by the three components involved: + +* The header (`<header>...</header>`), production navigation bar (`<nav>...</nav>`), and footer (`<footer>...</footer>`) elements and their content come from the `ProductionsLayout` component. +* The **Doctor Who™ Episode Database** heading (`<h1>...</h1>`), episode navigation bar (`<nav>...</nav>`), and trademark information element (`<div>...</div>`) come from the `DoctorWhoLayout` component. +* The **Episodes** heading (`<h2>...</h2>`) and episode list (`<ul>...</ul>`) come from the `Episodes` component. + +```html +<body> + <div id="app"> + <header> + <h1>Productions</h1> + </header> + + <nav> + <a href="main-production-list">Main Production List</a> + <a href="production-search">Search</a> + <a href="new-production">Add Production</a> + </nav> + + <h1>Doctor Who™ Episode Database</h1> + + <nav> + <a href="episode-main-list">Main Episode List</a> + <a href="episode-search">Search</a> + <a href="new-episode">Add Episode</a> + </nav> + + <h2>Episodes</h2> + + <ul> + <li>...</li> + <li>...</li> + <li>...</li> + </ul> + + <div> + Doctor Who is a registered trademark of the BBC. + https://www.doctorwho.tv/ + </div> + + <footer> + Footer of Productions Layout + </footer> + </div> +</body> +``` + +## Share a Razor Pages layout with integrated components + +When routable components are integrated into a Razor Pages app, the app's shared layout can be used with the components. For more information, see <xref:blazor/components/prerendering-and-integration>. + +## Additional resources + +* <xref:mvc/views/layout> +* [Blazor samples GitHub repository (`dotnet/blazor-samples`)](https://github.com/dotnet/blazor-samples) + +:::moniker-end diff --git a/aspnetcore/blazor/components/lifecycle.md b/aspnetcore/blazor/components/lifecycle.md index 384008db9059..ddc05ae1f30b 100644 --- a/aspnetcore/blazor/components/lifecycle.md +++ b/aspnetcore/blazor/components/lifecycle.md @@ -12,7 +12,7 @@ uid: blazor/components/lifecycle This article explains the ASP.NET Core Razor component lifecycle and how to use lifecycle events. -:::moniker range=">= aspnetcore-6.0" +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" The Razor component processes Razor component lifecycle events in a set of synchronous and asynchronous lifecycle methods. The lifecycle methods can be overridden to perform additional operations in components during component initialization and rendering. @@ -1380,3 +1380,461 @@ In the following example: The component lifecycle events covered in this article operate separately from [Blazor Server's reconnection event handlers](xref:blazor/fundamentals/signalr#reflect-the-connection-state-in-the-ui-blazor-server). When a Blazor Server app loses its SignalR connection to the client, only UI updates are interrupted. UI updates are resumed when the connection is re-established. For more information on circuit handler events and configuration, see <xref:blazor/fundamentals/signalr>. :::moniker-end + +:::moniker range=">= aspnetcore-7.0" + +The Razor component processes Razor component lifecycle events in a set of synchronous and asynchronous lifecycle methods. The lifecycle methods can be overridden to perform additional operations in components during component initialization and rendering. + +## Lifecycle events + +The following diagrams illustrate Razor component lifecycle events. The C# methods associated with the lifecycle events are defined with examples in the following sections of this article. + +Component lifecycle events: + +1. If the component is rendering for the first time on a request: + * Create the component's instance. + * Perform property injection. Run [`SetParametersAsync`](#when-parameters-are-set-setparametersasync). + * Call [`OnInitialized{Async}`](#component-initialization-oninitializedasync). If an incomplete <xref:System.Threading.Tasks.Task> is returned, the <xref:System.Threading.Tasks.Task> is awaited and then the component is rerendered. +1. Call [`OnParametersSet{Async}`](#after-parameters-are-set-onparameterssetasync). If an incomplete <xref:System.Threading.Tasks.Task> is returned, the <xref:System.Threading.Tasks.Task> is awaited and then the component is rerendered. +1. Render for all synchronous work and complete <xref:System.Threading.Tasks.Task>s. + +> [!NOTE] +> Asynchronous actions performed in lifecycle events might not have completed before a component is rendered. For more information, see the [Handle incomplete async actions at render](#handle-incomplete-async-actions-at-render) section later in this article. + +![Component lifecycle events of a Razor component in Blazor](~/blazor/components/lifecycle/_static/lifecycle1.png) + +Document Object Model (DOM) event processing: + +1. The event handler is run. +1. If an incomplete <xref:System.Threading.Tasks.Task> is returned, the <xref:System.Threading.Tasks.Task> is awaited and then the component is rerendered. +1. Render for all synchronous work and complete <xref:System.Threading.Tasks.Task>s. + +![Document Object Model (DOM) event processing](~/blazor/components/lifecycle/_static/lifecycle2.png) + +The `Render` lifecycle: + +1. Avoid further rendering operations on the component: + * After the first render. + * When [`ShouldRender`](xref:blazor/components/rendering#suppress-ui-refreshing-shouldrender) is `false`. +1. Build the render tree diff (difference) and render the component. +1. Await the DOM to update. +1. Call [`OnAfterRender{Async}`](#after-component-render-onafterrenderasync). + +![Render lifecycle](~/blazor/components/lifecycle/_static/lifecycle3.png) + +Developer calls to [`StateHasChanged`](#state-changes-statehaschanged) result in a render. For more information, see <xref:blazor/components/rendering>. + +## When parameters are set (`SetParametersAsync`) + +<xref:Microsoft.AspNetCore.Components.ComponentBase.SetParametersAsync%2A> sets parameters supplied by the component's parent in the render tree or from route parameters. + +The method's <xref:Microsoft.AspNetCore.Components.ParameterView> parameter contains the set of [component parameter](xref:blazor/components/index#component-parameters) values for the component each time <xref:Microsoft.AspNetCore.Components.ComponentBase.SetParametersAsync%2A> is called. By overriding the <xref:Microsoft.AspNetCore.Components.ComponentBase.SetParametersAsync%2A> method, developer code can interact directly with <xref:Microsoft.AspNetCore.Components.ParameterView>'s parameters. + +The default implementation of <xref:Microsoft.AspNetCore.Components.ComponentBase.SetParametersAsync%2A> sets the value of each property with the [`[Parameter]`](xref:Microsoft.AspNetCore.Components.ParameterAttribute) or [`[CascadingParameter]` attribute](xref:Microsoft.AspNetCore.Components.CascadingParameterAttribute) that has a corresponding value in the <xref:Microsoft.AspNetCore.Components.ParameterView>. Parameters that don't have a corresponding value in <xref:Microsoft.AspNetCore.Components.ParameterView> are left unchanged. + +If [`base.SetParametersAsync`](xref:Microsoft.AspNetCore.Components.ComponentBase.SetParametersAsync%2A) isn't invoked, developer code can interpret the incoming parameters' values in any way required. For example, there's no requirement to assign the incoming parameters to the properties of the class. + +If event handlers are provided in developer code, unhook them on disposal. For more information, see the [Component disposal with `IDisposable` `IAsyncDisposable`](#component-disposal-with-idisposable-and-iasyncdisposable) section. + +In the following example, <xref:Microsoft.AspNetCore.Components.ParameterView.TryGetValue%2A?displayProperty=nameWithType> assigns the `Param` parameter's value to `value` if parsing a route parameter for `Param` is successful. When `value` isn't `null`, the value is displayed by the component. + +Although [route parameter matching is case insensitive](xref:blazor/fundamentals/routing#route-parameters), <xref:Microsoft.AspNetCore.Components.ParameterView.TryGetValue%2A> only matches case sensitive parameter names in the route template. The following example requires the use of `/{Param?}` in the route template in order to get the value with <xref:Microsoft.AspNetCore.Components.ParameterView.TryGetValue%2A>, not `/{param?}`. If `/{param?}` is used in this scenario, <xref:Microsoft.AspNetCore.Components.ParameterView.TryGetValue%2A> returns `false` and `message` isn't set to either `message` string. + +`Pages/SetParamsAsync.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/lifecycle/SetParamsAsync.razor"::: + +## Component initialization (`OnInitialized{Async}`) + +<xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitialized%2A> and <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> are invoked when the component is initialized after having received its initial parameters in <xref:Microsoft.AspNetCore.Components.ComponentBase.SetParametersAsync%2A>. + +For a synchronous operation, override <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitialized%2A>: + +`Pages/OnInit.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/lifecycle/OnInit.razor" highlight="8"::: + +To perform an asynchronous operation, override <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> and use the [`await`](/dotnet/csharp/language-reference/operators/await) operator: + +```csharp +protected override async Task OnInitializedAsync() +{ + await ... +} +``` + +Blazor apps that prerender their content on the server call <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> *twice*: + +* Once when the component is initially rendered statically as part of the page. +* A second time when the browser renders the component. + +To prevent developer code in <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> from running twice when prerendering, see the [Stateful reconnection after prerendering](#stateful-reconnection-after-prerendering) section. Although the content in the section focuses on Blazor Server and stateful SignalR *reconnection*, the scenario for prerendering in hosted Blazor WebAssembly apps (<xref:Microsoft.AspNetCore.Mvc.Rendering.RenderMode.WebAssemblyPrerendered>) involves similar conditions and approaches to prevent executing developer code twice. To preserve state during the execution of initialization code while prerendering, see <xref:blazor/components/prerendering-and-integration#persist-prerendered-state>. + +While a Blazor app is prerendering, certain actions, such as calling into JavaScript (JS interop), aren't possible. Components may need to render differently when prerendered. For more information, see the [Detect when the app is prerendering](#detect-when-the-app-is-prerendering) section. + +If event handlers are provided in developer code, unhook them on disposal. For more information, see the [Component disposal with `IDisposable` `IAsyncDisposable`](#component-disposal-with-idisposable-and-iasyncdisposable) section. + +## After parameters are set (`OnParametersSet{Async}`) + +<xref:Microsoft.AspNetCore.Components.ComponentBase.OnParametersSet%2A> or <xref:Microsoft.AspNetCore.Components.ComponentBase.OnParametersSetAsync%2A> are called: + +* After the component is initialized in <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitialized%2A> or <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A>. + +* When the parent component rerenders and supplies: + + * Known or primitive immutable types when at least one parameter has changed. + * Complex-typed parameters. The framework can't know whether the values of a complex-typed parameter have mutated internally, so the framework always treats the parameter set as changed when one or more complex-typed parameters are present. + + For more information on rendering conventions, see <xref:blazor/components/rendering#rendering-conventions-for-componentbase>. + +For the following example component, navigate to the component's page at a URL: + +* With a start date that's received by `StartDate`: `/on-parameters-set/2021-03-19` +* Without a start date, where `StartDate` is assigned a value of the current local time: `/on-parameters-set` + +`Pages/OnParamsSet.razor`: + +> [!NOTE] +> In a component route, it isn't possible to both constrain a <xref:System.DateTime> parameter with the [route constraint `datetime`](xref:blazor/fundamentals/routing#route-constraints) and [make the parameter optional](xref:blazor/fundamentals/routing#route-parameters). Therefore, the following `OnParamsSet` component uses two [`@page`](xref:mvc/views/razor#page) directives to handle routing with and without a supplied date segment in the URL. + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/lifecycle/OnParamsSet.razor"::: + +Asynchronous work when applying parameters and property values must occur during the <xref:Microsoft.AspNetCore.Components.ComponentBase.OnParametersSetAsync%2A> lifecycle event: + +```csharp +protected override async Task OnParametersSetAsync() +{ + await ... +} +``` + +If event handlers are provided in developer code, unhook them on disposal. For more information, see the [Component disposal with `IDisposable` `IAsyncDisposable`](#component-disposal-with-idisposable-and-iasyncdisposable) section. + +For more information on route parameters and constraints, see <xref:blazor/fundamentals/routing>. + +## After component render (`OnAfterRender{Async}`) + +<xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRender%2A> and <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRenderAsync%2A> are called after a component has finished rendering. Element and component references are populated at this point. Use this stage to perform additional initialization steps with the rendered content, such as JS interop calls that interact with the rendered DOM elements. + +The `firstRender` parameter for <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRender%2A> and <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRenderAsync%2A>: + +* Is set to `true` the first time that the component instance is rendered. +* Can be used to ensure that initialization work is only performed once. + +`Pages/AfterRender.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/lifecycle/AfterRender.razor"::: + +Asynchronous work immediately after rendering must occur during the <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRenderAsync%2A> lifecycle event: + +```csharp +protected override async Task OnAfterRenderAsync(bool firstRender) +{ + if (firstRender) + { + await ... + } +} +``` + +Even if you return a <xref:System.Threading.Tasks.Task> from <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRenderAsync%2A>, the framework doesn't schedule a further render cycle for your component once that task completes. This is to avoid an infinite render loop. This is different from the other lifecycle methods, which schedule a further render cycle once a returned <xref:System.Threading.Tasks.Task> completes. + +<xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRender%2A> and <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRenderAsync%2A> *aren't called during the prerendering process on the server*. The methods are called when the component is rendered interactively after prerendering. When the app prerenders: + +1. The component executes on the server to produce some static HTML markup in the HTTP response. During this phase, <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRender%2A> and <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRenderAsync%2A> aren't called. +1. When the Blazor script (`blazor.webassembly.js` or `blazor.server.js`) start in the browser, the component is restarted in an interactive rendering mode. After a component is restarted, <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRender%2A> and <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRenderAsync%2A> **are** called because the app isn't in the prerendering phase any longer. + +If event handlers are provided in developer code, unhook them on disposal. For more information, see the [Component disposal with `IDisposable` `IAsyncDisposable`](#component-disposal-with-idisposable-and-iasyncdisposable) section. + +## State changes (`StateHasChanged`) + +<xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> notifies the component that its state has changed. When applicable, calling <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> causes the component to be rerendered. + +<xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> is called automatically for <xref:Microsoft.AspNetCore.Components.EventCallback> methods. For more information on event callbacks, see <xref:blazor/components/event-handling#eventcallback>. + +For more information on component rendering and when to call <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A>, including when to invoke it with <xref:Microsoft.AspNetCore.Components.ComponentBase.InvokeAsync%2A?displayProperty=nameWithType>, see <xref:blazor/components/rendering>. + +## Handle incomplete async actions at render + +Asynchronous actions performed in lifecycle events might not have completed before the component is rendered. Objects might be `null` or incompletely populated with data while the lifecycle method is executing. Provide rendering logic to confirm that objects are initialized. Render placeholder UI elements (for example, a loading message) while objects are `null`. + +In the `FetchData` component of the Blazor templates, <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> is overridden to asynchronously receive forecast data (`forecasts`). When `forecasts` is `null`, a loading message is displayed to the user. After the `Task` returned by <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> completes, the component is rerendered with the updated state. + +`Pages/FetchData.razor` in the Blazor Server template: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_Server/Pages/lifecycle/FetchData.razor" highlight="9,21,25"::: + +## Handle errors + +For information on handling errors during lifecycle method execution, see <xref:blazor/fundamentals/handle-errors>. + +## Stateful reconnection after prerendering + +In a Blazor Server app when <xref:Microsoft.AspNetCore.Mvc.TagHelpers.ComponentTagHelper.RenderMode> is <xref:Microsoft.AspNetCore.Mvc.Rendering.RenderMode.ServerPrerendered>, the component is initially rendered statically as part of the page. Once the browser establishes a SignalR connection back to the server, the component is rendered *again* and interactive. If the [`OnInitialized{Async}`](#component-initialization-oninitializedasync) lifecycle method for initializing the component is present, the method is executed *twice*: + +* When the component is prerendered statically. +* After the server connection has been established. + +This can result in a noticeable change in the data displayed in the UI when the component is finally rendered. To avoid this double-rendering behavior in a Blazor Server app, pass in an identifier to cache the state during prerendering and to retrieve the state after prerendering. + +The following code demonstrates an updated `WeatherForecastService` in a template-based Blazor Server app that avoids the double rendering. In the following example, the awaited <xref:System.Threading.Tasks.Task.Delay%2A> (`await Task.Delay(...)`) simulates a short delay before returning data from the `GetForecastAsync` method. + +`WeatherForecastService.cs`: + +:::code language="csharp" source="~/../blazor-samples/7.0/BlazorSample_Server/lifecycle/WeatherForecastService.cs"::: + +For more information on the <xref:Microsoft.AspNetCore.Mvc.TagHelpers.ComponentTagHelper.RenderMode>, see <xref:blazor/fundamentals/signalr#render-mode-blazor-server>. + +Although the content in this section focuses on Blazor Server and stateful SignalR *reconnection*, the scenario for prerendering in hosted Blazor WebAssembly apps (<xref:Microsoft.AspNetCore.Mvc.Rendering.RenderMode.WebAssemblyPrerendered>) involves similar conditions and approaches to prevent executing developer code twice. To preserve state during the execution of initialization code while prerendering, see <xref:blazor/components/prerendering-and-integration#persist-prerendered-state>. + +## Detect when the app is prerendering + +[!INCLUDE[](~/blazor/includes/prerendering.md)] + +## Component disposal with `IDisposable` and `IAsyncDisposable` + +If a component implements <xref:System.IDisposable>, <xref:System.IAsyncDisposable>, or both, the framework calls for unmanaged resource disposal when the component is removed from the UI. Disposal can occur at any time, including during [component initialization](#component-initialization-oninitializedasync). + +Components shouldn't need to implement <xref:System.IDisposable> and <xref:System.IAsyncDisposable> simultaneously. If both are implemented, the framework only executes the asynchronous overload. + +Developer code must ensure that <xref:System.IAsyncDisposable> implementations don't take a long time to complete. + +### Synchronous `IDisposable` + +For synchronous disposal tasks, use <xref:System.IDisposable.Dispose%2A?displayProperty=nameWithType>. + +The following component: + +* Implements <xref:System.IDisposable> with the [`@implements`](xref:mvc/views/razor#implements) Razor directive. +* Disposes of `obj`, which is an unmanaged type that implements <xref:System.IDisposable>. +* A null check is performed because `obj` is created in a lifecycle method (not shown). + +```razor +@implements IDisposable + +... + +@code { + ... + + public void Dispose() + { + obj?.Dispose(); + } +} +``` + +If a single object requires disposal, a lambda can be used to dispose of the object when <xref:System.IDisposable.Dispose%2A> is called. The following example appears in the <xref:blazor/components/rendering#receiving-a-call-from-something-external-to-the-blazor-rendering-and-event-handling-system> article and demonstrates the use of a lambda expression for the disposal of a <xref:System.Timers.Timer>. + +`Pages/CounterWithTimerDisposal1.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/lifecycle/CounterWithTimerDisposal1.razor" highlight="3,11,28"::: + +> [!NOTE] +> In the preceding example, the call to <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> is wrapped by a call to <xref:Microsoft.AspNetCore.Components.ComponentBase.InvokeAsync%2A?displayProperty=nameWithType> because the callback is invoked outside of Blazor's synchronization context. For more information, see <xref:blazor/components/rendering#receiving-a-call-from-something-external-to-the-blazor-rendering-and-event-handling-system>. + +If the object is created in a lifecycle method, such as [`OnInitialized`/`OnInitializedAsync`](#component-initialization-oninitializedasync), check for `null` before calling `Dispose`. + +`Pages/CounterWithTimerDisposal2.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/lifecycle/CounterWithTimerDisposal2.razor" highlight="15,29"::: + +For more information, see: + +* [Cleaning up unmanaged resources (.NET documentation)](/dotnet/standard/garbage-collection/unmanaged) +* [Null-conditional operators ?. and ?[]](/dotnet/csharp/language-reference/operators/member-access-operators#null-conditional-operators--and-) + +### Asynchronous `IAsyncDisposable` + +For asynchronous disposal tasks, use <xref:System.IAsyncDisposable.DisposeAsync%2A?displayProperty=nameWithType>. + +The following component: + +* Implements <xref:System.IAsyncDisposable> with the [`@implements`](xref:mvc/views/razor#implements) Razor directive. +* Disposes of `obj`, which is an unmanaged type that implements <xref:System.IAsyncDisposable>. +* A null check is performed because `obj` is created in a lifecycle method (not shown). + +```razor +@implements IAsyncDisposable + +... + +@code { + ... + + public async ValueTask DisposeAsync() + { + if (obj is not null) + { + await obj.DisposeAsync(); + } + } +} +``` + +For more information, see: + +* [Cleaning up unmanaged resources (.NET documentation)](/dotnet/standard/garbage-collection/unmanaged) +* [Null-conditional operators ?. and ?[]](/dotnet/csharp/language-reference/operators/member-access-operators#null-conditional-operators--and-) + +### Assignment of `null` to disposed objects + +Usually, there's no need to assign `null` to disposed objects after calling <xref:System.IDisposable.Dispose%2A>/<xref:System.IAsyncDisposable.DisposeAsync%2A>. Rare cases for assigning `null` include the following: + +* If the object's type is poorly implemented and doesn't tolerate repeat calls to <xref:System.IDisposable.Dispose%2A>/<xref:System.IAsyncDisposable.DisposeAsync%2A>, assign `null` after disposal to gracefully skip further calls to <xref:System.IDisposable.Dispose%2A>/<xref:System.IAsyncDisposable.DisposeAsync%2A>. +* If a long-lived process continues to hold a reference to a disposed object, assigning `null` allows the [garbage collector](/dotnet/standard/garbage-collection/fundamentals) to free the object in spite of the long-lived process holding a reference to it. + +These are unusual scenarios. For objects that are implemented correctly and behave normally, there's no point in assigning `null` to disposed objects. In the rare cases where an object must be assigned `null`, we recommend documenting the reason and seeking a solution that prevents the need to assign `null`. + +### `StateHasChanged` + +> [!NOTE] +> Calling <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> in `Dispose` isn't supported. <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> might be invoked as part of tearing down the renderer, so requesting UI updates at that point isn't supported. + +### Event handlers + +Always unsubscribe event handlers from .NET events. The following [Blazor form](xref:blazor/forms-validation) examples show how to unsubscribe an event handler in the `Dispose` method: + +* Private field and lambda approach + + ```razor + @implements IDisposable + + <EditForm EditContext="@editContext"> + ... + <button type="submit" disabled="@formInvalid">Submit</button> + </EditForm> + + @code { + ... + + private EventHandler<FieldChangedEventArgs>? fieldChanged; + + protected override void OnInitialized() + { + editContext = new(model); + + fieldChanged = (_, __) => + { + ... + }; + + editContext.OnFieldChanged += fieldChanged; + } + + public void Dispose() + { + editContext.OnFieldChanged -= fieldChanged; + } + } + ``` + +* Private method approach + + ```razor + @implements IDisposable + + <EditForm EditContext="@editContext"> + ... + <button type="submit" disabled="@formInvalid">Submit</button> + </EditForm> + + @code { + ... + + protected override void OnInitialized() + { + editContext = new(model); + editContext.OnFieldChanged += HandleFieldChanged; + } + + private void HandleFieldChanged(object sender, FieldChangedEventArgs e) + { + ... + } + + public void Dispose() + { + editContext.OnFieldChanged -= HandleFieldChanged; + } + } + ``` + +For more information, see the [Component disposal with `IDisposable` and `IAsyncDisposable`](#component-disposal-with-idisposable-and-iasyncdisposable) section. + +### Anonymous functions, methods, and expressions + +When [anonymous functions](/dotnet/csharp/programming-guide/statements-expressions-operators/anonymous-functions), methods, or expressions, are used, it isn't necessary to implement <xref:System.IDisposable> and unsubscribe delegates. However, failing to unsubscribe a delegate is a problem **when the object exposing the event outlives the lifetime of the component registering the delegate**. When this occurs, a memory leak results because the registered delegate keeps the original object alive. Therefore, only use the following approaches when you know that the event delegate disposes quickly. When in doubt about the lifetime of objects that require disposal, subscribe a delegate method and properly dispose the delegate as the earlier examples show. + +* Anonymous lambda method approach (explicit disposal not required): + + ```csharp + private void HandleFieldChanged(object sender, FieldChangedEventArgs e) + { + formInvalid = !editContext.Validate(); + StateHasChanged(); + } + + protected override void OnInitialized() + { + editContext = new(starship); + editContext.OnFieldChanged += (s, e) => HandleFieldChanged((editContext)s, e); + } + ``` + +* Anonymous lambda expression approach (explicit disposal not required): + + ```csharp + private ValidationMessageStore? messageStore; + + [CascadingParameter] + private EditContext? CurrentEditContext { get; set; } + + protected override void OnInitialized() + { + ... + + messageStore = new(CurrentEditContext); + + CurrentEditContext.OnValidationRequested += (s, e) => messageStore.Clear(); + CurrentEditContext.OnFieldChanged += (s, e) => + messageStore.Clear(e.FieldIdentifier); + } + ``` + + The full example of the preceding code with anonymous lambda expressions appears in the <xref:blazor/forms-validation#validator-components> article. + +For more information, see [Cleaning up unmanaged resources](/dotnet/standard/garbage-collection/unmanaged) and the topics that follow it on implementing the `Dispose` and `DisposeAsync` methods. + +## Cancelable background work + +Components often perform long-running background work, such as making network calls (<xref:System.Net.Http.HttpClient>) and interacting with databases. It's desirable to stop the background work to conserve system resources in several situations. For example, background asynchronous operations don't automatically stop when a user navigates away from a component. + +Other reasons why background work items might require cancellation include: + +* An executing background task was started with faulty input data or processing parameters. +* The current set of executing background work items must be replaced with a new set of work items. +* The priority of currently executing tasks must be changed. +* The app must be shut down for server redeployment. +* Server resources become limited, necessitating the rescheduling of background work items. + +To implement a cancelable background work pattern in a component: + +* Use a <xref:System.Threading.CancellationTokenSource> and <xref:System.Threading.CancellationToken>. +* On [disposal of the component](#component-disposal-with-idisposable-and-iasyncdisposable) and at any point cancellation is desired by manually canceling the token, call [`CancellationTokenSource.Cancel`](xref:System.Threading.CancellationTokenSource.Cancel%2A) to signal that the background work should be cancelled. +* After the asynchronous call returns, call <xref:System.Threading.CancellationToken.ThrowIfCancellationRequested%2A> on the token. + +In the following example: + +* `await Task.Delay(5000, cts.Token);` represents long-running asynchronous background work. +* `BackgroundResourceMethod` represents a long-running background method that shouldn't start if the `Resource` is disposed before the method is called. + +`Pages/BackgroundWork.razor`: + +:::code language="razor" source="~/../blazor-samples/7.0/BlazorSample_WebAssembly/Pages/lifecycle/BackgroundWork.razor"::: + +## Blazor Server reconnection events + +The component lifecycle events covered in this article operate separately from [Blazor Server's reconnection event handlers](xref:blazor/fundamentals/signalr#reflect-the-connection-state-in-the-ui-blazor-server). When a Blazor Server app loses its SignalR connection to the client, only UI updates are interrupted. UI updates are resumed when the connection is re-established. For more information on circuit handler events and configuration, see <xref:blazor/fundamentals/signalr>. + +:::moniker-end diff --git a/aspnetcore/blazor/components/prerendering-and-integration.md b/aspnetcore/blazor/components/prerendering-and-integration.md index f17201161c30..ff4eb507c3fb 100644 --- a/aspnetcore/blazor/components/prerendering-and-integration.md +++ b/aspnetcore/blazor/components/prerendering-and-integration.md @@ -13,7 +13,7 @@ zone_pivot_groups: blazor-hosting-models This article explains Razor component integration scenarios for Blazor apps, including prerendering of Razor components on the server. -:::moniker range=">= aspnetcore-6.0" +:::moniker range=">= aspnetcore-6.0 < aspnetcore-7.0" :::zone pivot="webassembly" @@ -304,14 +304,10 @@ Import static assets to the **`Server`** project from the donor project's `wwwro * `wwwroot/js` folder and contents * `wwwroot/lib` folder and contents -If the donor project is created from an ASP.NET Core project template and the files aren't modified, you can copy the entire `wwwroot` folder from the donor project into the **`Server`** project and remove the `favicon.ico` icon file. +If the donor project is created from an ASP.NET Core project template and the files aren't modified, you can copy the entire `wwwroot` folder from the donor project into the **`Server`** project and remove the :::no-loc text="favicon"::: icon file. > [!WARNING] -> Avoid placing the same file (for example, `favicon.ico`) into both the **`Client`** and **`Server`** `wwwroot` folders. If the same file is present in both folders an exception is thrown because the static asset in each folder shares the same web root path: -> -> > The static web asset '...\favicon.ico' has a conflicting web root path '/wwwroot/favicon.ico' with the project file 'wwwroot\favicon.ico'. -> -> Therefore, host a static asset in either `wwwroot` folder, not both. +> Avoid placing the static asset into both the **`Client`** and **`Server`** `wwwroot` folders. If the same file is present in both folders, an exception is thrown because the static asset in each folder shares the same web root path. Therefore, host a static asset in either `wwwroot` folder, not both. After adopting the preceding configuration, embed Razor components into pages or views of the **`Server`** project. Use the guidance in the following sections of this article: @@ -1115,14 +1111,10 @@ Import static assets to the **`Server`** project from the donor project's `wwwro * `wwwroot/js` folder and contents * `wwwroot/lib` folder and contents -If the donor project is created from an ASP.NET Core project template and the files aren't modified, you can copy the entire `wwwroot` folder from the donor project into the **`Server`** project and remove the `favicon.ico` icon file. +If the donor project is created from an ASP.NET Core project template and the files aren't modified, you can copy the entire `wwwroot` folder from the donor project into the **`Server`** project and remove the :::no-loc text="favicon"::: icon file. -> [!NOTE] -> If the **`Client`** and **`Server`** projects contain the same static asset in their `wwwroot` folders (for example, `favicon.ico`), an exception is thrown because the static asset in each folder shares the same web root path: -> -> > The static web asset '...\favicon.ico' has a conflicting web root path '/wwwroot/favicon.ico' with the project file 'wwwroot\favicon.ico'. -> -> Therefore, host a static asset in either `wwwroot` folder, not both. +> [!WARNING] +> Avoid placing the static asset into both the **`Client`** and **`Server`** `wwwroot` folders. If the same file is present in both folders, an exception is thrown because the static asset in each folder shares the same web root path. Therefore, host a static asset in either `wwwroot` folder, not both. ## Render components in a page or view with the Component Tag Helper @@ -2074,3 +2066,941 @@ For more information, see <xref:blazor/components/index#namespaces>. :::zone-end :::moniker-end + +:::moniker range=">= aspnetcore-7.0" + +:::zone pivot="webassembly" + +Razor components can be integrated into Razor Pages and MVC apps in a hosted Blazor WebAssembly [solution](xref:blazor/tooling#visual-studio-solution-file-sln). When the page or view is rendered, components can be prerendered at the same time. + +Prerendering can improve [Search Engine Optimization (SEO)](https://developer.mozilla.org/docs/Glossary/SEO) by rendering content for the initial HTTP response that search engines can use to calculate page rank. + +## Solution configuration + +### Prerendering configuration + +To set up prerendering for a hosted Blazor WebAssembly app: + +1. Host the Blazor WebAssembly app in an ASP.NET Core app. A standalone Blazor WebAssembly app can be added to an ASP.NET Core solution, or you can use a hosted Blazor WebAssembly app created from the [Blazor WebAssembly project template](xref:blazor/tooling) with the hosted option: + + * Visual Studio: In the **Additional information** dialog, select the **ASP.NET Core hosted** checkbox when creating the Blazor WebAssembly app. In this article's examples, the solution is named `BlazorHosted`. + * Visual Studio Code/.NET CLI command shell: `dotnet new blazorwasm -ho` (use the `-ho|--hosted` option). Use the `-o|--output {LOCATION}` option to create a folder for the solution and set the solution's project namespaces. In this article's examples, the solution is named `BlazorHosted` (`dotnet new blazorwasm -ho -o BlazorHosted`). + + For the examples in this article, the client project's namespace is `BlazorHosted.Client`, and the server project's namespace is `BlazorHosted.Server`. + +1. **Delete** the `wwwroot/index.html` file from the Blazor WebAssembly **`Client`** project. + +1. In the **`Client`** project, **delete** the following lines in `Program.cs`: + + ```diff + - builder.RootComponents.Add<App>("#app"); + - builder.RootComponents.Add<HeadOutlet>("head::after"); + ``` + +1. Add `_Host.cshtml` file to the **`Server`** project's `Pages` folder. You can obtain the files from a project created from the Blazor Server template using Visual Studio or using the .NET CLI with the `dotnet new blazorserver -o BlazorServer` command in a command shell (the `-o BlazorServer` option creates a folder for the project). After placing the files into the **`Server`** project's `Pages` folder, make the following changes to the files. + + Make the following changes to the `_Host.cshtml` file: + + * Update the `Pages` namespace at the top of the file to match the namespace of the **`Server`** app's pages. The `{APP NAMESPACE}` placeholder in the following example represents the namespace of the donor app's pages that provided the `_Host.cshtml` file: + + Delete: + + ```diff + - @namespace {APP NAMESPACE}.Pages + ``` + + Add: + + ```razor + @namespace BlazorHosted.Server.Pages + ``` + + * Add an [`@using`](xref:mvc/views/razor#using) directive for the **`Client`** project at the top of the file: + + ```razor + @using BlazorHosted.Client + ``` + + * Update the stylesheet links to point to the WebAssembly project's stylesheets. In the following example, the client project's namespace is `BlazorHosted.Client`. The `{APP NAMESPACE}` placeholder represents the namespace of the donor app that provided the `_Host.cshtml` file. Update the Component Tag Helper (`<component>` tag) for the `HeadOutlet` component to prerender the component. + + Delete: + + ```diff + - <link href="css/site.css" rel="stylesheet" /> + - <link href="{APP NAMESPACE}.styles.css" rel="stylesheet" /> + - <component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" /> + ``` + + Add: + + ```razor + <link href="css/app.css" rel="stylesheet" /> + <link href="BlazorHosted.Client.styles.css" rel="stylesheet" /> + <component type="typeof(HeadOutlet)" render-mode="WebAssemblyPrerendered" /> + ``` + + > [!NOTE] + > Leave the `<link>` element that requests the Bootstrap stylesheet (`css/bootstrap/bootstrap.min.css`) in place. + + * Update the Blazor script source to use the client-side Blazor WebAssembly script: + + Delete: + + ```diff + - <script src="_framework/blazor.server.js"></script> + ``` + + Add: + + ```html + <script src="_framework/blazor.webassembly.js"></script> + ``` + + In the `_Host.cshtml` file: + + * Change the `Pages` namespace to that of the **`Client`** project. The `{APP NAMESPACE}` placeholder represents the namespace of the donor app's pages that provided the `_Host.cshtml` file: + + Delete: + + ```diff + - @namespace {APP NAMESPACE}.Pages + ``` + + Add: + + ```razor + @namespace BlazorHosted.Client + ``` + + * Update the `render-mode` of the [Component Tag Helper](xref:mvc/views/tag-helpers/builtin-th/component-tag-helper) to prerender the root `App` component with <xref:Microsoft.AspNetCore.Mvc.Rendering.RenderMode.WebAssemblyPrerendered>: + + Delete: + + ```diff + - <component type="typeof(App)" render-mode="ServerPrerendered" /> + ``` + + Add: + + ```razor + <component type="typeof(App)" render-mode="WebAssemblyPrerendered" /> + ``` + + > [!IMPORTANT] + > Prerendering isn't supported for authentication endpoints (`/authentication/` path segment). For more information, see <xref:blazor/security/webassembly/additional-scenarios#support-prerendering-with-authentication>. + +1. In endpoint mapping of the **`Server`** project in `Program.cs`, change the fallback from the `index.html` file to the `_Host.cshtml` page: + + Delete: + + ```diff + - app.MapFallbackToFile("index.html"); + ``` + + Add: + + ```csharp + app.MapFallbackToPage("/_Host"); + ``` + +1. If the **`Client`** and **`Server`** projects use one or more common services during prerendering, factor the service registrations into a method that can be called from both projects. For more information, see <xref:blazor/fundamentals/dependency-injection#register-common-services-in-a-hosted-blazor-webassembly-solution>. + +1. Run the **`Server`** project. The hosted Blazor WebAssembly app is prerendered by the **`Server`** project for clients. + +### Configuration for embedding Razor components into pages and views + +The following sections and examples for embedding Razor components from the **`Client`** Blazor WebAssembly app into pages and views of the server app require additional configuration. + +The **`Server`** project must have the following files and folders. + +Razor Pages: + +* `Pages/Shared/_Layout.cshtml` +* `Pages/Shared/_Layout.cshtml.css` +* `Pages/_ViewImports.cshtml` +* `Pages/_ViewStart.cshtml` + +MVC: + +* `Views/Shared/_Layout.cshtml` +* `Views/Shared/_Layout.cshtml.css` +* `Views/_ViewImports.cshtml` +* `Views/_ViewStart.cshtml` + +The preceding files can be obtained by generating an app from the ASP.NET Core project templates using: + +* Visual Studio's new project creation tools. +* Opening a command shell and executing `dotnet new webapp -o {APP NAME}` (Razor Pages) or `dotnet new mvc -o {APP NAME}` (MVC). The option `-o|--output` with a value for the `{APP NAME}` placeholder provides a name for the app and creates a folder for the app. + +Update the namespaces in the imported `_ViewImports.cshtml` file to match those in use by the **`Server`** project receiving the files. + +`Pages/_ViewImports.cshtml` (Razor Pages): + +```razor +@using BlazorHosted.Server +@namespace BlazorHosted.Server.Pages +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +``` + +`Views/_ViewImports.cshtml` (MVC): + +```razor +@using BlazorHosted.Server +@using BlazorHosted.Server.Models +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +``` + +Update the imported layout file, which is `Pages/Shared/_Layout.cshtml` for Razor Pages or `Views/Shared/_Layout.cshtml` for MVC. + +First, delete the title and the stylesheet from the donor project, which is `RPDonor.styles.css` in the following example. The `{APP NAME}` placeholder represents the donor project's app name. + +```diff +- <title>@ViewData["Title"] - {APP NAME} +- +``` + +Include the **`Client`** project's styles in the layout file. In the following example, the **`Client`** project's namespace is `BlazorHosted.Client`. The `` element can be updated at the same time. + +Place the following lines in the `<head>` content of the layout file: + +```html +<title>@ViewData["Title"] - BlazorHosted + + + +``` + +The imported layout contains two `Home` (`Index` page) and `Privacy` navigation links. To make the `Home` links point to the hosted Blazor WebAssembly app, change the hyperlinks: + +```diff +- {APP NAME} ++ BlazorHosted +``` + +```diff +- Home ++ Home +``` + +In an MVC layout file: + +```diff +- {APP NAME} ++ BlazorHosted +``` + +```diff +- Home ++ Home +``` + +Update the `