diff --git a/aspnetcore/blazor/components/cascading-values-and-parameters.md b/aspnetcore/blazor/components/cascading-values-and-parameters.md index 443814c3e863..cbe0baf18366 100644 --- a/aspnetcore/blazor/components/cascading-values-and-parameters.md +++ b/aspnetcore/blazor/components/cascading-values-and-parameters.md @@ -517,4 +517,4 @@ The following `ExampleTabSet` component uses the `TabSet` component, which conta ## Additional resources * [Generic type support: Explicit generic types based on ancestor components](xref:blazor/components/generic-type-support#explicit-generic-types-based-on-ancestor-components) -* [State management: Factor out the state preservation to a common location](xref:blazor/state-management?pivots=server#factor-out-the-state-preservation-to-a-common-location) +* [State management: Factor out state preservation to a common provider](xref:blazor/state-management?pivots=server#factor-out-state-preservation-to-a-common-provider) diff --git a/aspnetcore/blazor/javascript-interoperability/location-of-javascript.md b/aspnetcore/blazor/javascript-interoperability/location-of-javascript.md index 74f95d165e0f..cee1ec797a16 100644 --- a/aspnetcore/blazor/javascript-interoperability/location-of-javascript.md +++ b/aspnetcore/blazor/javascript-interoperability/location-of-javascript.md @@ -33,17 +33,17 @@ Load JavaScript (JS) code using any of the following approaches: :::moniker-end +## Location of ``) inside the [closing `` element](xref:blazor/project-structure#location-of-head-and-body-content) after the Blazor script reference: @@ -105,7 +105,7 @@ Place the JavaScript tags (``) inside the [closing `` :::moniker range=">= aspnetcore-6.0" -### Load a script from an external JavaScript file (`.js`) collocated with a component +## Load a script from an external JavaScript file (`.js`) collocated with a component [!INCLUDE[](~/blazor/includes/js-interop/js-collocation.md)] @@ -170,7 +170,7 @@ For more information, see . :::moniker range=">= aspnetcore-6.0" -### Inject a script before or after Blazor starts +## Inject a script before or after Blazor starts To ensure scripts load before or after Blazor starts, use a JavaScript initializer. For more information and examples, see . @@ -178,7 +178,7 @@ To ensure scripts load before or after Blazor starts, use a JavaScript initializ :::moniker range="< aspnetcore-6.0" -### Inject a script after Blazor starts +## Inject a script after Blazor starts To inject a script after Blazor starts, chain to the `Promise` that results from a manual start of Blazor. For more information and an example, see . diff --git a/aspnetcore/blazor/performance.md b/aspnetcore/blazor/performance.md index 41a4c4229e06..953c8b57f8d5 100644 --- a/aspnetcore/blazor/performance.md +++ b/aspnetcore/blazor/performance.md @@ -228,7 +228,9 @@ To reduce parameter load, bundle multiple parameters in a custom class. For exam } ``` -However, consider that it might be an improvement not to have a table cell component, as shown in the preceding example, and instead [inline its logic into the parent component](#inline-child-components-into-their-parents). +However, keep in mind that bundling primitive parameters into a class isn't always an advantage. While it can reduce parameter count, it also impacts how change detection and rendering behave. Passing non-primitive parameters always triggers a re-render, because Blazor can't know whether arbitrary objects have internally mutable state, whereas passing primitive parameters only triggers a re-render if their values have actually changed. + +Also, consider that it might be an improvement not to have a table cell component, as shown in the preceding example, and instead [inline its logic into the parent component](#inline-child-components-into-their-parents). > [!NOTE] > When multiple approaches are available for improving performance, benchmarking the approaches is usually required to determine which approach yields the best results. diff --git a/aspnetcore/blazor/security/interactive-server-side-rendering.md b/aspnetcore/blazor/security/interactive-server-side-rendering.md index 6107d84c4723..22617f8290ef 100644 --- a/aspnetcore/blazor/security/interactive-server-side-rendering.md +++ b/aspnetcore/blazor/security/interactive-server-side-rendering.md @@ -391,10 +391,21 @@ In addition to the safeguards that the framework implements, the app must be cod For a XSS vulnerability to exist, the app must incorporate user input in the rendered page. Blazor executes a compile-time step where the markup in a `.razor` file is transformed into procedural C# logic. At runtime, the C# logic builds a *render tree* describing the elements, text, and child components. This is applied to the browser's DOM via a sequence of JavaScript instructions (or is serialized to HTML in the case of prerendering): +:::moniker range=">= aspnetcore-8.0" + +* User input rendered via normal Razor syntax (for example, `@someStringValue`) doesn't expose a XSS vulnerability because the Razor syntax is added to the DOM via commands that can only write text. Even if the value includes HTML markup, the value is displayed as static text. When prerendering, the output is HTML-encoded, which also displays the content as static text. +* Component authors can author components in C# without using Razor. The component author is responsible for using the correct APIs when emitting output. For example, use `builder.AddContent(0, someUserSuppliedString)` and *not* `builder.AddMarkupContent(0, someUserSuppliedString)`, as the latter could create a XSS vulnerability. + +:::moniker-end + +:::moniker range="< aspnetcore-8.0" + * User input rendered via normal Razor syntax (for example, `@someStringValue`) doesn't expose a XSS vulnerability because the Razor syntax is added to the DOM via commands that can only write text. Even if the value includes HTML markup, the value is displayed as static text. When prerendering, the output is HTML-encoded, which also displays the content as static text. * Script tags aren't allowed and shouldn't be included in the app's component render tree. If a script tag is included in a component's markup, a compile-time error is generated. * Component authors can author components in C# without using Razor. The component author is responsible for using the correct APIs when emitting output. For example, use `builder.AddContent(0, someUserSuppliedString)` and *not* `builder.AddMarkupContent(0, someUserSuppliedString)`, as the latter could create a XSS vulnerability. +:::moniker-end + Consider further mitigating XSS vulnerabilities. For example, implement a restrictive [Content Security Policy (CSP)](https://developer.mozilla.org/docs/Web/HTTP/CSP). For more information, see . For more information, see . diff --git a/aspnetcore/blazor/security/webassembly/standalone-with-identity/qrcodes-for-authenticator-apps.md b/aspnetcore/blazor/security/webassembly/standalone-with-identity/qrcodes-for-authenticator-apps.md index 353b9227a7d6..07d390437e72 100644 --- a/aspnetcore/blazor/security/webassembly/standalone-with-identity/qrcodes-for-authenticator-apps.md +++ b/aspnetcore/blazor/security/webassembly/standalone-with-identity/qrcodes-for-authenticator-apps.md @@ -160,16 +160,6 @@ At the top of the `CookieAuthenticationStateProvider.cs` file, add a `using` sta using System.Text.Json.Serialization; ``` -Inject an `ILogger` to log exceptions in the class: - -```diff -- public class CookieAuthenticationStateProvider(IHttpClientFactory httpClientFactory) -- : AuthenticationStateProvider, IAccountManagement -+ public class CookieAuthenticationStateProvider(IHttpClientFactory httpClientFactory, -+ ILogger logger) -+ : AuthenticationStateProvider, IAccountManagement -``` - In the , add the option set to , which avoids serializing null properties: ```diff diff --git a/aspnetcore/blazor/state-management.md b/aspnetcore/blazor/state-management.md index 2a80d762bed7..fb5a2bb7a49f 100644 --- a/aspnetcore/blazor/state-management.md +++ b/aspnetcore/blazor/state-management.md @@ -399,11 +399,17 @@ else :::moniker-end -### Factor out the state preservation to a common location +### Factor out state preservation to a common provider If many components rely on browser-based storage, implementing state provider code many times creates code duplication. One option for avoiding code duplication is to create a *state provider parent component* that encapsulates the state provider logic. Child components can work with persisted data without regard to the state persistence mechanism. -In the following example of a `CounterStateProvider` component, counter data is persisted to `sessionStorage`: +In the following example of a `CounterStateProvider` component, counter data is persisted to `sessionStorage`, and it handles the loading phase by not rendering its child content until state loading is complete. + +The `CounterStateProvider` component deals with prerendering by not loading state until after component rendering in the [`OnAfterRenderAsync` lifecycle method](xref:blazor/components/lifecycle#after-component-render-onafterrenderasync), which doesn't execute during prerendering. + +The approach in this section isn't capable of triggering rerendering of multiple subscribed components on the same page. If one subscribed component changes the state, it rerenders and can display the updated state, but a different component on the same page displaying that state displays stale data until its own next rerender. Therefore, the approach described in this section is best suited to using state in a single component on the page. + +`CounterStateProvider.razor`: :::moniker range=">= aspnetcore-5.0" @@ -430,15 +436,26 @@ else public int CurrentCount { get; set; } - protected override async Task OnInitializedAsync() + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + isLoaded = true; + await LoadStateAsync(); + StateHasChanged(); + } + } + + private async Task LoadStateAsync() { var result = await ProtectedSessionStore.GetAsync("count"); CurrentCount = result.Success ? result.Value : 0; - isLoaded = true; + isConnected = true; } - public async Task SaveChangesAsync() + public async Task IncrementCount() { + CurrentCount++; await ProtectedSessionStore.SetAsync("count", CurrentCount); } } @@ -452,7 +469,7 @@ else @using Microsoft.AspNetCore.ProtectedBrowserStorage @inject ProtectedSessionStorage ProtectedSessionStore -@if (isLoaded) +@if (isConnected) { @ChildContent @@ -464,21 +481,32 @@ else } @code { - private bool isLoaded; + private bool isConnected; [Parameter] public RenderFragment ChildContent { get; set; } public int CurrentCount { get; set; } - protected override async Task OnInitializedAsync() + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + isConnected = true; + await LoadStateAsync(); + StateHasChanged(); + } + } + + private async Task LoadStateAsync() { CurrentCount = await ProtectedSessionStore.GetAsync("count"); - isLoaded = true; + isConnected = true; } - public async Task SaveChangesAsync() + public async Task IncrementCount() { + CurrentCount++; await ProtectedSessionStore.SetAsync("count", CurrentCount); } } @@ -489,8 +517,6 @@ else > [!NOTE] > For more information on , see . -The `CounterStateProvider` component handles the loading phase by not rendering its child content until state loading is complete. - :::moniker range=">= aspnetcore-8.0" To make the state accessible to all components in an app, wrap the `CounterStateProvider` component around the (`...`) in the `Routes` component with global interactive server-side rendering (interactive SSR). @@ -541,8 +567,7 @@ Wrapped components receive and can modify the persisted counter state. The follo { if (CounterStateProvider is not null) { - CounterStateProvider.CurrentCount++; - await CounterStateProvider.SaveChangesAsync(); + await CounterStateProvider.IncrementCount(); } } } @@ -550,8 +575,6 @@ Wrapped components receive and can modify the persisted counter state. The follo The preceding component isn't required to interact with `ProtectedBrowserStorage`, nor does it deal with a "loading" phase. -To deal with prerendering as described earlier, `CounterStateProvider` can be amended so that all of the components that consume the counter data automatically work with prerendering. For more information, see the [Handle prerendering](#handle-prerendering) section. - In general, the *state provider parent component* pattern is recommended: * To consume state across many components. @@ -791,7 +814,7 @@ When implementing custom state storage, a useful approach is to adopt [cascading ## Troubleshoot -In a custom state management service, a callback invoked outside of Blazor's synchronization context must wrap the logic of the callback in to move it onto the renderer's synchronization context. +When using a custom state management service where you want to support state modifications from outside Blazor's synchronization context (for example from a timer or a background service), all consuming components must wrap the call in . This ensures the change notification is handled on the renderer's synchronization context. When the state management service doesn't call on Blazor's synchronization context, the following error is thrown: diff --git a/aspnetcore/blazor/tutorials/movie-database-app/part-2.md b/aspnetcore/blazor/tutorials/movie-database-app/part-2.md index 38cffb270f7d..c5f4fecd887b 100644 --- a/aspnetcore/blazor/tutorials/movie-database-app/part-2.md +++ b/aspnetcore/blazor/tutorials/movie-database-app/part-2.md @@ -319,7 +319,7 @@ EF Core adopts the *code-first* approach for database design and maintenance: * Entity classes are created and updated first in the app. * The database is created and updated from the app's entity classes. -This is the reverse procedure of *database-first* approaches, where the database is designed, built, and updated first. Adopting EF Core's code-first approach speeds up the process of app development because most of the difficult and time-consuming database creation and management procedures are handled transparently by the EF Core tooling, so you can focus on app development. +This is the reverse procedure of *database-first* approaches, where the database is designed, built, and updated first. Adopting EF Core's code-first approach aims to speed up the process of app development because most of the difficult and time-consuming database creation and management procedures are handled transparently by the EF Core tooling, so you can focus on app development. :::zone pivot="vs" diff --git a/aspnetcore/blazor/webassembly-lazy-load-assemblies.md b/aspnetcore/blazor/webassembly-lazy-load-assemblies.md index a89174db8f3d..a0054cb6672b 100644 --- a/aspnetcore/blazor/webassembly-lazy-load-assemblies.md +++ b/aspnetcore/blazor/webassembly-lazy-load-assemblies.md @@ -592,16 +592,18 @@ The assembly is assigned to lazyLoadedAssemblies = new(); + private bool grantImaharaRobotControlsAssemblyLoaded; private async Task OnNavigateAsync(NavigationContext args) { try { - if (args.Path == "robot") + if ((args.Path == "robot") && !grantImaharaRobotControlsAssemblyLoaded) { var assemblies = await AssemblyLoader.LoadAssembliesAsync( new[] { "GrantImaharaRobotControls.{FILE EXTENSION}" }); lazyLoadedAssemblies.AddRange(assemblies); + grantImaharaRobotControlsAssemblyLoaded = true; } } catch (Exception ex) @@ -644,16 +646,18 @@ The assembly is assigned to lazyLoadedAssemblies = new List(); + private bool grantImaharaRobotControlsAssemblyLoaded; private async Task OnNavigateAsync(NavigationContext args) { try { - if (args.Path == "robot") + if ((args.Path == "robot") && !grantImaharaRobotControlsAssemblyLoaded) { var assemblies = await AssemblyLoader.LoadAssembliesAsync( new[] { "GrantImaharaRobotControls.{FILE EXTENSION}" }); lazyLoadedAssemblies.AddRange(assemblies); + grantImaharaRobotControlsAssemblyLoaded = true; } } catch (Exception ex)