From 86a7dfbac3d982f29cd9dc1dc5eaae3543991a08 Mon Sep 17 00:00:00 2001 From: Rockford Lhotka Date: Tue, 30 Jan 2024 21:28:57 -0800 Subject: [PATCH] #3657 Support .NET 7 context behavior --- .../ApplicationContextManagerInMemory.cs | 220 ++++++++++++++++++ Source/Csla.Blazor/ConfigurationExtensions.cs | 15 +- 2 files changed, 233 insertions(+), 2 deletions(-) create mode 100644 Source/Csla.AspNetCore/Blazor/ApplicationContextManagerInMemory.cs diff --git a/Source/Csla.AspNetCore/Blazor/ApplicationContextManagerInMemory.cs b/Source/Csla.AspNetCore/Blazor/ApplicationContextManagerInMemory.cs new file mode 100644 index 0000000000..a7643bbda5 --- /dev/null +++ b/Source/Csla.AspNetCore/Blazor/ApplicationContextManagerInMemory.cs @@ -0,0 +1,220 @@ +#if NET5_0_OR_GREATER +//----------------------------------------------------------------------- +// +// Copyright (c) Marimer LLC. All rights reserved. +// Website: https://cslanet.com +// +// Application context manager that uses HttpContextAccessor +//----------------------------------------------------------------------- +using Csla.Core; +using Microsoft.AspNetCore.Components.Authorization; +using System; +using System.Security.Claims; +using System.Security.Principal; +using System.Threading.Tasks; + +namespace Csla.AspNetCore.Blazor +{ + /// + /// Application context manager that uses HttpContextAccessor when + /// resolving HttpContext to store context values. + /// + public class ApplicationContextManagerInMemory : IContextManager, IDisposable + { + private ContextDictionary LocalContext { get; set; } + private ContextDictionary ClientContext { get; set; } + private IPrincipal CurrentPrincipal { get; set; } + private readonly ClaimsPrincipal UnauthenticatedPrincipal = new(); + private bool disposedValue; + + /// + /// Gets the current HttpContext instance. + /// + protected AuthenticationStateProvider AuthenticationStateProvider { get; private set; } + + /// + /// Gets or sets a reference to the current ApplicationContext. + /// + public ApplicationContext ApplicationContext { get; set; } + + /// + /// Gets the active circuit state. + /// + protected ActiveCircuitState ActiveCircuitState { get; private set; } + + /// + /// Creates an instance of the object, initializing it + /// with the required IServiceProvider. + /// + /// AuthenticationStateProvider service + /// + public ApplicationContextManagerInMemory(AuthenticationStateProvider authenticationStateProvider, ActiveCircuitState activeCircuitState) + { + AuthenticationStateProvider = authenticationStateProvider; + ActiveCircuitState = activeCircuitState; + CurrentPrincipal = UnauthenticatedPrincipal; + AuthenticationStateProvider.AuthenticationStateChanged += AuthenticationStateProvider_AuthenticationStateChanged; + InitializeUser(); + } + + private void InitializeUser() + { + Task task = default; + try + { + task = AuthenticationStateProvider.GetAuthenticationStateAsync(); + } + catch (InvalidOperationException ex) + { + task = Task.FromResult(new AuthenticationState((ClaimsPrincipal)UnauthenticatedPrincipal)); + string message = ex.Message; + if (message.Contains(nameof(AuthenticationStateProvider.GetAuthenticationStateAsync)) + && message.Contains(nameof(IHostEnvironmentAuthenticationStateProvider.SetAuthenticationState))) + { + SetHostPrincipal(task); + } + else + { + throw; + } + } + AuthenticationStateProvider_AuthenticationStateChanged(task); + } + + private void AuthenticationStateProvider_AuthenticationStateChanged(Task task) + { + if (task is null) + { + CurrentPrincipal = UnauthenticatedPrincipal; + } + else + { + task.ContinueWith((t) => + { + if (task.IsCompletedSuccessfully && task.Result != null) + CurrentPrincipal = task.Result.User; + else + CurrentPrincipal = UnauthenticatedPrincipal; + }); + } + } + + /// + /// Gets a value indicating whether this + /// context manager is valid for use in + /// the current environment. + /// + public bool IsValid + { + get { return ActiveCircuitState.CircuitExists; } + } + + /// + /// Gets a value indicating whether the context manager + /// is stateful. + /// + public bool IsStatefulContext => true; + + /// + /// Gets the current principal. + /// + public IPrincipal GetUser() + { + return CurrentPrincipal; + } + + /// + /// Attempts to set the current principal on the registered + /// IHostEnvironmentAuthenticationStateProvider service. + /// + /// Principal object. + public virtual void SetUser(IPrincipal principal) + { + if (!ReferenceEquals(CurrentPrincipal, principal)) + { + if (principal is ClaimsPrincipal claimsPrincipal) + { + CurrentPrincipal = principal; + SetHostPrincipal(Task.FromResult(new AuthenticationState(claimsPrincipal))); + } + else + { + throw new ArgumentException("typeof(principal) != ClaimsPrincipal"); + } + } + } + + private void SetHostPrincipal(Task task) + { + if (AuthenticationStateProvider is IHostEnvironmentAuthenticationStateProvider hostProvider) + hostProvider.SetAuthenticationState(task); + } + + /// + /// Gets the local context. + /// + public ContextDictionary GetLocalContext() + { + if (LocalContext == null) + LocalContext = new ContextDictionary(); + return LocalContext; + } + + /// + /// Sets the local context. + /// + /// Local context. + public void SetLocalContext(ContextDictionary localContext) + { + LocalContext = localContext; + } + + /// + /// Gets the client context. + /// + /// + public ContextDictionary GetClientContext(ApplicationContext.ExecutionLocations executionLocation) + { + if (ClientContext == null) + ClientContext = new ContextDictionary(); + return ClientContext; + } + + /// + /// Sets the client context. + /// + /// Client context. + /// + public void SetClientContext(ContextDictionary clientContext, ApplicationContext.ExecutionLocations executionLocation) + { + ClientContext = clientContext; + } + + /// + /// Dispose this object's resources. + /// + /// + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + AuthenticationStateProvider.AuthenticationStateChanged -= AuthenticationStateProvider_AuthenticationStateChanged; + } + disposedValue = true; + } + } + + /// + /// Dispose this object's resources. + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} +#endif \ No newline at end of file diff --git a/Source/Csla.Blazor/ConfigurationExtensions.cs b/Source/Csla.Blazor/ConfigurationExtensions.cs index 95dd03b6a8..80fcd7d5b4 100644 --- a/Source/Csla.Blazor/ConfigurationExtensions.cs +++ b/Source/Csla.Blazor/ConfigurationExtensions.cs @@ -45,9 +45,14 @@ public static CslaOptions AddServerSideBlazor(this CslaOptions config, Action i.ServiceType.Equals(contextManagerType)).ToList(); foreach ( var manager in managers ) @@ -82,6 +87,12 @@ public class BlazorServerConfigurationOptions /// public bool UseCslaPermissionsPolicy { get; set; } = true; + /// + /// Gets or sets a value indicating whether to use the + /// pre-Blazor 8 in-memory context manager. + /// + public bool UseInMemoryApplicationContextManager { get; set; } + /// /// Gets or sets the type of the ISessionManager service. ///