diff --git a/src/Components/WebAssembly/Server/src/AuthenticationStateSerializer.cs b/src/Components/WebAssembly/Server/src/AuthenticationStateSerializer.cs index 4c4a83e23196..482808b37a0f 100644 --- a/src/Components/WebAssembly/Server/src/AuthenticationStateSerializer.cs +++ b/src/Components/WebAssembly/Server/src/AuthenticationStateSerializer.cs @@ -1,52 +1,40 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Security.Claims; using Microsoft.AspNetCore.Components.Authorization; -using Microsoft.AspNetCore.Components.Web; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Components.WebAssembly.Server; -internal sealed class AuthenticationStateSerializer : IHostEnvironmentAuthenticationStateProvider, IDisposable +internal sealed class AuthenticationStateSerializer : AuthenticationStateProvider, IHostEnvironmentAuthenticationStateProvider { - // Do not change. This must match all versions of the server-side DeserializedAuthenticationStateProvider.PersistenceKey. - internal const string PersistenceKey = $"__internal__{nameof(AuthenticationState)}"; - - private readonly PersistentComponentState _state; private readonly Func> _serializeCallback; - private readonly PersistingComponentStateSubscription _subscription; - private Task? _authenticationStateTask; + [SupplyParameterFromPersistentComponentState] + public AuthenticationStateData? CurrentAuthenticationState { get; set; } - public AuthenticationStateSerializer(PersistentComponentState persistentComponentState, IOptions options) - { - _state = persistentComponentState; - _serializeCallback = options.Value.SerializationCallback; - _subscription = persistentComponentState.RegisterOnPersisting(OnPersistingAsync, RenderMode.InteractiveWebAssembly); - } + private static readonly Task _defaultUnauthenticatedTask = + Task.FromResult(new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()))); - private async Task OnPersistingAsync() + public AuthenticationStateSerializer(IOptions options) { - if (_authenticationStateTask is null) - { - throw new InvalidOperationException($"{nameof(SetAuthenticationState)} must be called before the {nameof(PersistentComponentState)}.{nameof(PersistentComponentState.RegisterOnPersisting)} callback."); - } - - var authenticationStateData = await _serializeCallback(await _authenticationStateTask); - if (authenticationStateData is not null) - { - _state.PersistAsJson(PersistenceKey, authenticationStateData); - } + _serializeCallback = options.Value.SerializationCallback; } /// public void SetAuthenticationState(Task authenticationStateTask) { - _authenticationStateTask = authenticationStateTask ?? throw new ArgumentNullException(nameof(authenticationStateTask)); + ArgumentNullException.ThrowIfNull(authenticationStateTask, nameof(authenticationStateTask)); + _ = SetAuthenticationStateAsync(authenticationStateTask); } - public void Dispose() + private async Task SetAuthenticationStateAsync(Task authenticationStateTask) { - _subscription.Dispose(); + var authenticationState = await authenticationStateTask; + CurrentAuthenticationState = await _serializeCallback(authenticationState); } + + public override Task GetAuthenticationStateAsync() + => _defaultUnauthenticatedTask; } diff --git a/src/Components/WebAssembly/Server/src/WebAssemblyRazorComponentsBuilderExtensions.cs b/src/Components/WebAssembly/Server/src/WebAssemblyRazorComponentsBuilderExtensions.cs index d783bede44f5..8c9852375d79 100644 --- a/src/Components/WebAssembly/Server/src/WebAssemblyRazorComponentsBuilderExtensions.cs +++ b/src/Components/WebAssembly/Server/src/WebAssemblyRazorComponentsBuilderExtensions.cs @@ -4,7 +4,9 @@ using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Endpoints.Infrastructure; +using Microsoft.AspNetCore.Components.Infrastructure; using Microsoft.AspNetCore.Components.WebAssembly.Server; +using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Services; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -40,7 +42,9 @@ public static IRazorComponentsBuilder AddInteractiveWebAssemblyComponents(this I /// An that can be used to further customize the configuration. public static IRazorComponentsBuilder AddAuthenticationStateSerialization(this IRazorComponentsBuilder builder, Action? configure = null) { - builder.Services.TryAddEnumerable(ServiceDescriptor.Scoped()); + builder.Services.TryAddEnumerable(ServiceDescriptor.Scoped()); + builder.Services.TryAddScoped(sp => (AuthenticationStateSerializer)sp.GetRequiredService()); + RegisterPersistentComponentStateServiceCollectionExtensions.AddPersistentServiceRegistration(builder.Services, RenderMode.InteractiveAuto); if (configure is not null) { builder.Services.Configure(configure); diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/DeserializedAuthenticationStateProvider.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/DeserializedAuthenticationStateProvider.cs index bff25cd51fbe..ec954f1bee7c 100644 --- a/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/DeserializedAuthenticationStateProvider.cs +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/Services/DeserializedAuthenticationStateProvider.cs @@ -1,40 +1,34 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics.CodeAnalysis; using System.Security.Claims; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.Extensions.Options; -using static Microsoft.AspNetCore.Internal.LinkerFlags; namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication; internal sealed class DeserializedAuthenticationStateProvider : AuthenticationStateProvider { - // Do not change. This must match all versions of the server-side AuthenticationStateSerializer.PersistenceKey. - private const string PersistenceKey = $"__internal__{nameof(AuthenticationState)}"; + private readonly Func> _deserializeCallback; + + [SupplyParameterFromPersistentComponentState] + public AuthenticationStateData? CurrentAuthenticationState { get; set; } private static readonly Task _defaultUnauthenticatedTask = Task.FromResult(new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()))); - private readonly Task _authenticationStateTask = _defaultUnauthenticatedTask; + public DeserializedAuthenticationStateProvider(IOptions options) + { + _deserializeCallback = options.Value.DeserializationCallback; + } - [UnconditionalSuppressMessage( - "Trimming", - "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", - Justification = $"{nameof(DeserializedAuthenticationStateProvider)} uses the {nameof(DynamicDependencyAttribute)} to preserve the necessary members.")] - [DynamicDependency(JsonSerialized, typeof(AuthenticationStateData))] - [DynamicDependency(JsonSerialized, typeof(IList))] - [DynamicDependency(JsonSerialized, typeof(ClaimData))] - public DeserializedAuthenticationStateProvider(PersistentComponentState state, IOptions options) + public override Task GetAuthenticationStateAsync() { - if (!state.TryTakeFromJson(PersistenceKey, out var authenticationStateData) || authenticationStateData is null) + if (CurrentAuthenticationState is null) { - return; + return _defaultUnauthenticatedTask; } - - _authenticationStateTask = options.Value.DeserializationCallback(authenticationStateData); + var authenticationState = _deserializeCallback(CurrentAuthenticationState); + return authenticationState; } - - public override Task GetAuthenticationStateAsync() => _authenticationStateTask; } diff --git a/src/Components/WebAssembly/WebAssembly.Authentication/src/WebAssemblyAuthenticationServiceCollectionExtensions.cs b/src/Components/WebAssembly/WebAssembly.Authentication/src/WebAssemblyAuthenticationServiceCollectionExtensions.cs index 20cf1e0867f7..221173abd257 100644 --- a/src/Components/WebAssembly/WebAssembly.Authentication/src/WebAssemblyAuthenticationServiceCollectionExtensions.cs +++ b/src/Components/WebAssembly/WebAssembly.Authentication/src/WebAssemblyAuthenticationServiceCollectionExtensions.cs @@ -5,6 +5,8 @@ using System.Reflection; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Components.Infrastructure; +using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Authentication; using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -29,7 +31,10 @@ public static class WebAssemblyAuthenticationServiceCollectionExtensions public static IServiceCollection AddAuthenticationStateDeserialization(this IServiceCollection services, Action? configure = null) { services.AddOptions(); - services.TryAddScoped(); + services.TryAddSingleton(); + services.TryAddSingleton(sp => (DeserializedAuthenticationStateProvider)sp.GetRequiredService()); + RegisterPersistentComponentStateServiceCollectionExtensions.AddPersistentServiceRegistration(services, RenderMode.InteractiveWebAssembly); + if (configure != null) { services.Configure(configure);