From 15930305438d296f30b85d0f50e838e6591ee1c0 Mon Sep 17 00:00:00 2001 From: Luke Latham Date: Wed, 5 Aug 2020 13:46:24 -0500 Subject: [PATCH] Blazor state management updates (#19287) --- .../blazor/call-javascript-from-dotnet.md | 2 +- .../security/server/additional-scenarios.md | 4 +- .../webassembly/additional-scenarios.md | 6 +- .../hosted-with-identity-server.md | 2 +- .../blazor/security/webassembly/index.md | 2 +- .../standalone-with-authentication-library.md | 2 +- aspnetcore/blazor/state-management.md | 561 ++++++++++++++---- aspnetcore/blazor/tooling.md | 6 +- .../fundamentals/dependency-injection.md | 4 +- .../samples/2.x/SampleApp/README.md | 2 +- .../samples/3.x/SampleApp/README.md | 2 +- aspnetcore/release-notes/aspnetcore-3.0.md | 2 +- .../identity-api-authorization.md | 4 +- aspnetcore/security/authentication/mfa.md | 10 +- aspnetcore/signalr/hubcontext.md | 2 +- aspnetcore/zone-pivot-groups.yml | 14 +- 16 files changed, 482 insertions(+), 143 deletions(-) diff --git a/aspnetcore/blazor/call-javascript-from-dotnet.md b/aspnetcore/blazor/call-javascript-from-dotnet.md index 38bbd957d5..7c404f310a 100644 --- a/aspnetcore/blazor/call-javascript-from-dotnet.md +++ b/aspnetcore/blazor/call-javascript-from-dotnet.md @@ -165,7 +165,7 @@ The following example shows capturing a reference to the `username` `` el ``` > [!WARNING] -> Only use an element reference to mutate the contents of an empty element that doesn't interact with Blazor. This scenario is useful when a 3rd party API supplies content to the element. Because Blazor doesn't interact with the element, there's no possibility of a conflict between Blazor's representation of the element and the DOM. +> Only use an element reference to mutate the contents of an empty element that doesn't interact with Blazor. This scenario is useful when a third-party API supplies content to the element. Because Blazor doesn't interact with the element, there's no possibility of a conflict between Blazor's representation of the element and the DOM. > > In the following example, it's *dangerous* to mutate the contents of the unordered list (`ul`) because Blazor interacts with the DOM to populate this element's list items (`
  • `): > diff --git a/aspnetcore/blazor/security/server/additional-scenarios.md b/aspnetcore/blazor/security/server/additional-scenarios.md index 1b825693cf..c2c17bbfd1 100644 --- a/aspnetcore/blazor/security/server/additional-scenarios.md +++ b/aspnetcore/blazor/security/server/additional-scenarios.md @@ -149,9 +149,9 @@ endpoints.MapBlazorHub().RequireAuthorization( }); ``` -## Use Open ID Connect (OIDC) v2.0 endpoints +## Use OpenID Connect (OIDC) v2.0 endpoints -The authentication library and Blazor templates use Open ID Connect (OIDC) v1.0 endpoints. To use a v2.0 endpoint, configure the option in the : +The authentication library and Blazor templates use OpenID Connect (OIDC) v1.0 endpoints. To use a v2.0 endpoint, configure the option in the : ```csharp services.Configure(AzureADDefaults.OpenIdScheme, diff --git a/aspnetcore/blazor/security/webassembly/additional-scenarios.md b/aspnetcore/blazor/security/webassembly/additional-scenarios.md index b166ecddee..ca63b6e334 100644 --- a/aspnetcore/blazor/security/webassembly/additional-scenarios.md +++ b/aspnetcore/blazor/security/webassembly/additional-scenarios.md @@ -447,7 +447,7 @@ For more information, see and the sample app's HTTP Request ## Handle token request errors -When a Single Page Application (SPA) authenticates a user using Open ID Connect (OIDC), the authentication state is maintained locally within the SPA and in the Identity Provider (IP) in the form of a session cookie that's set as a result of the user providing their credentials. +When a Single Page Application (SPA) authenticates a user using OpenID Connect (OIDC), the authentication state is maintained locally within the SPA and in the Identity Provider (IP) in the form of a session cookie that's set as a result of the user providing their credentials. The tokens that the IP emits for the user typically are valid for short periods of time, about one hour normally, so the client app must regularly fetch new tokens. Otherwise, the user would be logged-out after the granted tokens expire. In most cases, OIDC clients are able to provision new tokens without requiring the user to authenticate again thanks to the authentication state or "session" that is kept within the IP. @@ -1002,9 +1002,9 @@ While this approach requires an extra network hop through the server to call a t * The server can store refresh tokens and ensure that the app doesn't lose access to third-party resources. * The app can't leak access tokens from the server that might contain more sensitive permissions. -## Use Open ID Connect (OIDC) v2.0 endpoints +## Use OpenID Connect (OIDC) v2.0 endpoints -The authentication library and Blazor templates use Open ID Connect (OIDC) v1.0 endpoints. To use a v2.0 endpoint, configure the JWT Bearer option. In the following example, AAD is configured for v2.0 by appending a `v2.0` segment to the property: +The authentication library and Blazor templates use OpenID Connect (OIDC) v1.0 endpoints. To use a v2.0 endpoint, configure the JWT Bearer option. In the following example, AAD is configured for v2.0 by appending a `v2.0` segment to the property: ```csharp builder.Services.Configure( diff --git a/aspnetcore/blazor/security/webassembly/hosted-with-identity-server.md b/aspnetcore/blazor/security/webassembly/hosted-with-identity-server.md index 9e91573150..2cb4281147 100644 --- a/aspnetcore/blazor/security/webassembly/hosted-with-identity-server.md +++ b/aspnetcore/blazor/security/webassembly/hosted-with-identity-server.md @@ -94,7 +94,7 @@ The `Startup` class has the following additions. * In `Startup.Configure`: - * The IdentityServer middleware exposes the Open ID Connect (OIDC) endpoints: + * The IdentityServer middleware exposes the OpenID Connect (OIDC) endpoints: ```csharp app.UseIdentityServer(); diff --git a/aspnetcore/blazor/security/webassembly/index.md b/aspnetcore/blazor/security/webassembly/index.md index d0d2eb8752..877d8c42a3 100644 --- a/aspnetcore/blazor/security/webassembly/index.md +++ b/aspnetcore/blazor/security/webassembly/index.md @@ -13,7 +13,7 @@ uid: blazor/security/webassembly/index By [Javier Calvarro Nelson](https://github.com/javiercn) -Blazor WebAssembly apps are secured in the same manner as Single Page Applications (SPAs). There are several approaches for authenticating users to SPAs, but the most common and comprehensive approach is to use an implementation based on the [OAuth 2.0 protocol](https://oauth.net/), such as [Open ID Connect (OIDC)](https://openid.net/connect/). +Blazor WebAssembly apps are secured in the same manner as Single Page Applications (SPAs). There are several approaches for authenticating users to SPAs, but the most common and comprehensive approach is to use an implementation based on the [OAuth 2.0 protocol](https://oauth.net/), such as [OpenID Connect (OIDC)](https://openid.net/connect/). ## Authentication library diff --git a/aspnetcore/blazor/security/webassembly/standalone-with-authentication-library.md b/aspnetcore/blazor/security/webassembly/standalone-with-authentication-library.md index dfd59db42d..8b16069f62 100644 --- a/aspnetcore/blazor/security/webassembly/standalone-with-authentication-library.md +++ b/aspnetcore/blazor/security/webassembly/standalone-with-authentication-library.md @@ -87,7 +87,7 @@ Configuration is supplied by the `wwwroot/appsettings.json` file: } ``` -Authentication support for standalone apps is offered using Open ID Connect (OIDC). The method accepts a callback to configure the parameters required to authenticate an app using OIDC. The values required for configuring the app can be obtained from the OIDC-compliant IP. Obtain the values when you register the app, which typically occurs in their online portal. +Authentication support for standalone apps is offered using OpenID Connect (OIDC). The method accepts a callback to configure the parameters required to authenticate an app using OIDC. The values required for configuring the app can be obtained from the OIDC-compliant IP. Obtain the values when you register the app, which typically occurs in their online portal. ## Access token scopes diff --git a/aspnetcore/blazor/state-management.md b/aspnetcore/blazor/state-management.md index 0c37d63aad..2c9196a689 100644 --- a/aspnetcore/blazor/state-management.md +++ b/aspnetcore/blazor/state-management.md @@ -5,80 +5,172 @@ description: Learn how to persist state in Blazor Server apps. monikerRange: '>= aspnetcore-3.1' ms.author: riande ms.custom: mvc -ms.date: 05/19/2020 +ms.date: 07/22/2020 no-loc: [Blazor, "Blazor Server", "Blazor WebAssembly", "Identity", "Let's Encrypt", Razor, SignalR] uid: blazor/state-management +zone_pivot_groups: blazor-hosting-models --- # ASP.NET Core Blazor state management -By [Steve Sanderson](https://github.com/SteveSandersonMS) +By [Steve Sanderson](https://github.com/SteveSandersonMS) and [Luke Latham](https://github.com/guardrex) -Blazor Server is a stateful app framework. Most of the time, the app maintains an ongoing connection to the server. The user's state is held in the server's memory in a *circuit*. +::: zone pivot="webassembly" -Examples of state held for a user's circuit include: +User state created in a Blazor WebAssembly app is held in the browser's memory. -* The rendered UI: The hierarchy of component instances and their most recent render output. -* The values of any fields and properties in component instances. -* Data held in [dependency injection (DI)](xref:fundamentals/dependency-injection) service instances that are scoped to the circuit. +Examples of user state held in browser memory include: -> [!NOTE] -> This article addresses state persistence in Blazor Server apps. Blazor WebAssembly apps can take advantage of [client-side state persistence in the browser](#client-side-in-the-browser) but require custom solutions or 3rd party packages beyond the scope of this article. +* The hierarchy of component instances and their most recent render output in the rendered UI. +* The values of fields and properties in component instances. +* Data held in [dependency injection (DI)](xref:fundamentals/dependency-injection) service instances. +* Values set through [JavaScript interop](xref:blazor/call-javascript-from-dotnet) calls. -## Blazor circuits +When a user closes and re-opens their browser or reloads the page, user state held in the browser's memory is lost. -If a user experiences a temporary network connection loss, Blazor attempts to reconnect the user to their original circuit so they can continue to use the app. However, reconnecting a user to their original circuit in the server's memory isn't always possible: +## Persist state across browser sessions -* The server can't retain a disconnected circuit forever. The server must release a disconnected circuit after a timeout or when the server is under memory pressure. -* In multiserver, load-balanced deployment environments, any server processing requests may become unavailable at any given time. Individual servers may fail or be automatically removed when no longer required to handle the overall volume of requests. The original server may not be available when the user attempts to reconnect. -* The user might close and re-open their browser or reload the page, which removes any state held in the browser's memory. For example, values set through JavaScript interop calls are lost. +Generally, maintain state across browser sessions where users are actively creating data, not simply reading data that already exists. -When a user can't be reconnected to their original circuit, the user receives a new circuit with an empty state. This is equivalent to closing and re-opening a desktop app. +To preserve state across browser sessions, the app must persist the data to some other storage location than the browser's memory. State persistence isn't automatic. You must take steps when developing the app to implement stateful data persistence. -## Preserve state across circuits +Data persistence is typically only required for high-value state that users expended effort to create. In the following examples, persisting state either saves time or aids in commercial activities: -In some scenarios, preserving state across circuits is desirable. An app can retain important data for a user if: +* Multi-step web forms: It's time-consuming for a user to re-enter data for several completed steps of a multi-step web form if their state is lost. A user loses state in this scenario if they navigate away from the form and return later. +* Shopping carts: Any commercially important component of an app that represents potential revenue can be maintained. A user who loses their state, and thus their shopping cart, may purchase fewer products or services when they return to the site later. -* The web server becomes unavailable. -* The user's browser is forced to start a new circuit with a new web server. - -In general, maintaining state across circuits applies to scenarios where users are actively creating data, not simply reading data that already exists. - -To preserve state beyond a single circuit, *don't merely store the data in the server's memory*. The app must persist the data to some other storage location. State persistence isn't automatic. You must take steps when developing the app to implement stateful data persistence. - -Data persistence is typically only required for high-value state that users have expended effort to create. In the following examples, persisting state either saves time or aids in commercial activities: - -* Multistep webform: It's time-consuming for a user to re-enter data for several completed steps of a multistep process if their state is lost. A user loses state in this scenario if they navigate away from the multistep form and return to the form later. -* Shopping cart: Any commercially important component of an app that represents potential revenue can be maintained. A user who loses their state, and thus their shopping cart, may purchase fewer products or services when they return to the site later. - -It's usually not necessary to preserve easily-recreated state, such as the username entered into a sign-in dialog that hasn't been submitted. - -> [!IMPORTANT] -> An app can only persist *app state*. UIs can't be persisted, such as component instances and their render trees. Components and render trees aren't generally serializable. To persist something similar to UI state, such as the expanded nodes of a TreeView, the app must have custom code to model the behavior as serializable app state. +An app can only persist *app state*. UIs can't be persisted, such as component instances and their render trees. Components and render trees aren't generally serializable. To persist UI state, such as the expanded nodes of a tree view control, the app must use custom code to model the behavior of the UI state as serializable app state. ## Where to persist state -Three common locations exist for persisting state in a Blazor Server app. Each approach is best suited to different scenarios and has different caveats: +Three common locations exist for persisting state: -* [Server-side in a database](#server-side-in-a-database) +* [Server-side storage](#server-side-storage) * [URL](#url) -* [Client-side in the browser](#client-side-in-the-browser) +* [Browser storage](#browser-storage) -### Server-side in a database +### Server-side storage -For permanent data persistence or for any data that must span multiple users or devices, an independent server-side database is almost certainly the best choice. Options include: +For permanent data persistence that spans multiple users and devices, the app can use independent server-side storage accessed via a web API. Options include: -* Relational SQL database -* Key-value store -* Blob store -* Table store +* Blob storage +* Key-value storage +* Relational database +* Table storage -After data is saved in the database, a new circuit can be started by a user at any time. The user's data is retained and available in any new circuit. +After data is saved, the user's state is retained and available in any new browser session. -For more information on Azure data storage options, see the [Azure Storage Documentation](/azure/storage/) and [Azure Databases](https://azure.microsoft.com/product-categories/databases/). +Because Blazor WebAssembly apps run entirely in the user's browser, they require additional measures to access secure external systems, such as storage services and databases. Blazor WebAssembly apps are secured in the same manner as Single Page Applications (SPAs). Typically, an app authenticates a user via [OAuth](https://oauth.net)/[OpenID Connect (OIDC)](https://openid.net/connect/) and then interacts with storage services and databases through web API calls to a server-side app. The server-side app mediates the transfer of data between the Blazor WebAssembly app and the storage service or database. The Blazor WebAssembly app maintains an ephemeral connection to the server-side app, while the server-side app has a persistent connection to storage. + +For more information, see the following resources: + +* +* +* Blazor *Security and Identity* articles + +For more information on Azure data storage options, see the following: + +* [Azure Databases](https://azure.microsoft.com/product-categories/databases/) +* [Azure Storage Documentation](/azure/storage/) ### URL -For transient data representing navigation state, model the data as a part of the URL. Examples of state modeled in the URL include: +For transient data representing navigation state, model the data as a part of the URL. Examples of user state modeled in the URL include: + +* The ID of a viewed entity. +* The current page number in a paged grid. + +The contents of the browser's address bar are retained if the user manually reloads the page. + +For information on defining URL patterns with the [`@page`](xref:mvc/views/razor#page) directive, see . + +### Browser storage + +For transient data that the user is actively creating, a commonly used storage location is the browser's [`localStorage`](https://developer.mozilla.org/docs/Web/API/Window/localStorage) and [`sessionStorage`](https://developer.mozilla.org/docs/Web/API/Window/sessionStorage) collections: + +* `localStorage` is scoped to the browser's window. If the user reloads the page or closes and re-opens the browser, the state persists. If the user opens multiple browser tabs, the state is shared across the tabs. Data persists in `localStorage` until explicitly cleared. +* `sessionStorage` is scoped to the browser tab. If the user reloads the tab, the state persists. If the user closes the tab or the browser, the state is lost. If the user opens multiple browser tabs, each tab has its own independent version of the data. + +> [!NOTE] +> `localStorage` and `sessionStorage` can be used in Blazor WebAssembly apps but only by writing custom code or using a third-party package. + +Generally, `sessionStorage` is safer to use. `sessionStorage` avoids the risk that a user opens multiple tabs and encounters the following: + +* Bugs in state storage across tabs. +* Confusing behavior when a tab overwrites the state of other tabs. + +`localStorage` is the better choice if the app must persist state across closing and re-opening the browser. + +> [!WARNING] +> Users may view or tamper with the data stored in `localStorage` and `sessionStorage`. + +## Additional resources + +* [Save app state before an authentication operation](xref:blazor/security/webassembly/additional-scenarios#save-app-state-before-an-authentication-operation) +* +* + +::: zone-end + +::: zone pivot="server" + +Blazor Server is a stateful app framework. Most of the time, the app maintains a connection to the server. The user's state is held in the server's memory in a *circuit*. + +Examples of user state held in a circuit include: + +* The hierarchy of component instances and their most recent render output in the rendered UI. +* The values of fields and properties in component instances. +* Data held in [dependency injection (DI)](xref:fundamentals/dependency-injection) service instances that are scoped to the circuit. + +User state might also be found in JavaScript variables in the browser's memory set via [JavaScript interop](xref:blazor/call-javascript-from-dotnet) calls. + +If a user experiences a temporary network connection loss, Blazor attempts to reconnect the user to their original circuit with their original state. However, reconnecting a user to their original circuit in the server's memory isn't always possible: + +* The server can't retain a disconnected circuit forever. The server must release a disconnected circuit after a timeout or when the server is under memory pressure. +* In multi-server, load-balanced deployment environments, individual servers may fail or be automatically removed when no longer required to handle the overall volume of requests. The original server processing requests for a user may become unavailable when the user attempts to reconnect. +* The user might close and re-open their browser or reload the page, which removes any state held in the browser's memory. For example, JavaScript variable values set through JavaScript interop calls are lost. + +When a user can't be reconnected to their original circuit, the user receives a new circuit with an empty state. This is equivalent to closing and re-opening a desktop app. + +## Persist state across circuits + +Generally, maintain state across circuits where users are actively creating data, not simply reading data that already exists. + +To preserve state across circuits, the app must persist the data to some other storage location than the server's memory. State persistence isn't automatic. You must take steps when developing the app to implement stateful data persistence. + +Data persistence is typically only required for high-value state that users expended effort to create. In the following examples, persisting state either saves time or aids in commercial activities: + +* Multi-step web forms: It's time-consuming for a user to re-enter data for several completed steps of a multi-step web form if their state is lost. A user loses state in this scenario if they navigate away from the form and return later. +* Shopping carts: Any commercially important component of an app that represents potential revenue can be maintained. A user who loses their state, and thus their shopping cart, may purchase fewer products or services when they return to the site later. + +An app can only persist *app state*. UIs can't be persisted, such as component instances and their render trees. Components and render trees aren't generally serializable. To persist UI state, such as the expanded nodes of a tree view control, the app must use custom code to model the behavior of the UI state as serializable app state. + +## Where to persist state + +Three common locations exist for persisting state: + +* [Server-side storage](#server-side-storage) +* [URL](#url) +* [Browser storage](#browser-storage) + +### Server-side storage + +For permanent data persistence that spans multiple users and devices, the app can use server-side storage. Options include: + +* Blob storage +* Key-value storage +* Relational database +* Table storage + +After data is saved, the user's state is retained and available in any new circuit. + +For more information on Azure data storage options, see the following: + +* [Azure Databases](https://azure.microsoft.com/product-categories/databases/) +* [Azure Storage Documentation](/azure/storage/) + +### URL + +For transient data representing navigation state, model the data as a part of the URL. Examples of user state modeled in the URL include: * The ID of a viewed entity. * The current page number in a paged grid. @@ -88,19 +180,14 @@ The contents of the browser's address bar are retained: * If the user manually reloads the page. * If the web server becomes unavailable, and the user is forced to reload the page in order to connect to a different server. -For information on defining URL patterns with the `@page` directive, see . +For information on defining URL patterns with the [`@page`](xref:mvc/views/razor#page) directive, see . -### Client-side in the browser +### Browser storage -For transient data that the user is actively creating, a common backing store is the browser's `localStorage` and `sessionStorage` collections. The app isn't required to manage or clear the stored state if the circuit is abandoned, which is an advantage over server-side storage. +For transient data that the user is actively creating, a commonly used storage location is the browser's [`localStorage`](https://developer.mozilla.org/docs/Web/API/Window/localStorage) and [`sessionStorage`](https://developer.mozilla.org/docs/Web/API/Window/sessionStorage) collections: -> [!NOTE] -> "Client-side" in this section refers to client-side scenarios in the browser, not the [Blazor WebAssembly hosting model](xref:blazor/hosting-models#blazor-webassembly). `localStorage` and `sessionStorage` can be used in Blazor WebAssembly apps but only by writing custom code or using a 3rd party package. - -`localStorage` and `sessionStorage` differ as follows: - -* `localStorage` is scoped to the user's browser. If the user reloads the page or closes and re-opens the browser, the state persists. If the user opens multiple browser tabs, the state is shared across the tabs. Data persists in `localStorage` until explicitly cleared. -* `sessionStorage` is scoped to the user's browser tab. If the user reloads the tab, the state persists. If the user closes the tab or the browser, the state is lost. If the user opens multiple browser tabs, each tab has its own independent version of the data. +* `localStorage` is scoped to the browser's window. If the user reloads the page or closes and re-opens the browser, the state persists. If the user opens multiple browser tabs, the state is shared across the tabs. Data persists in `localStorage` until explicitly cleared. +* `sessionStorage` is scoped to the browser tab. If the user reloads the tab, the state persists. If the user closes the tab or the browser, the state is lost. If the user opens multiple browser tabs, each tab has its own independent version of the data. Generally, `sessionStorage` is safer to use. `sessionStorage` avoids the risk that a user opens multiple tabs and encounters the following: @@ -114,33 +201,23 @@ Caveats for using browser storage: * Similar to the use of a server-side database, loading and saving data are asynchronous. * Unlike a server-side database, storage isn't available during prerendering because the requested page doesn't exist in the browser during the prerendering stage. * Storage of a few kilobytes of data is reasonable to persist for Blazor Server apps. Beyond a few kilobytes, you must consider the performance implications because the data is loaded and saved across the network. -* Users may view or tamper with the data. ASP.NET Core [Data Protection](xref:security/data-protection/introduction) can mitigate the risk. +* Users may view or tamper with the data. [ASP.NET Core Data Protection](xref:security/data-protection/introduction) can mitigate the risk. For example, [ASP.NET Core Protected Browser Storage](#aspnet-core-protected-browser-storage) uses ASP.NET Core Data Protection. -## Third-party browser storage solutions +Third-party NuGet packages provide APIs for working with `localStorage` and `sessionStorage`. It's worth considering choosing a package that transparently uses [ASP.NET Core Data Protection](xref:security/data-protection/introduction). Data Protection encrypts stored data and reduces the potential risk of tampering with stored data. If JSON-serialized data is stored in plain text, users can see the data using browser developer tools and also modify the stored data. Securing data isn't always a problem because the data might be trivial in nature. For example, reading or modifying the stored color of a UI element isn't a significant security risk to the user or the organization. Avoid allowing users to inspect or tamper with *sensitive data*. -Third-party NuGet packages provide APIs for working with `localStorage` and `sessionStorage`. +::: moniker range=">= aspnetcore-5.0" -It's worth considering choosing a package that transparently uses ASP.NET Core's [Data Protection](xref:security/data-protection/introduction). ASP.NET Core Data Protection encrypts stored data and reduces the potential risk of tampering with stored data. If JSON-serialized data is stored in plaintext, users can see the data using browser developer tools and also modify the stored data. Securing data isn't always a problem because the data might be trivial in nature. For example, reading or modifying the stored color of a UI element isn't a significant security risk to the user or the organization. Avoid allowing users to inspect or tamper with *sensitive data*. +## ASP.NET Core Protected Browser Storage -## Protected Browser Storage experimental package +ASP.NET Core Protected Browser Storage leverages [ASP.NET Core Data Protection](xref:security/data-protection/introduction) for [`localStorage`](https://developer.mozilla.org/docs/Web/API/Window/localStorage) and [`sessionStorage`](https://developer.mozilla.org/docs/Web/API/Window/sessionStorage). -An example of a NuGet package that provides [Data Protection](xref:security/data-protection/introduction) for `localStorage` and `sessionStorage` is [`Microsoft.AspNetCore.ProtectedBrowserStorage`](https://www.nuget.org/packages/Microsoft.AspNetCore.ProtectedBrowserStorage). +> [!NOTE] +> Protected Browser Storage relies on ASP.NET Core Data Protection and is only supported for Blazor Server apps. -> [!WARNING] -> `Microsoft.AspNetCore.ProtectedBrowserStorage` is an unsupported experimental package unsuitable for production use at this time. +### Configuration -### Installation - -To install the `Microsoft.AspNetCore.ProtectedBrowserStorage` package: - -1. In the Blazor Server app project, add a package reference to [`Microsoft.AspNetCore.ProtectedBrowserStorage`](https://www.nuget.org/packages/Microsoft.AspNetCore.ProtectedBrowserStorage). -1. In the top-level HTML (for example, in the `Pages/_Host.cshtml` file in the default project template), add the following ` - ``` - -1. In the `Startup.ConfigureServices` method, call `AddProtectedBrowserStorage` to add `localStorage` and `sessionStorage` services to the service collection: +1. Add a package reference to [`Microsoft.AspNetCore.Components.Web.Extensions`](https://www.nuget.org/packages/Microsoft.AspNetCore.Http.Extensions). +1. In `Startup.ConfigureServices`, call `AddProtectedBrowserStorage` to add `localStorage` and `sessionStorage` services to the service collection: ```csharp services.AddProtectedBrowserStorage(); @@ -148,21 +225,21 @@ To install the `Microsoft.AspNetCore.ProtectedBrowserStorage` package: ### Save and load data within a component -In any component that requires loading or saving data to browser storage, use [`@inject`](xref:mvc/views/razor#inject) to inject an instance of either of the following: +In any component that requires loading or saving data to browser storage, use the [`@inject`](xref:mvc/views/razor#inject) directive to inject an instance of either of the following: * `ProtectedLocalStorage` * `ProtectedSessionStorage` -The choice depends on which backing store you wish to use. In the following example, `sessionStorage` is used: +The choice depends on which browser storage location you wish to use. In the following example, `sessionStorage` is used: ```razor -@using Microsoft.AspNetCore.ProtectedBrowserStorage +@using Microsoft.AspNetCore.Components.Web.Extensions @inject ProtectedSessionStorage ProtectedSessionStore ``` -The `@using` statement can be placed into an `_Imports.razor` file instead of in the component. Use of the `_Imports.razor` file makes the namespace available to larger segments of the app or the whole app. +The `@using` directive can be placed in the app's `_Imports.razor` file instead of in the component. Use of the `_Imports.razor` file makes the namespace available to larger segments of the app or the whole app. -To persist the `currentCount` value in the `Counter` component of the project template, modify the `IncrementCount` method to use `ProtectedSessionStore.SetAsync`: +To persist the `currentCount` value in the `Counter` component of an app based on the Blazor Server project template, modify the `IncrementCount` method to use `ProtectedSessionStore.SetAsync`: ```csharp private async Task IncrementCount() @@ -172,45 +249,43 @@ private async Task IncrementCount() } ``` -In larger, more realistic apps, storage of individual fields is an unlikely scenario. Apps are more likely to store entire model objects that include complex state. `ProtectedSessionStore` automatically serializes and deserializes JSON data. +In larger, more realistic apps, storage of individual fields is an unlikely scenario. Apps are more likely to store entire model objects that include complex state. `ProtectedSessionStore` automatically serializes and deserializes JSON data to store complex state objects. -In the preceding code example, the `currentCount` data is stored as `sessionStorage['count']` in the user's browser. The data isn't stored in plaintext but rather is protected using ASP.NET Core's [Data Protection](xref:security/data-protection/introduction). The encrypted data can be seen if `sessionStorage['count']` is evaluated in the browser's developer console. +In the preceding code example, the `currentCount` data is stored as `sessionStorage['count']` in the user's browser. The data isn't stored in plain text but rather is protected using ASP.NET Core Data Protection. The encrypted data can be inspected if `sessionStorage['count']` is evaluated in the browser's developer console. -To recover the `currentCount` data if the user returns to the `Counter` component later (including if they're on an entirely new circuit), use `ProtectedSessionStore.GetAsync`: +To recover the `currentCount` data if the user returns to the `Counter` component later, including if the user is on a new circuit, use `ProtectedSessionStore.GetAsync`: ```csharp protected override async Task OnInitializedAsync() { - currentCount = await ProtectedSessionStore.GetAsync("count"); + var result = await ProtectedSessionStore.GetAsync("count"); + currentCount = result.Success ? result.Value : 0; } ``` -If the component's parameters include navigation state, call `ProtectedSessionStore.GetAsync` and assign the result in , not . is only called one time when the component is first instantiated. isn't called again later if the user navigates to a different URL while remaining on the same page. For more information, see . +If the component's parameters include navigation state, call `ProtectedSessionStore.GetAsync` and assign a non-`null` result in , not . is only called once when the component is first instantiated. isn't called again later if the user navigates to a different URL while remaining on the same page. For more information, see . > [!WARNING] -> The examples in this section only work if the server doesn't have prerendering enabled. With prerendering enabled, an error is generated similar to: -> -> > JavaScript interop calls cannot be issued at this time. This is because the component is being prerendered. +> The examples in this section only work if the server doesn't have prerendering enabled. With prerendering enabled, an error is generated explaining that JavaScript interop calls cannot be issued because the component is being prerendered. > > Either disable prerendering or add additional code to work with prerendering. To learn more about writing code that works with prerendering, see the [Handle prerendering](#handle-prerendering) section. ### Handle the loading state -Since browser storage is asynchronous (accessed over a network connection), there's always a period of time before the data is loaded and available for use by a component. For the best results, render a loading-state message while loading is in progress instead of displaying blank or default data. +Since browser storage is accessed asynchronously over a network connection, there's always a period of time before the data is loaded and available to a component. For the best results, render a loading-state message while loading is in progress instead of displaying blank or default data. -One approach is to track whether the data is `null` (still loading) or not. In the default `Counter` component, the count is held in an `int`. Make `currentCount` nullable by adding a question mark (`?`) to the type (`int`): +One approach is to track whether the data is `null`, which means that the data is still loading. In the default `Counter` component, the count is held in an `int`. [Make `currentCount` nullable](/dotnet/csharp/language-reference/builtin-types/nullable-value-types) by adding a question mark (`?`) to the type (`int`): ```csharp private int? currentCount; ``` -Instead of unconditionally displaying the count and **`Increment`** button, choose to display these elements only if the data is loaded: +Instead of unconditionally displaying the count and **`Increment`** button, display these elements only if the data is loaded by checking : ```razor @if (currentCount.HasValue) {

    Current count: @currentCount

    - } else @@ -226,32 +301,40 @@ During prerendering: * An interactive connection to the user's browser doesn't exist. * The browser doesn't yet have a page in which it can run JavaScript code. -`localStorage` or `sessionStorage` aren't available during prerendering. If the component attempts to interact with storage, an error is generated similar to: - -> JavaScript interop calls cannot be issued at this time. This is because the component is being prerendered. +`localStorage` or `sessionStorage` aren't available during prerendering. If the component attempts to interact with storage, an error is generated explaining that JavaScript interop calls cannot be issued because the component is being prerendered. One way to resolve the error is to disable prerendering. This is usually the best choice if the app makes heavy use of browser-based storage. Prerendering adds complexity and doesn't benefit the app because the app can't prerender any useful content until `localStorage` or `sessionStorage` are available. -To disable prerendering, open the `Pages/_Host.cshtml` file and change the `render-mode` of the [Component Tag Helper](xref:mvc/views/tag-helpers/builtin-th/component-tag-helper) to . +To disable prerendering, open the `Pages/_Host.cshtml` file and change the `render-mode` attribute of the [Component Tag Helper](xref:mvc/views/tag-helpers/builtin-th/component-tag-helper) to : -Prerendering might be useful for other pages that don't use `localStorage` or `sessionStorage`. To keep prerendering enabled, defer the loading operation until the browser is connected to the circuit. The following is an example for storing a counter value: +```cshtml + +``` + +Prerendering might be useful for other pages that don't use `localStorage` or `sessionStorage`. To retain prerendering, defer the loading operation until the browser is connected to the circuit. The following is an example for storing a counter value: ```razor -@using Microsoft.AspNetCore.ProtectedBrowserStorage +@using Microsoft.AspNetCore.Components.Web.Extensions @inject ProtectedLocalStorage ProtectedLocalStore -... rendering code goes here ... +@if (isConnected) +{ +

    Current count: @currentCount

    + +} +else +{ +

    Loading...

    +} @code { - private int? currentCount; - private bool isConnected = false; + private int currentCount; + private bool isConnected; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { - // When execution reaches this point, the first *interactive* render - // is complete. The component has an active connection to the browser. isConnected = true; await LoadStateAsync(); StateHasChanged(); @@ -260,13 +343,14 @@ Prerendering might be useful for other pages that don't use `localStorage` or `s private async Task LoadStateAsync() { - currentCount = await ProtectedLocalStore.GetAsync("prerenderedCount"); + var result = await ProtectedLocalStore.GetAsync("count"); + currentCount = result.Success ? result.Value : 0; } private async Task IncrementCount() { currentCount++; - await ProtectedSessionStore.SetAsync("count", currentCount); + await ProtectedLocalStore.SetAsync("count", currentCount); } } ``` @@ -275,13 +359,13 @@ Prerendering might be useful for other pages that don't use `localStorage` or `s If many components rely on browser-based storage, re-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: +In the following example of a `CounterStateProvider` component, counter data is persisted to `sessionStorage`: ```razor -@using Microsoft.AspNetCore.ProtectedBrowserStorage +@using Microsoft.AspNetCore.Components.Web.Extensions @inject ProtectedSessionStorage ProtectedSessionStore -@if (hasLoaded) +@if (isLoaded) { @ChildContent @@ -293,7 +377,7 @@ else } @code { - private bool hasLoaded; + private bool isLoaded; [Parameter] public RenderFragment ChildContent { get; set; } @@ -302,8 +386,9 @@ else protected override async Task OnInitializedAsync() { - CurrentCount = await ProtectedSessionStore.GetAsync("count"); - hasLoaded = true; + var result = await ProtectedSessionStore.GetAsync("count"); + currentCount = result.Success ? result.Value : 0; + isLoaded = true; } public async Task SaveChangesAsync() @@ -331,7 +416,6 @@ Wrapped components receive and can modify the persisted counter state. The follo @page "/counter"

    Current count: @CounterStateProvider.CurrentCount

    - @code { @@ -348,11 +432,258 @@ 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. See the [Handle prerendering](#handle-prerendering) section for details. +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. +* If there's just one top-level state object to persist. + +To persist many different state objects and consume different subsets of objects in different places, it's better to avoid persisting state globally. + +::: moniker-end + +::: moniker range="< aspnetcore-5.0" + +## Protected Browser Storage experimental NuGet package + +ASP.NET Core Protected Browser Storage leverages [ASP.NET Core Data Protection](xref:security/data-protection/introduction) for [`localStorage`](https://developer.mozilla.org/docs/Web/API/Window/localStorage) and [`sessionStorage`](https://developer.mozilla.org/docs/Web/API/Window/sessionStorage). + +> [!WARNING] +> `Microsoft.AspNetCore.ProtectedBrowserStorage` is an unsupported, experimental package unsuitable for production use. +> +> The package is only available for use in ASP.NET Core 3.1 Blazor Server apps. + +### Configuration + +1. Add a package reference to [`Microsoft.AspNetCore.ProtectedBrowserStorage`](https://www.nuget.org/packages/Microsoft.AspNetCore.ProtectedBrowserStorage). +1. In the `Pages/_Host.cshtml` file, add the following script inside the closing `` tag: + + ```cshtml + + ``` + +1. In `Startup.ConfigureServices`, call `AddProtectedBrowserStorage` to add `localStorage` and `sessionStorage` services to the service collection: + + ```csharp + services.AddProtectedBrowserStorage(); + ``` + +### Save and load data within a component + +In any component that requires loading or saving data to browser storage, use the [`@inject`](xref:mvc/views/razor#inject) directive to inject an instance of either of the following: + +* `ProtectedLocalStorage` +* `ProtectedSessionStorage` + +The choice depends on which browser storage location you wish to use. In the following example, `sessionStorage` is used: + +```razor +@using Microsoft.AspNetCore.ProtectedBrowserStorage +@inject ProtectedSessionStorage ProtectedSessionStore +``` + +The `@using` statement can be placed into an `_Imports.razor` file instead of in the component. Use of the `_Imports.razor` file makes the namespace available to larger segments of the app or the whole app. + +To persist the `currentCount` value in the `Counter` component of an app based on the Blazor Server project template, modify the `IncrementCount` method to use `ProtectedSessionStore.SetAsync`: + +```csharp +private async Task IncrementCount() +{ + currentCount++; + await ProtectedSessionStore.SetAsync("count", currentCount); +} +``` + +In larger, more realistic apps, storage of individual fields is an unlikely scenario. Apps are more likely to store entire model objects that include complex state. `ProtectedSessionStore` automatically serializes and deserializes JSON data. + +In the preceding code example, the `currentCount` data is stored as `sessionStorage['count']` in the user's browser. The data isn't stored in plain text but rather is protected using ASP.NET Core Data Protection. The encrypted data can be inspected if `sessionStorage['count']` is evaluated in the browser's developer console. + +To recover the `currentCount` data if the user returns to the `Counter` component later, including if they're on an entirely new circuit, use `ProtectedSessionStore.GetAsync`: + +```csharp +protected override async Task OnInitializedAsync() +{ + currentCount = await ProtectedSessionStore.GetAsync("count"); +} +``` + +If the component's parameters include navigation state, call `ProtectedSessionStore.GetAsync` and assign the result in , not . is only called once when the component is first instantiated. isn't called again later if the user navigates to a different URL while remaining on the same page. For more information, see . + +> [!WARNING] +> The examples in this section only work if the server doesn't have prerendering enabled. With prerendering enabled, an error is generated explaining that JavaScript interop calls cannot be issued because the component is being prerendered. +> +> Either disable prerendering or add additional code to work with prerendering. To learn more about writing code that works with prerendering, see the [Handle prerendering](#handle-prerendering) section. + +### Handle the loading state + +Since browser storage is accessed asynchronously over a network connection, there's always a period of time before the data is loaded and available to a component. For the best results, render a loading-state message while loading is in progress instead of displaying blank or default data. + +One approach is to track whether the data is `null`, which means that the data is still loading. In the default `Counter` component, the count is held in an `int`. [Make `currentCount` nullable](/dotnet/csharp/language-reference/builtin-types/nullable-value-types) by adding a question mark (`?`) to the type (`int`): + +```csharp +private int? currentCount; +``` + +Instead of unconditionally displaying the count and **`Increment`** button, choose to display these elements only if the data is loaded: + +```razor +@if (currentCount.HasValue) +{ +

    Current count: @currentCount

    + +} +else +{ +

    Loading...

    +} +``` + +### Handle prerendering + +During prerendering: + +* An interactive connection to the user's browser doesn't exist. +* The browser doesn't yet have a page in which it can run JavaScript code. + +`localStorage` or `sessionStorage` aren't available during prerendering. If the component attempts to interact with storage, an error is generated explaining that JavaScript interop calls cannot be issued because the component is being prerendered. + +One way to resolve the error is to disable prerendering. This is usually the best choice if the app makes heavy use of browser-based storage. Prerendering adds complexity and doesn't benefit the app because the app can't prerender any useful content until `localStorage` or `sessionStorage` are available. + +To disable prerendering, open the `Pages/_Host.cshtml` file and change the `render-mode` attribute of the [Component Tag Helper](xref:mvc/views/tag-helpers/builtin-th/component-tag-helper) to : + +```cshtml + +``` + +Prerendering might be useful for other pages that don't use `localStorage` or `sessionStorage`. To retain prerendering, defer the loading operation until the browser is connected to the circuit. The following is an example for storing a counter value: + +```razor +@using Microsoft.AspNetCore.ProtectedBrowserStorage +@inject ProtectedLocalStorage ProtectedLocalStore + +@if (isConnected) +{ +

    Current count: @currentCount

    + +} +else +{ +

    Loading...

    +} + +@code { + private int? currentCount; + private bool isConnected = false; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + isConnected = true; + await LoadStateAsync(); + StateHasChanged(); + } + } + + private async Task LoadStateAsync() + { + currentCount = await ProtectedLocalStore.GetAsync("count"); + } + + private async Task IncrementCount() + { + currentCount++; + await ProtectedLocalStore.SetAsync("count", currentCount); + } +} +``` + +### Factor out the state preservation to a common location + +If many components rely on browser-based storage, re-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`: + +```razor +@using Microsoft.AspNetCore.ProtectedBrowserStorage +@inject ProtectedSessionStorage ProtectedSessionStore + +@if (isLoaded) +{ + + @ChildContent + +} +else +{ +

    Loading...

    +} + +@code { + private bool isLoaded; + + [Parameter] + public RenderFragment ChildContent { get; set; } + + public int CurrentCount { get; set; } + + protected override async Task OnInitializedAsync() + { + CurrentCount = await ProtectedSessionStore.GetAsync("count"); + isLoaded = true; + } + + public async Task SaveChangesAsync() + { + await ProtectedSessionStore.SetAsync("count", CurrentCount); + } +} +``` + +The `CounterStateProvider` component handles the loading phase by not rendering its child content until loading is complete. + +To use the `CounterStateProvider` component, wrap an instance of the component around any other component that requires access to the counter state. To make the state accessible to all components in an app, wrap the `CounterStateProvider` component around the in the `App` component (`App.razor`): + +```razor + + + ... + + +``` + +Wrapped components receive and can modify the persisted counter state. The following `Counter` component implements the pattern: + +```razor +@page "/counter" + +

    Current count: @CounterStateProvider.CurrentCount

    + + +@code { + [CascadingParameter] + private CounterStateProvider CounterStateProvider { get; set; } + + private async Task IncrementCount() + { + CounterStateProvider.CurrentCount++; + await CounterStateProvider.SaveChangesAsync(); + } +} +``` + +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, *state provider parent component* pattern is recommended: -* To consume state in many other components. +* To consume state across many components. * If there's just one top-level state object to persist. -To persist many different state objects and consume different subsets of objects in different places, it's better to avoid handling the loading and saving of state globally. +To persist many different state objects and consume different subsets of objects in different places, it's better to avoid persisting state globally. + +::: moniker-end + +::: zone-end diff --git a/aspnetcore/blazor/tooling.md b/aspnetcore/blazor/tooling.md index 67284b65ca..4818d47707 100644 --- a/aspnetcore/blazor/tooling.md +++ b/aspnetcore/blazor/tooling.md @@ -14,7 +14,7 @@ zone_pivot_groups: operating-systems By [Daniel Roth](https://github.com/danroth27) and [Luke Latham](https://github.com/guardrex) -::: zone pivot="os-windows" +::: zone pivot="windows" 1. Install the latest version of [Visual Studio 2019](https://visualstudio.microsoft.com/downloads/) with the **ASP.NET and web development** workload. @@ -34,7 +34,7 @@ For more information on trusting the ASP.NET Core HTTPS development certificate, ::: zone-end -::: zone pivot="os-linux" +::: zone pivot="linux" 1. Install the latest version of the [.NET Core 3.1 SDK](https://dotnet.microsoft.com/download/dotnet-core/3.1). If you previously installed the SDK, you can determine your installed version by executing the following command in a command shell: @@ -78,7 +78,7 @@ For more information, see the guidance provided by your browser and Linux distri ::: zone-end -::: zone pivot="os-macos" +::: zone pivot="macos" 1. Install [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/). diff --git a/aspnetcore/fundamentals/dependency-injection.md b/aspnetcore/fundamentals/dependency-injection.md index a2b16d716e..26581d628a 100644 --- a/aspnetcore/fundamentals/dependency-injection.md +++ b/aspnetcore/fundamentals/dependency-injection.md @@ -505,7 +505,7 @@ The built-in service container is designed to serve the needs of the framework a * `Func` support for lazy initialization * Convention-based registration -The following 3rd party containers can be used with ASP.NET Core apps: +The following third-party containers can be used with ASP.NET Core apps: * [Autofac](https://autofac.readthedocs.io/en/latest/integration/aspnetcore.html) * [DryIoc](https://www.nuget.org/packages/DryIoc.Microsoft.DependencyInjection) @@ -1086,7 +1086,7 @@ The built-in service container is designed to serve the needs of the framework a * `Func` support for lazy initialization * Convention-based registration -The following 3rd party containers can be used with ASP.NET Core apps: +The following third-party containers can be used with ASP.NET Core apps: * [Autofac](https://autofac.readthedocs.io/en/latest/integration/aspnetcore.html) * [DryIoc](https://www.nuget.org/packages/DryIoc.Microsoft.DependencyInjection) diff --git a/aspnetcore/fundamentals/middleware/extensibility-third-party-container/samples/2.x/SampleApp/README.md b/aspnetcore/fundamentals/middleware/extensibility-third-party-container/samples/2.x/SampleApp/README.md index ebdf9bd0a8..96c8f55930 100644 --- a/aspnetcore/fundamentals/middleware/extensibility-third-party-container/samples/2.x/SampleApp/README.md +++ b/aspnetcore/fundamentals/middleware/extensibility-third-party-container/samples/2.x/SampleApp/README.md @@ -1,3 +1,3 @@ # ASP.NET Core Middleware Extensibility Sample -This sample illustrates the use of [IMiddleware](https://docs.microsoft.com/dotnet/api/microsoft.aspnetcore.http.imiddleware) and [IMiddlewareFactory](https://docs.microsoft.com/dotnet/api/microsoft.aspnetcore.http.imiddlewarefactory) with a 3rd party dependency injection container, [Simple Injector](https://simpleinjector.org). This sample demonstrates the features described in [Middleware activation with a third-party container in ASP.NET Core](https://docs.microsoft.com/aspnet/core/fundamentals/middleware/extensibility-third-party-container). +This sample illustrates the use of [IMiddleware](https://docs.microsoft.com/dotnet/api/microsoft.aspnetcore.http.imiddleware) and [IMiddlewareFactory](https://docs.microsoft.com/dotnet/api/microsoft.aspnetcore.http.imiddlewarefactory) with a third-party dependency injection container, [Simple Injector](https://simpleinjector.org). This sample demonstrates the features described in [Middleware activation with a third-party container in ASP.NET Core](https://docs.microsoft.com/aspnet/core/fundamentals/middleware/extensibility-third-party-container). diff --git a/aspnetcore/fundamentals/middleware/extensibility-third-party-container/samples/3.x/SampleApp/README.md b/aspnetcore/fundamentals/middleware/extensibility-third-party-container/samples/3.x/SampleApp/README.md index ebdf9bd0a8..96c8f55930 100644 --- a/aspnetcore/fundamentals/middleware/extensibility-third-party-container/samples/3.x/SampleApp/README.md +++ b/aspnetcore/fundamentals/middleware/extensibility-third-party-container/samples/3.x/SampleApp/README.md @@ -1,3 +1,3 @@ # ASP.NET Core Middleware Extensibility Sample -This sample illustrates the use of [IMiddleware](https://docs.microsoft.com/dotnet/api/microsoft.aspnetcore.http.imiddleware) and [IMiddlewareFactory](https://docs.microsoft.com/dotnet/api/microsoft.aspnetcore.http.imiddlewarefactory) with a 3rd party dependency injection container, [Simple Injector](https://simpleinjector.org). This sample demonstrates the features described in [Middleware activation with a third-party container in ASP.NET Core](https://docs.microsoft.com/aspnet/core/fundamentals/middleware/extensibility-third-party-container). +This sample illustrates the use of [IMiddleware](https://docs.microsoft.com/dotnet/api/microsoft.aspnetcore.http.imiddleware) and [IMiddlewareFactory](https://docs.microsoft.com/dotnet/api/microsoft.aspnetcore.http.imiddlewarefactory) with a third-party dependency injection container, [Simple Injector](https://simpleinjector.org). This sample demonstrates the features described in [Middleware activation with a third-party container in ASP.NET Core](https://docs.microsoft.com/aspnet/core/fundamentals/middleware/extensibility-third-party-container). diff --git a/aspnetcore/release-notes/aspnetcore-3.0.md b/aspnetcore/release-notes/aspnetcore-3.0.md index c9f776c64e..bbf4d79ded 100644 --- a/aspnetcore/release-notes/aspnetcore-3.0.md +++ b/aspnetcore/release-notes/aspnetcore-3.0.md @@ -316,7 +316,7 @@ The following list contains new Razor directives: ## IdentityServer4 supports authentication and authorization for web APIs and SPAs -ASP.NET Core 3.0 offers authentication in Single Page Apps (SPAs) using the support for web API authorization. ASP.NET Core Identity for authenticating and storing users is combined with [IdentityServer4](https://identityserver.io/) for implementing Open ID Connect. +ASP.NET Core 3.0 offers authentication in Single Page Apps (SPAs) using the support for web API authorization. ASP.NET Core Identity for authenticating and storing users is combined with [IdentityServer4](https://identityserver.io/) for implementing OpenID Connect. IdentityServer4 is an OpenID Connect and OAuth 2.0 framework for ASP.NET Core 3.0. It enables the following security features: diff --git a/aspnetcore/security/authentication/identity-api-authorization.md b/aspnetcore/security/authentication/identity-api-authorization.md index 4caec8379a..0682a39f0e 100644 --- a/aspnetcore/security/authentication/identity-api-authorization.md +++ b/aspnetcore/security/authentication/identity-api-authorization.md @@ -11,7 +11,7 @@ uid: security/authentication/identity/spa --- # Authentication and authorization for SPAs -ASP.NET Core 3.0 or later offers authentication in Single Page Apps (SPAs) using the support for API authorization. ASP.NET Core Identity for authenticating and storing users is combined with [IdentityServer](https://identityserver.io/) for implementing Open ID Connect. +ASP.NET Core 3.0 or later offers authentication in Single Page Apps (SPAs) using the support for API authorization. ASP.NET Core Identity for authenticating and storing users is combined with [IdentityServer](https://identityserver.io/) for implementing OpenID Connect. An authentication parameter was added to the **Angular** and **React** project templates that is similar to the authentication parameter in the **Web Application (Model-View-Controller)** (MVC) and **Web Application** (Razor Pages) project templates. The allowed parameter values are **None** and **Individual**. The **React.js and Redux** project template doesn't support the authentication parameter at this time. @@ -75,7 +75,7 @@ The `Startup` class has the following additions: app.UseAuthentication(); ``` - * The IdentityServer middleware that exposes the Open ID Connect endpoints: + * The IdentityServer middleware that exposes the OpenID Connect endpoints: ```csharp app.UseIdentityServer(); diff --git a/aspnetcore/security/authentication/mfa.md b/aspnetcore/security/authentication/mfa.md index a927254a62..0625b62786 100644 --- a/aspnetcore/security/authentication/mfa.md +++ b/aspnetcore/security/authentication/mfa.md @@ -246,11 +246,11 @@ The user is redirected to the MFA enable view when clicking the **Admin** link: The `acr_values` parameter can be used to pass the `mfa` required value from the client to the server in an authentication request. > [!NOTE] -> The `acr_values` parameter needs to be handled on the Open ID Connect server for this to work. +> The `acr_values` parameter needs to be handled on the OpenID Connect server for this to work. ### OpenID Connect ASP.NET Core client -The ASP.NET Core Razor Pages Open ID Connect client app uses the `AddOpenIdConnect` method to login to the Open ID Connect server. The `acr_values` parameter is set with the `mfa` value and sent with the authentication request. The `OpenIdConnectEvents` is used to add this. +The ASP.NET Core Razor Pages OpenID Connect client app uses the `AddOpenIdConnect` method to login to the OpenID Connect server. The `acr_values` parameter is set with the `mfa` value and sent with the authentication request. The `OpenIdConnectEvents` is used to add this. For recommended `acr_values` parameter values, see [Authentication Method Reference Values](https://tools.ietf.org/html/draft-ietf-oauth-amr-values-08). @@ -312,7 +312,7 @@ You can enable MFA to login here: Enable MFA ``` -In the `Login` method, the `IIdentityServerInteractionService` interface implementation `_interaction` is used to access the Open ID Connect request parameters. The `acr_values` parameter is accessed using the `AcrValues` property. As the client sent this with `mfa` set, this can then be checked. +In the `Login` method, the `IIdentityServerInteractionService` interface implementation `_interaction` is used to access the OpenID Connect request parameters. The `acr_values` parameter is accessed using the `AcrValues` property. As the client sent this with `mfa` set, this can then be checked. If MFA is required, and the user in ASP.NET Core Identity has MFA enabled, then the login continues. When the user has no MFA enabled, the user is redirected to the custom view *ErrorEnable2FA.cshtml*. Then ASP.NET Core Identity signs the user in. @@ -416,7 +416,7 @@ namespace AspNetCoreRequireMfaOidc An `AuthorizationHandler` is implemented that will use the `amr` claim and check for the value `mfa`. The `amr` is returned in the `id_token` of a successful authentication and can have many different values as defined in the [Authentication Method Reference Values](https://tools.ietf.org/html/draft-ietf-oauth-amr-values-08) specification. -The returned value depends on how the identity authenticated and on the Open ID Connect server implementation. +The returned value depends on how the identity authenticated and on the OpenID Connect server implementation. The `AuthorizationHandler` uses the `RequireMfa` requirement and validates the `amr` claim. The OpenID Connect server can be implemented using IdentityServer4 with ASP.NET Core Identity. When a user logs in using TOTP, the `amr` claim is returned with an MFA value. If using a different OpenID Connect server implementation or a different MFA type, the `amr` claim will, or can, have a different value. The code must be extended to accept this as well. @@ -546,7 +546,7 @@ You require MFA to login here Enable MFA ``` -Now only users that authenticate with MFA can access the page or website. If different MFA types are used or if 2FA is okay, the `amr` claim will have different values and needs to be processed correctly. Different Open ID Connect servers also return different values for this claim and might not follow the [Authentication Method Reference Values](https://tools.ietf.org/html/draft-ietf-oauth-amr-values-08) specification. +Now only users that authenticate with MFA can access the page or website. If different MFA types are used or if 2FA is okay, the `amr` claim will have different values and needs to be processed correctly. Different OpenID Connect servers also return different values for this claim and might not follow the [Authentication Method Reference Values](https://tools.ietf.org/html/draft-ietf-oauth-amr-values-08) specification. When logging in without MFA (for example, using just a password): diff --git a/aspnetcore/signalr/hubcontext.md b/aspnetcore/signalr/hubcontext.md index a0a2dc71ba..a8cebd8eb6 100644 --- a/aspnetcore/signalr/hubcontext.md +++ b/aspnetcore/signalr/hubcontext.md @@ -58,7 +58,7 @@ app.Use(async (context, next) => ### Get an instance of IHubContext from IHost Accessing an `IHubContext` from the web host is useful for -integrating with areas outside of ASP.NET Core, for example, using 3rd party dependency injection frameworks: +integrating with areas outside of ASP.NET Core, for example, using third-party dependency injection frameworks: ```csharp public class Program diff --git a/aspnetcore/zone-pivot-groups.yml b/aspnetcore/zone-pivot-groups.yml index 79206c6fd6..aa081a7574 100644 --- a/aspnetcore/zone-pivot-groups.yml +++ b/aspnetcore/zone-pivot-groups.yml @@ -4,9 +4,17 @@ groups: title: Operating System prompt: Choose an operating system pivots: - - id: os-windows + - id: windows title: Windows - - id: os-linux + - id: linux title: Linux - - id: os-macos + - id: macos title: macOS +- id: blazor-hosting-models + title: Blazor Hosting Model + prompt: Choose a Blazor hosting model + pivots: + - id: webassembly + title: Blazor WebAssembly + - id: server + title: Blazor Server