Skip to content

Commit 906f018

Browse files
authored
Merge pull request #34345 from dotnet/main
2 parents 2684252 + a641dcf commit 906f018

File tree

8 files changed

+71
-41
lines changed

8 files changed

+71
-41
lines changed

aspnetcore/blazor/components/cascading-values-and-parameters.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -517,4 +517,4 @@ The following `ExampleTabSet` component uses the `TabSet` component, which conta
517517
## Additional resources
518518

519519
* [Generic type support: Explicit generic types based on ancestor components](xref:blazor/components/generic-type-support#explicit-generic-types-based-on-ancestor-components)
520-
* [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)
520+
* [State management: Factor out state preservation to a common provider](xref:blazor/state-management?pivots=server#factor-out-state-preservation-to-a-common-provider)

aspnetcore/blazor/javascript-interoperability/location-of-javascript.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,17 @@ Load JavaScript (JS) code using any of the following approaches:
3333

3434
:::moniker-end
3535

36+
## Location of `<script>` tags
37+
3638
:::moniker range=">= aspnetcore-8.0"
3739

38-
> [!WARNING]
39-
> 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.
40+
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.
4041

4142
:::moniker-end
4243

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

45-
> [!WARNING]
46-
> Don't place a `<script>` tag in a component file (`.razor`) because the `<script>` tag can't be updated dynamically.
46+
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.
4747

4848
:::moniker-end
4949

@@ -61,7 +61,7 @@ Load JavaScript (JS) code using any of the following approaches:
6161
6262
:::moniker-end
6363

64-
### Load a script in `<head>` markup
64+
## Load a script in `<head>` markup
6565

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

@@ -84,7 +84,7 @@ Loading JS from the `<head>` isn't the best approach for the following reasons:
8484
* 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.
8585
* The page may become interactive slower due to the time it takes to parse the JS in the script.
8686

87-
### Load a script in `<body>` markup
87+
## Load a script in `<body>` markup
8888

8989
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:
9090

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

106106
:::moniker range=">= aspnetcore-6.0"
107107

108-
### Load a script from an external JavaScript file (`.js`) collocated with a component
108+
## Load a script from an external JavaScript file (`.js`) collocated with a component
109109

110110
[!INCLUDE[](~/blazor/includes/js-interop/js-collocation.md)]
111111

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

171171
:::moniker range=">= aspnetcore-6.0"
172172

173-
### Inject a script before or after Blazor starts
173+
## Inject a script before or after Blazor starts
174174

175175
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>.
176176

177177
:::moniker-end
178178

179179
:::moniker range="< aspnetcore-6.0"
180180

181-
### Inject a script after Blazor starts
181+
## Inject a script after Blazor starts
182182

183183
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>.
184184

aspnetcore/blazor/performance.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,9 @@ To reduce parameter load, bundle multiple parameters in a custom class. For exam
228228
}
229229
```
230230

231-
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).
231+
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.
232+
233+
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).
232234

233235
> [!NOTE]
234236
> When multiple approaches are available for improving performance, benchmarking the approaches is usually required to determine which approach yields the best results.

aspnetcore/blazor/security/interactive-server-side-rendering.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,10 +391,21 @@ In addition to the safeguards that the framework implements, the app must be cod
391391

392392
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):
393393

394+
:::moniker range=">= aspnetcore-8.0"
395+
396+
* 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.
397+
* 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.
398+
399+
:::moniker-end
400+
401+
:::moniker range="< aspnetcore-8.0"
402+
394403
* 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.
395404
* 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.
396405
* 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.
397406

407+
:::moniker-end
408+
398409
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>.
399410

400411
For more information, see <xref:security/cross-site-scripting>.

aspnetcore/blazor/security/webassembly/standalone-with-identity/qrcodes-for-authenticator-apps.md

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -160,16 +160,6 @@ At the top of the `CookieAuthenticationStateProvider.cs` file, add a `using` sta
160160
using System.Text.Json.Serialization;
161161
```
162162

163-
Inject an `ILogger<CookieAuthenticationStateProvider>` to log exceptions in the class:
164-
165-
```diff
166-
- public class CookieAuthenticationStateProvider(IHttpClientFactory httpClientFactory)
167-
- : AuthenticationStateProvider, IAccountManagement
168-
+ public class CookieAuthenticationStateProvider(IHttpClientFactory httpClientFactory,
169-
+ ILogger<CookieAuthenticationStateProvider> logger)
170-
+ : AuthenticationStateProvider, IAccountManagement
171-
```
172-
173163
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:
174164

175165
```diff

aspnetcore/blazor/state-management.md

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -399,11 +399,17 @@ else
399399

400400
:::moniker-end
401401

402-
### Factor out the state preservation to a common location
402+
### Factor out state preservation to a common provider
403403

404404
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.
405405

406-
In the following example of a `CounterStateProvider` component, counter data is persisted to `sessionStorage`:
406+
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.
407+
408+
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.
409+
410+
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.
411+
412+
`CounterStateProvider.razor`:
407413

408414
:::moniker range=">= aspnetcore-5.0"
409415

@@ -430,15 +436,26 @@ else
430436
431437
public int CurrentCount { get; set; }
432438
433-
protected override async Task OnInitializedAsync()
439+
protected override async Task OnAfterRenderAsync(bool firstRender)
440+
{
441+
if (firstRender)
442+
{
443+
isLoaded = true;
444+
await LoadStateAsync();
445+
StateHasChanged();
446+
}
447+
}
448+
449+
private async Task LoadStateAsync()
434450
{
435451
var result = await ProtectedSessionStore.GetAsync<int>("count");
436452
CurrentCount = result.Success ? result.Value : 0;
437-
isLoaded = true;
453+
isConnected = true;
438454
}
439455
440-
public async Task SaveChangesAsync()
456+
public async Task IncrementCount()
441457
{
458+
CurrentCount++;
442459
await ProtectedSessionStore.SetAsync("count", CurrentCount);
443460
}
444461
}
@@ -452,7 +469,7 @@ else
452469
@using Microsoft.AspNetCore.ProtectedBrowserStorage
453470
@inject ProtectedSessionStorage ProtectedSessionStore
454471
455-
@if (isLoaded)
472+
@if (isConnected)
456473
{
457474
<CascadingValue Value="this">
458475
@ChildContent
@@ -464,21 +481,32 @@ else
464481
}
465482
466483
@code {
467-
private bool isLoaded;
484+
private bool isConnected;
468485
469486
[Parameter]
470487
public RenderFragment ChildContent { get; set; }
471488
472489
public int CurrentCount { get; set; }
473490
474-
protected override async Task OnInitializedAsync()
491+
protected override async Task OnAfterRenderAsync(bool firstRender)
492+
{
493+
if (firstRender)
494+
{
495+
isConnected = true;
496+
await LoadStateAsync();
497+
StateHasChanged();
498+
}
499+
}
500+
501+
private async Task LoadStateAsync()
475502
{
476503
CurrentCount = await ProtectedSessionStore.GetAsync<int>("count");
477-
isLoaded = true;
504+
isConnected = true;
478505
}
479506
480-
public async Task SaveChangesAsync()
507+
public async Task IncrementCount()
481508
{
509+
CurrentCount++;
482510
await ProtectedSessionStore.SetAsync("count", CurrentCount);
483511
}
484512
}
@@ -489,8 +517,6 @@ else
489517
> [!NOTE]
490518
> For more information on <xref:Microsoft.AspNetCore.Components.RenderFragment>, see <xref:blazor/components/index#child-content-render-fragments>.
491519
492-
The `CounterStateProvider` component handles the loading phase by not rendering its child content until state loading is complete.
493-
494520
:::moniker range=">= aspnetcore-8.0"
495521

496522
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).
@@ -541,17 +567,14 @@ Wrapped components receive and can modify the persisted counter state. The follo
541567
{
542568
if (CounterStateProvider is not null)
543569
{
544-
CounterStateProvider.CurrentCount++;
545-
await CounterStateProvider.SaveChangesAsync();
570+
await CounterStateProvider.IncrementCount();
546571
}
547572
}
548573
}
549574
```
550575

551576
The preceding component isn't required to interact with `ProtectedBrowserStorage`, nor does it deal with a "loading" phase.
552577

553-
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.
554-
555578
In general, the *state provider parent component* pattern is recommended:
556579

557580
* To consume state across many components.
@@ -791,7 +814,7 @@ When implementing custom state storage, a useful approach is to adopt [cascading
791814

792815
## Troubleshoot
793816

794-
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.
817+
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.
795818

796819
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:
797820

aspnetcore/blazor/tutorials/movie-database-app/part-2.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ EF Core adopts the *code-first* approach for database design and maintenance:
319319
* Entity classes are created and updated first in the app.
320320
* The database is created and updated from the app's entity classes.
321321

322-
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.
322+
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.
323323

324324
:::zone pivot="vs"
325325

aspnetcore/blazor/webassembly-lazy-load-assemblies.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -592,16 +592,18 @@ The assembly is assigned to <xref:Microsoft.AspNetCore.Components.Routing.Router
592592
593593
@code {
594594
private List<Assembly> lazyLoadedAssemblies = new();
595+
private bool grantImaharaRobotControlsAssemblyLoaded;
595596
596597
private async Task OnNavigateAsync(NavigationContext args)
597598
{
598599
try
599600
{
600-
if (args.Path == "robot")
601+
if ((args.Path == "robot") && !grantImaharaRobotControlsAssemblyLoaded)
601602
{
602603
var assemblies = await AssemblyLoader.LoadAssembliesAsync(
603604
new[] { "GrantImaharaRobotControls.{FILE EXTENSION}" });
604605
lazyLoadedAssemblies.AddRange(assemblies);
606+
grantImaharaRobotControlsAssemblyLoaded = true;
605607
}
606608
}
607609
catch (Exception ex)
@@ -644,16 +646,18 @@ The assembly is assigned to <xref:Microsoft.AspNetCore.Components.Routing.Router
644646
645647
@code {
646648
private List<Assembly> lazyLoadedAssemblies = new List<Assembly>();
649+
private bool grantImaharaRobotControlsAssemblyLoaded;
647650
648651
private async Task OnNavigateAsync(NavigationContext args)
649652
{
650653
try
651654
{
652-
if (args.Path == "robot")
655+
if ((args.Path == "robot") && !grantImaharaRobotControlsAssemblyLoaded)
653656
{
654657
var assemblies = await AssemblyLoader.LoadAssembliesAsync(
655658
new[] { "GrantImaharaRobotControls.{FILE EXTENSION}" });
656659
lazyLoadedAssemblies.AddRange(assemblies);
660+
grantImaharaRobotControlsAssemblyLoaded = true;
657661
}
658662
}
659663
catch (Exception ex)

0 commit comments

Comments
 (0)