diff --git a/aspnetcore/blazor/fundamentals/dependency-injection.md b/aspnetcore/blazor/fundamentals/dependency-injection.md index 13e026a48bd3..e74b0f672c3d 100644 --- a/aspnetcore/blazor/fundamentals/dependency-injection.md +++ b/aspnetcore/blazor/fundamentals/dependency-injection.md @@ -675,7 +675,70 @@ Navigate to the `TransientExample` component at `/transient-example` and an . + +Prior to the release of ASP.NET Core 8.0, 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: + +```csharp +public class CircuitServicesAccessor +{ + static readonly AsyncLocal blazorServices = new(); + + public IServiceProvider? Services + { + get => blazorServices.Value; + set => blazorServices.Value = value; + } +} + +public class ServicesAccessorCircuitHandler : CircuitHandler +{ + readonly IServiceProvider services; + readonly CircuitServicesAccessor circuitServicesAccessor; + + public ServicesAccessorCircuitHandler(IServiceProvider services, + CircuitServicesAccessor servicesAccessor) + { + this.services = services; + this.circuitServicesAccessor = servicesAccessor; + } + + public override Func CreateInboundActivityHandler( + Func next) + { + return async context => + { + circuitServicesAccessor.Services = services; + await next(context); + circuitServicesAccessor.Services = null; + }; + } +} + +public static class CircuitServicesServiceCollectionExtensions +{ + public static IServiceCollection AddCircuitServicesAccessor( + this IServiceCollection services) + { + services.AddScoped(); + services.AddScoped(); + + return services; + } +} +``` + +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 . + +:::moniker-end + +:::moniker range="< aspnetcore-8.0" There may be times when a Razor component invokes asynchronous methods that execute code in a different DI scope. Without the correct approach, these DI scopes don't have access to Blazor's services, such as and . @@ -804,15 +867,28 @@ public class CustomComponentBase : ComponentBase, IHandleEvent, IHandleAfterRend Any components extending `CustomComponentBase` automatically have `BlazorServiceAccessor.Services` set to the in the current Blazor DI scope. +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-8.0" + Finally, in `Program.cs`, add the `BlazorServiceAccessor` as a scoped service: ```csharp -var builder = WebApplication.CreateBuilder(args); -// ... builder.Services.AddScoped(); -// ... ``` +:::moniker-end + +:::moniker range="< aspnetcore-6.0" + +Finally, in `Startup.ConfigureServices` of `Startup.cs`, add the `BlazorServiceAccessor` as a scoped service: + +```csharp +services.AddScoped(); +``` + +:::moniker-end + ## Additional resources * diff --git a/aspnetcore/blazor/fundamentals/signalr.md b/aspnetcore/blazor/fundamentals/signalr.md index 2fd828a8fa86..409f62a0805a 100644 --- a/aspnetcore/blazor/fundamentals/signalr.md +++ b/aspnetcore/blazor/fundamentals/signalr.md @@ -251,6 +251,88 @@ There was a problem with the connection! (Current reconnect attempt: 3 / 8) By default, Blazor Server apps prerender the UI on the server before the client connection to the server is established. For more information, see . +:::moniker-end + +:::moniker range=">= aspnetcore-8.0" + +## Monitor circuit activity (Blazor Server) + +Monitor inbound circuit activity in Blazor Server apps using the `CreateInboundActivityHandler` method on . Inbound circuit activity is any activity sent from the browser to the server, such as UI events or JavaScript-to-.NET interop calls. + +For example, you can use a circuit activity handler to detect if the client is idle: + +```csharp +public sealed class IdleCircuitHandler : CircuitHandler, IDisposable +{ + readonly Timer timer; + readonly ILogger logger; + + public IdleCircuitHandler(IOptions options, + ILogger logger) + { + timer = new Timer(); + timer.Interval = options.Value.IdleTimeout.TotalMilliseconds; + timer.AutoReset = false; + timer.Elapsed += CircuitIdle; + this.logger = logger; + } + + private void CircuitIdle(object? sender, System.Timers.ElapsedEventArgs e) + { + logger.LogInformation("{Circuit} is idle", nameof(CircuitIdle)); + } + + public override Func CreateInboundActivityHandler( + Func next) + { + return context => + { + timer.Stop(); + timer.Start(); + return next(context); + }; + } + + public void Dispose() + { + timer.Dispose(); + } +} + +public class IdleCircuitOptions +{ + public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromMinutes(5); +} + +public static class IdleCircuitHandlerServiceCollectionExtensions +{ + public static IServiceCollection AddIdleCircuitHandler( + this IServiceCollection services, + Action configureOptions) + { + services.Configure(configureOptions); + services.AddIdleCircuitHandler(); + return services; + } + + public static IServiceCollection AddIdleCircuitHandler( + this IServiceCollection services) + { + services.AddScoped(); + return services; + } +} +``` + +Circuit activity handlers also provide an approach for accessing scoped Blazor services from other non-Blazor dependency injection (DI) scopes. For more information and examples, see: + +* +* + +:::moniker-end + +:::moniker range=">= aspnetcore-7.0" + ## Blazor startup Configure the manual start of a Blazor app's SignalR circuit in the `Pages/_Host.cshtml` file (Blazor Server) or `wwwroot/index.html` (hosted Blazor WebAssembly with SignalR implemented): diff --git a/aspnetcore/blazor/security/server/additional-scenarios.md b/aspnetcore/blazor/security/server/additional-scenarios.md index 1dadd0f42a5e..05a5194ce4b3 100644 --- a/aspnetcore/blazor/security/server/additional-scenarios.md +++ b/aspnetcore/blazor/security/server/additional-scenarios.md @@ -697,3 +697,65 @@ Immediately before the call to `app.MapBlazorHub()` in `Startup.Configure` of `S ```csharp app.UseMiddleware(); ``` + +:::moniker range=">= aspnetcore-8.0" + +## 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). + +> [!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 : +> +> * [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. + +First, implement the `CircuitServicesAccessor` class in the following section of the Blazor dependency injection (DI) article: + +[Access Blazor services from a different DI scope](xref:blazor/fundamentals/dependency-injection#access-blazor-services-from-a-different-di-scope) + +Use the `CircuitServicesAccessor` to access the in the implementation. + +`AuthenticationStateHandler.cs`: + +```csharp +public class AuthenticationStateHandler : DelegatingHandler +{ + readonly CircuitServicesAccessor circuitServicesAccessor; + + public AuthenticationStateHandler( + CircuitServicesAccessor circuitServicesAccessor) + { + this.circuitServicesAccessor = circuitServicesAccessor; + } + + protected override async Task SendAsync( + HttpRequestMessage request, CancellationToken cancellationToken) + { + var authStateProvider = circuitServicesAccessor.Services + .GetRequiredService(); + var authState = await authStateProvider.GetAuthenticationStateAsync(); + var user = authState.User; + + if (user.Identity is not null && user.Identity.IsAuthenticated) + { + request.Headers.Add("X-USER-IDENTITY-NAME", user.Identity.Name); + } + + return await base.SendAsync(request, cancellationToken); + } +} +``` + +In `Program.cs`, register the `AuthenticationStateHandler` and add the handler to the that creates instances: + +```csharp +builder.Services.AddTransient(); + +builder.Services.AddHttpClient("HttpMessageHandler") + .AddHttpMessageHandler(); +``` + +:::moniker-end