From 21e2de35e75919fa842856c153a88eb24e189183 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Mon, 5 Jan 2026 10:36:46 -0500 Subject: [PATCH 1/5] Add application scope handler approach --- aspnetcore/blazor/call-web-api.md | 12 ++ .../fundamentals/dependency-injection.md | 4 +- .../blazor/security/additional-scenarios.md | 158 +++++++++++++++++- .../security/blazor-web-app-with-oidc.md | 2 +- .../webassembly/additional-scenarios.md | 3 +- .../blazor/security/webassembly/graph-api.md | 1 + .../fundamentals/dependency-injection.md | 6 +- 7 files changed, 176 insertions(+), 10 deletions(-) diff --git a/aspnetcore/blazor/call-web-api.md b/aspnetcore/blazor/call-web-api.md index 1541d00b521b..c879b9353f89 100644 --- a/aspnetcore/blazor/call-web-api.md +++ b/aspnetcore/blazor/call-web-api.md @@ -464,6 +464,18 @@ The `BlazorWebAppCallWebApi` [sample app](#sample-apps) demonstrates calling a w :::moniker-end +:::moniker range=">= aspnetcore-8.0" + +## Accessing services outside of the `HttpClient`'s scope + + creates instances in a separate dependency injection (DI) scope from the app. If you inject a scoped service into a derived type, the handler doesn't have access to the service from the Blazor circuit. + +For an example of how to access a service in outgoing request middleware using either an application scope handler or a circuit activity handler, see . + +For more information on instances, see . + +:::moniker-end + ## Disposal of `HttpRequestMessage`, `HttpResponseMessage`, and `HttpClient` An without a body doesn't require explicit disposal. However, you can dispose of it with either of the following patterns: diff --git a/aspnetcore/blazor/fundamentals/dependency-injection.md b/aspnetcore/blazor/fundamentals/dependency-injection.md index 9a39bfc0f50b..66c0a1724e05 100644 --- a/aspnetcore/blazor/fundamentals/dependency-injection.md +++ b/aspnetcore/blazor/fundamentals/dependency-injection.md @@ -586,7 +586,7 @@ For more information, see . :::moniker range=">= aspnetcore-8.0" -[Circuit activity handlers](xref:blazor/fundamentals/signalr#monitor-server-side-circuit-activity) provide an approach for accessing scoped Blazor services from other non-Blazor dependency injection (DI) scopes, such as scopes created using . +[Circuit activity handlers](xref:blazor/fundamentals/signalr#monitor-server-side-circuit-activity) provide an approach for accessing scoped Blazor services from other non-Blazor dependency injection (DI) scopes. Prior to the release of ASP.NET Core in .NET 8, accessing circuit-scoped services from other dependency injection scopes required using a custom base component type. With circuit activity handlers, a custom base component type isn't required, as the following example demonstrates: @@ -637,7 +637,7 @@ builder.Services.AddCircuitServicesAccessor(); Access the circuit-scoped services by injecting the `CircuitServicesAccessor` where it's needed. -For an example that shows how to access the from a set up using , see . +For an example that shows how to access the from a set up using , see the circuit activity handler approach in . :::moniker-end diff --git a/aspnetcore/blazor/security/additional-scenarios.md b/aspnetcore/blazor/security/additional-scenarios.md index f86c03279355..836e714c8354 100644 --- a/aspnetcore/blazor/security/additional-scenarios.md +++ b/aspnetcore/blazor/security/additional-scenarios.md @@ -25,6 +25,9 @@ This article explains how to configure server-side Blazor for additional securit If you merely want to use access tokens to make web API calls from a Blazor Web App with a [named HTTP client](xref:blazor/call-web-api#named-httpclient-with-ihttpclientfactory), see the [Use a token handler for web API calls](#use-a-token-handler-for-web-api-calls) section, which explains how to use a implementation to attach a user's access token to outgoing requests. The following guidance in this section is for developers who need access tokens, refresh tokens, and other authentication properties server-side for other purposes. +> [!NOTE] +> For more information on instances, see . + To save tokens and other authentication properties for server-side use in Blazor Web Apps, we recommend using [`IHttpContextAccessor`/`HttpContext`](xref:blazor/components/httpcontext) (, ). Reading tokens from , including as a [cascading parameter](xref:Microsoft.AspNetCore.Components.CascadingParameterAttribute), using is supported for obtaining tokens for use during interactive server rendering if the tokens are obtained during static server-side rendering (static SSR) or prerendering. However, tokens aren't updated if the user authenticates after the circuit is established, since the is captured at the start of the SignalR connection. Also, the use of by means that you must be careful not to lose the execution context before reading the . For more information, see . In a service class, obtain access to the members of the namespace to surface the method on . An alternative approach, which is commented out in the following example, is to call on . For the returned , call . @@ -848,15 +851,164 @@ app.UseMiddleware(); ## Access `AuthenticationStateProvider` in outgoing request middleware -The from a for created with can be accessed in outgoing request middleware using a [circuit activity handler](xref:blazor/fundamentals/signalr#monitor-circuit-activity-blazor-server). + creates instances in a separate dependency injection (DI) scope from the app. If you inject into a derived type, the handler doesn't have access to the current user's authentication state from the Blazor circuit. + +Use either of the following approaches to address this scenario: + +* [Application scope handler](#application-scope-handler-recommended) (*Recommended*) +* [Circuit activity handler](#circuit-activity-handler) > [!NOTE] -> For general guidance on defining delegating handlers for HTTP requests by instances created using in ASP.NET Core apps, see the following sections of : +> For general guidance on defining delegating handlers for HTTP requests by instances created using , see the following sections of : > > * [Outgoing request middleware](xref:fundamentals/http-requests#outgoing-request-middleware) > * [Use DI in outgoing request middleware](xref:fundamentals/http-requests#use-di-in-outgoing-request-middleware) -The following example uses to attach a custom user name header for authenticated users to outgoing requests. +The examples in the following subsections attach a custom user name header for authenticated users to outgoing requests. + +### Application scope handler (*Recommended*) + +The approach in this section uses a [keyed service](xref:fundamentals/dependency-injection#keyed-services) to register a custom that wraps the base client with an application scope handler resolved from the current application scope to access . + +Overview of the approach: + +* Base client configuration: is called to register a [named client](xref:blazor/call-web-api#named-httpclient-with-ihttpclientfactory) with . +* Keyed registration: A custom `AddApplicationScopeHandler` extension method registers a keyed with the same client name. +* Scope-aware handler: The application scope handler is resolved from the current scope, giving it access to . +* Handler caching: The application scope handler uses to get the cached , which preserves connection pooling. +* Configuration reuse: The application scope handler applies the same configuration to its as the base client. + +Create the following methods and classes: + +* `AddApplicationScopeHandler`: An extension method to add the application scope handler and a keyed service to the DI container. +* `ApplicationScopeHandler`: The application scope handler class. +* `AuthenticationStateHandler`: A that attaches a custom user name header for authenticated users to outgoing requests. + +`Services/ApplicationScopeHttpClientExtensions.cs`: + +```csharp +using System.Security.Claims; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.Extensions.Http; +using Microsoft.Extensions.Options; + +namespace BlazorApp.Services; + +public static class ApplicationScopeHttpClientExtensions +{ + public static readonly HttpRequestOptionsKey ScopeKey = + new("ApplicationScope"); + + public static IHttpClientBuilder AddApplicationScopeHandler( + this IHttpClientBuilder builder) + { + var name = builder.Name; + + builder.Services.AddTransient(); + + builder.Services.AddKeyedScoped(name, (sp, key) => + { + var handler = sp.GetRequiredService(); + handler.InnerHandler = + sp.GetRequiredService() + .CreateHandler(name); + + var client = new HttpClient(handler, disposeHandler: false); + + var options = + sp.GetRequiredService>() + .Get(name); + + foreach (var action in options.HttpClientActions) + { + action(client); + } + + return client; + }); + + return builder; + } +} + +public class ApplicationScopeHandler(IServiceProvider serviceProvider) + : DelegatingHandler +{ + protected override Task SendAsync( + HttpRequestMessage request, + CancellationToken cancellationToken) + { + request.Options.Set(ApplicationScopeHttpClientExtensions.ScopeKey, + serviceProvider); + return base.SendAsync(request, cancellationToken); + } +} + +public class AuthenticationStateHandler : DelegatingHandler +{ + private ClaimsPrincipal? user; + + protected override async Task SendAsync( + HttpRequestMessage request, + CancellationToken cancellationToken) + { + if (user is null) + { + if (request.Options.TryGetValue( + ApplicationScopeHttpClientExtensions.ScopeKey, out var sp)) + { + var authStateProvider = sp.GetService(); + + if (authStateProvider is not null) + { + user = (await authStateProvider.GetAuthenticationStateAsync()) + .User; + } + } + } + + if (user?.Identity?.IsAuthenticated) + { + request.Headers.TryAddWithoutValidation("X-USER-IDENTITY-NAME", + user.Identity.Name); + } + + return await base.SendAsync(request, cancellationToken); + } +} +``` + +Register the named client in the `Program` file, calling `AddApplicationScopeHandler` to add the application scope handler: + +```csharp +builder.Services.AddHttpClient("ExternalApi", client => +{ + client.BaseAddress = new Uri("{REQUEST URI}"); +}) +.AddApplicationScopeHandler(); +``` + +The `{REQUEST URI}` placeholder in the preceding example is the request URI (localhost example: `http://localhost:5209`). + +Inject the client into components using the keyed service: + +```razor +@using Microsoft.Extensions.DependencyInjection + +@code { + [Inject(Key = "ExternalApi")] + public HttpClient Http { get; set; } = default!; + + private async Task CallApiAsync() + { + var response = await Http.GetAsync("/api/endpoint"); + } +} +``` + +### Circuit activity handler + +The approach in this section uses a [circuit activity handler](xref:blazor/fundamentals/signalr#monitor-circuit-activity-blazor-server) to access the , which is an alternative to the recommended [application scope handler approach](#application-scope-handler-recommended) in the preceding section. First, implement the `CircuitServicesAccessor` class in the following section of the Blazor dependency injection (DI) article: diff --git a/aspnetcore/blazor/security/blazor-web-app-with-oidc.md b/aspnetcore/blazor/security/blazor-web-app-with-oidc.md index 0ac7cabad9b5..92bbaaf0171e 100644 --- a/aspnetcore/blazor/security/blazor-web-app-with-oidc.md +++ b/aspnetcore/blazor/security/blazor-web-app-with-oidc.md @@ -416,7 +416,7 @@ Sample solution features: * The app securely calls a web API for weather data: - * When rendering the `Weather` component on the server, the component uses the `ServerWeatherForecaster` on the server to obtain weather data from the web API in the `MinimalApiJwt` project using a (`TokenHandler`) that attaches the access token from the to the request. + * When rendering the `Weather` component on the server, the component uses the `ServerWeatherForecaster` on the server to obtain weather data from the web API in the `MinimalApiJwt` project using a (`TokenHandler`) that attaches the access token from the to the request. For more information on instances, see . * When the component is rendered on the client, the component uses the `ClientWeatherForecaster` service implementation, which uses a preconfigured (in the client project's `Program` file) to make the web API call from the server project's `ServerWeatherForecaster`. :::moniker range=">= aspnetcore-9.0" diff --git a/aspnetcore/blazor/security/webassembly/additional-scenarios.md b/aspnetcore/blazor/security/webassembly/additional-scenarios.md index 08304606a178..c3aabb2aa562 100644 --- a/aspnetcore/blazor/security/webassembly/additional-scenarios.md +++ b/aspnetcore/blazor/security/webassembly/additional-scenarios.md @@ -26,7 +26,8 @@ For convenience, the framework provides the adds and related services to the service collection and configures a named (`WebAPI`). is the base address of the resource URI when sending requests. is provided by the [`Microsoft.Extensions.Http`](https://www.nuget.org/packages/Microsoft.Extensions.Http) NuGet package. -* is the used to process access tokens. Access tokens are only added when the request URI is within the app's base URI. +* is the used to process access tokens. Access tokens are only added when the request URI is within the app's base URI. For more information on instances, see . + * creates and configures an instance for outgoing requests using the configuration that corresponds to the named (`WebAPI`). In the following example, is an extension in . Add the package to an app that doesn't already reference it. diff --git a/aspnetcore/blazor/security/webassembly/graph-api.md b/aspnetcore/blazor/security/webassembly/graph-api.md index 4e744466f2de..1b2de076f40e 100644 --- a/aspnetcore/blazor/security/webassembly/graph-api.md +++ b/aspnetcore/blazor/security/webassembly/graph-api.md @@ -938,6 +938,7 @@ In the preceding example, the `GraphAuthorizationMessageHandler` +For more information on creating Middleware, see . ## Constructor injection behavior From 67b2a0c40beb33bb77083b7bdc02686db659b9d0 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Mon, 5 Jan 2026 10:41:54 -0500 Subject: [PATCH 2/5] Updates --- aspnetcore/blazor/security/additional-scenarios.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/blazor/security/additional-scenarios.md b/aspnetcore/blazor/security/additional-scenarios.md index 836e714c8354..44db4254558e 100644 --- a/aspnetcore/blazor/security/additional-scenarios.md +++ b/aspnetcore/blazor/security/additional-scenarios.md @@ -892,7 +892,7 @@ using Microsoft.AspNetCore.Components.Authorization; using Microsoft.Extensions.Http; using Microsoft.Extensions.Options; -namespace BlazorApp.Services; +namespace BlazorSample.Services; public static class ApplicationScopeHttpClientExtensions { From ebce9b9cf6118175d801cc6cc711ab7afe9dc0a9 Mon Sep 17 00:00:00 2001 From: Luke Latham <1622880+guardrex@users.noreply.github.com> Date: Mon, 5 Jan 2026 11:21:05 -0500 Subject: [PATCH 3/5] Update aspnetcore/blazor/security/additional-scenarios.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- aspnetcore/blazor/security/additional-scenarios.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/aspnetcore/blazor/security/additional-scenarios.md b/aspnetcore/blazor/security/additional-scenarios.md index 44db4254558e..43aedbb146a3 100644 --- a/aspnetcore/blazor/security/additional-scenarios.md +++ b/aspnetcore/blazor/security/additional-scenarios.md @@ -985,7 +985,8 @@ builder.Services.AddHttpClient("ExternalApi", client => { client.BaseAddress = new Uri("{REQUEST URI}"); }) -.AddApplicationScopeHandler(); +.AddApplicationScopeHandler() +.AddHttpMessageHandler(); ``` The `{REQUEST URI}` placeholder in the preceding example is the request URI (localhost example: `http://localhost:5209`). From b5a7eff7e939801cbe1e9a3b16cd6168568db71d Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Mon, 5 Jan 2026 11:29:24 -0500 Subject: [PATCH 4/5] Updates --- aspnetcore/blazor/security/additional-scenarios.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/aspnetcore/blazor/security/additional-scenarios.md b/aspnetcore/blazor/security/additional-scenarios.md index 43aedbb146a3..c077fd2445bf 100644 --- a/aspnetcore/blazor/security/additional-scenarios.md +++ b/aspnetcore/blazor/security/additional-scenarios.md @@ -978,6 +978,8 @@ public class AuthenticationStateHandler : DelegatingHandler } ``` +The `AuthenticationStateHandler` in the preceding example caches the user for the lifetime of the . To fetch the user's current authentication state for each request, remove the `null` conditional check on the user. + Register the named client in the `Program` file, calling `AddApplicationScopeHandler` to add the application scope handler: ```csharp From 19360f9964e096594d84186745b2b3388f36385a Mon Sep 17 00:00:00 2001 From: Luke Latham <1622880+guardrex@users.noreply.github.com> Date: Tue, 20 Jan 2026 18:30:26 -0500 Subject: [PATCH 5/5] Update aspnetcore/fundamentals/dependency-injection.md Co-authored-by: Wade Pickett --- aspnetcore/fundamentals/dependency-injection.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/fundamentals/dependency-injection.md b/aspnetcore/fundamentals/dependency-injection.md index d3cf2e33cf2d..911945ead054 100644 --- a/aspnetcore/fundamentals/dependency-injection.md +++ b/aspnetcore/fundamentals/dependency-injection.md @@ -158,7 +158,7 @@ Middleware supports keyed services in both the constructor and the `Invoke`/`Inv :::code language="csharp" source="~/../AspNetCore.Docs.Samples/samples/KeyedServices9/Program.cs" id="snippet_2"::: -For more information on creating Middleware, see . +For more information on creating middleware, see . ## Constructor injection behavior