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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,17 @@ Load JavaScript (JS) code using any of the following approaches:

:::moniker-end

## Location of `<script>` tags

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

> [!WARNING]
> Only place a `<script>` tag in a component file (`.razor`) if the component is guaranteed to adopt [static server-side rendering (static SSR)](xref:blazor/fundamentals/index#client-and-server-rendering-concepts) because the `<script>` tag can't be updated dynamically.
Only place a `<script>` tag in a component file (`.razor`) if the component is guaranteed to adopt [static server-side rendering (static SSR)](xref:blazor/fundamentals/index#client-and-server-rendering-concepts) because the `<script>` tag can't be updated dynamically. Placing a `<script>` tag in a component file doesn't produce a compile-time warning or error, but script loading behavior might not match your expectations in components that don't adopt static SSR when the component is rendered.

:::moniker-end

:::moniker range="< aspnetcore-8.0"

> [!WARNING]
> Don't place a `<script>` tag in a component file (`.razor`) because the `<script>` tag can't be updated dynamically.
Don't place a `<script>` tag in a component file (`.razor`) because the `<script>` tag can't be updated dynamically. Placing a `<script>` tag in a component file produces a compile-time error.

:::moniker-end

Expand All @@ -61,7 +61,7 @@ Load JavaScript (JS) code using any of the following approaches:

:::moniker-end

### Load a script in `<head>` markup
## Load a script in `<head>` markup

*The approach in this section isn't generally recommended.*

Expand All @@ -84,7 +84,7 @@ Loading JS from the `<head>` isn't the best approach for the following reasons:
* JS interop may fail if the script depends on Blazor. We recommend loading scripts using one of the other approaches, not via the `<head>` markup.
* The page may become interactive slower due to the time it takes to parse the JS in the script.

### Load a script in `<body>` markup
## Load a script in `<body>` markup

Place the JavaScript tags (`<script>...</script>`) inside the [closing `</body>` element](xref:blazor/project-structure#location-of-head-and-body-content) after the Blazor script reference:

Expand All @@ -105,7 +105,7 @@ Place the JavaScript tags (`<script>...</script>`) inside the [closing `</body>`

:::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)]

Expand Down Expand Up @@ -170,15 +170,15 @@ For more information, see <xref:blazor/components/class-libraries>.

:::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 <xref:blazor/fundamentals/startup#javascript-initializers>.

:::moniker-end

:::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 <xref:blazor/fundamentals/startup#inject-a-script-after-blazor-starts>.

Expand Down
4 changes: 3 additions & 1 deletion aspnetcore/blazor/performance.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
11 changes: 11 additions & 0 deletions aspnetcore/blazor/security/interactive-server-side-rendering.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <xref:blazor/security/content-security-policy>.

For more information, see <xref:security/cross-site-scripting>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,16 +160,6 @@ At the top of the `CookieAuthenticationStateProvider.cs` file, add a `using` sta
using System.Text.Json.Serialization;
```

Inject an `ILogger<CookieAuthenticationStateProvider>` to log exceptions in the class:

```diff
- public class CookieAuthenticationStateProvider(IHttpClientFactory httpClientFactory)
- : AuthenticationStateProvider, IAccountManagement
+ public class CookieAuthenticationStateProvider(IHttpClientFactory httpClientFactory,
+ ILogger<CookieAuthenticationStateProvider> logger)
+ : AuthenticationStateProvider, IAccountManagement
```

In the <xref:System.Text.Json.JsonSerializerOptions>, add the <xref:System.Text.Json.JsonSerializerOptions.DefaultIgnoreCondition> option set to <xref:System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull?displayProperty=nameWithType>, which avoids serializing null properties:

```diff
Expand Down
57 changes: 40 additions & 17 deletions aspnetcore/blazor/state-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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<int>("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);
}
}
Expand All @@ -452,7 +469,7 @@ else
@using Microsoft.AspNetCore.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore

@if (isLoaded)
@if (isConnected)
{
<CascadingValue Value="this">
@ChildContent
Expand All @@ -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<int>("count");
isLoaded = true;
isConnected = true;
}

public async Task SaveChangesAsync()
public async Task IncrementCount()
{
CurrentCount++;
await ProtectedSessionStore.SetAsync("count", CurrentCount);
}
}
Expand All @@ -489,8 +517,6 @@ else
> [!NOTE]
> For more information on <xref:Microsoft.AspNetCore.Components.RenderFragment>, see <xref:blazor/components/index#child-content-render-fragments>.

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 <xref:Microsoft.AspNetCore.Components.Routing.Router> (`<Router>...</Router>`) in the `Routes` component with global interactive server-side rendering (interactive SSR).
Expand Down Expand Up @@ -541,17 +567,14 @@ 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();
}
}
}
```

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.
Expand Down Expand Up @@ -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 <xref:Microsoft.AspNetCore.Components.ComponentBase.InvokeAsync%2A?displayProperty=nameWithType> 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 <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> call in <xref:Microsoft.AspNetCore.Components.ComponentBase.InvokeAsync%2A?displayProperty=nameWithType>. This ensures the change notification is handled on the renderer's synchronization context.

When the state management service doesn't call <xref:Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged%2A> on Blazor's synchronization context, the following error is thrown:

Expand Down
2 changes: 1 addition & 1 deletion aspnetcore/blazor/tutorials/movie-database-app/part-2.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
8 changes: 6 additions & 2 deletions aspnetcore/blazor/webassembly-lazy-load-assemblies.md
Original file line number Diff line number Diff line change
Expand Up @@ -592,16 +592,18 @@ The assembly is assigned to <xref:Microsoft.AspNetCore.Components.Routing.Router

@code {
private List<Assembly> 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)
Expand Down Expand Up @@ -644,16 +646,18 @@ The assembly is assigned to <xref:Microsoft.AspNetCore.Components.Routing.Router

@code {
private List<Assembly> lazyLoadedAssemblies = new List<Assembly>();
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)
Expand Down
Loading