Skip to content

Commit

Permalink
#3657 Support .NET 7 context behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
rockfordlhotka committed Jan 31, 2024
1 parent 628bdec commit 86a7dfb
Show file tree
Hide file tree
Showing 2 changed files with 233 additions and 2 deletions.
220 changes: 220 additions & 0 deletions Source/Csla.AspNetCore/Blazor/ApplicationContextManagerInMemory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
#if NET5_0_OR_GREATER
//-----------------------------------------------------------------------
// <copyright file="ApplicationContextManagerInMemory.cs" company="Marimer LLC">
// Copyright (c) Marimer LLC. All rights reserved.
// Website: https://cslanet.com
// </copyright>
// <summary>Application context manager that uses HttpContextAccessor</summary>
//-----------------------------------------------------------------------
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
{
/// <summary>
/// Application context manager that uses HttpContextAccessor when
/// resolving HttpContext to store context values.
/// </summary>
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;

/// <summary>
/// Gets the current HttpContext instance.
/// </summary>
protected AuthenticationStateProvider AuthenticationStateProvider { get; private set; }

/// <summary>
/// Gets or sets a reference to the current ApplicationContext.
/// </summary>
public ApplicationContext ApplicationContext { get; set; }

/// <summary>
/// Gets the active circuit state.
/// </summary>
protected ActiveCircuitState ActiveCircuitState { get; private set; }

/// <summary>
/// Creates an instance of the object, initializing it
/// with the required IServiceProvider.
/// </summary>
/// <param name="authenticationStateProvider">AuthenticationStateProvider service</param>
/// <param name="activeCircuitState"></param>
public ApplicationContextManagerInMemory(AuthenticationStateProvider authenticationStateProvider, ActiveCircuitState activeCircuitState)
{
AuthenticationStateProvider = authenticationStateProvider;
ActiveCircuitState = activeCircuitState;
CurrentPrincipal = UnauthenticatedPrincipal;
AuthenticationStateProvider.AuthenticationStateChanged += AuthenticationStateProvider_AuthenticationStateChanged;
InitializeUser();
}

private void InitializeUser()
{
Task<AuthenticationState> 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<AuthenticationState> task)
{
if (task is null)
{
CurrentPrincipal = UnauthenticatedPrincipal;
}
else
{
task.ContinueWith((t) =>
{
if (task.IsCompletedSuccessfully && task.Result != null)
CurrentPrincipal = task.Result.User;
else
CurrentPrincipal = UnauthenticatedPrincipal;
});
}
}

/// <summary>
/// Gets a value indicating whether this
/// context manager is valid for use in
/// the current environment.
/// </summary>
public bool IsValid
{
get { return ActiveCircuitState.CircuitExists; }
}

/// <summary>
/// Gets a value indicating whether the context manager
/// is stateful.
/// </summary>
public bool IsStatefulContext => true;

/// <summary>
/// Gets the current principal.
/// </summary>
public IPrincipal GetUser()
{
return CurrentPrincipal;
}

/// <summary>
/// Attempts to set the current principal on the registered
/// IHostEnvironmentAuthenticationStateProvider service.
/// </summary>
/// <param name="principal">Principal object.</param>
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<AuthenticationState> task)
{
if (AuthenticationStateProvider is IHostEnvironmentAuthenticationStateProvider hostProvider)
hostProvider.SetAuthenticationState(task);
}

/// <summary>
/// Gets the local context.
/// </summary>
public ContextDictionary GetLocalContext()
{
if (LocalContext == null)
LocalContext = new ContextDictionary();
return LocalContext;
}

/// <summary>
/// Sets the local context.
/// </summary>
/// <param name="localContext">Local context.</param>
public void SetLocalContext(ContextDictionary localContext)
{
LocalContext = localContext;
}

/// <summary>
/// Gets the client context.
/// </summary>
/// <param name="executionLocation"></param>
public ContextDictionary GetClientContext(ApplicationContext.ExecutionLocations executionLocation)
{
if (ClientContext == null)
ClientContext = new ContextDictionary();
return ClientContext;
}

/// <summary>
/// Sets the client context.
/// </summary>
/// <param name="clientContext">Client context.</param>
/// <param name="executionLocation"></param>
public void SetClientContext(ContextDictionary clientContext, ApplicationContext.ExecutionLocations executionLocation)
{
ClientContext = clientContext;
}

/// <summary>
/// Dispose this object's resources.
/// </summary>
/// <param name="disposing"></param>
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
AuthenticationStateProvider.AuthenticationStateChanged -= AuthenticationStateProvider_AuthenticationStateChanged;
}
disposedValue = true;
}
}

/// <summary>
/// Dispose this object's resources.
/// </summary>
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}
#endif
15 changes: 13 additions & 2 deletions Source/Csla.Blazor/ConfigurationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,14 @@ public static CslaOptions AddServerSideBlazor(this CslaOptions config, Action<Bl
// minimize PropertyChanged events
config.BindingOptions.PropertyChangedMode = ApplicationContext.PropertyChangedModes.Windows;

var managerType = Type.GetType("Csla.AspNetCore.Blazor.ApplicationContextManagerBlazor,Csla.AspNetCore");
string managerTypeName;
if (blazorOptions.UseInMemoryApplicationContextManager)
managerTypeName = "Csla.AspNetCore.Blazor.ApplicationContextManagerInMemory,Csla.AspNetCore";
else
managerTypeName = "Csla.AspNetCore.Blazor.ApplicationContextManagerBlazor,Csla.AspNetCore";
var managerType = Type.GetType(managerTypeName);
if (managerType is null)
throw new TypeLoadException("Csla.AspNetCore.Blazor.ApplicationContextManagerBlazor,Csla.AspNetCore");
throw new TypeLoadException(managerTypeName);
var contextManagerType = typeof(Core.IContextManager);
var managers = config.Services.Where(i => i.ServiceType.Equals(contextManagerType)).ToList();
foreach ( var manager in managers )
Expand Down Expand Up @@ -82,6 +87,12 @@ public class BlazorServerConfigurationOptions
/// </summary>
public bool UseCslaPermissionsPolicy { get; set; } = true;

/// <summary>
/// Gets or sets a value indicating whether to use the
/// pre-Blazor 8 in-memory context manager.
/// </summary>
public bool UseInMemoryApplicationContextManager { get; set; }

/// <summary>
/// Gets or sets the type of the ISessionManager service.
/// </summary>
Expand Down

0 comments on commit 86a7dfb

Please sign in to comment.