在Blazor Server应用程序中,我有一个处理文件上传的api控制器类。它运行良好(上传成功(,直到我尝试注入依赖AuthenticationStateProvider的自定义数据服务。
我可以声明一个虚拟服务,并将其注册到:
builder.Services.AddScoped<MyService>();
并将其注入api控制器,而不是真正的数据服务,这没有问题。
自定义数据服务的构造函数看起来像:
public CustomDataService(IConfiguration Configuration, AuthenticationStateProvider Asp)
{
configuration = Configuration;
asp = Asp;
// Get user name and AspNetUserId for use in CRUD functions.
var authState = asp.GetAuthenticationStateAsync().Result;
.
.
.
}
这在注入Razor页面时工作良好,但在注入api控制器时,数据服务构造函数中调用的行:
asp.GetAuthenticationStateAsync().Result;
是500错误到达客户端之前执行的最后一行。
我想也许我需要用不同的解析器注册自定义数据服务,但不能解决的并不是自定义数据服务。无法解析的是AuthenticationStateProvider。
在这种情况下,我如何获得AuthenticationStateProvider的有效解决方案?
我找到了一个变通方法/解决方案。
我将服务更改为使用HttpContext
而不是AuthenticationStateProvider
作为获取当前UserName
和AspNetUserId
的一种方式,并且此方法似乎与Blazor组件和API控制器都兼容。
新的服务类构造函数:
public CustomDataService(IConfiguration Configuration, IHttpContextAccessor HttpContextAccessor)
{
configuration = Configuration;
_httpContextAccessor = HttpContextAccessor;
userName = HttpContextAccessor.HttpContext.User.Identity.Name;
aspNetUserId = HttpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
.
.
.
}
我也遇到了这个问题。
这里是我的变通方法,以便以一种";常规的";时尚您将需要向备用方法提供HttpContext对象。这是默认重新验证IdentityAuthenticationStateProvider的修改实现,具有泛型类型:
public class RevalidatingIdentityAuthenticationStateProvider<TUser>
: RevalidatingServerAuthenticationStateProvider where TUser : class
{
private readonly IServiceScopeFactory _scopeFactory;
private readonly IdentityOptions _options;
public RevalidatingIdentityAuthenticationStateProvider(
ILoggerFactory loggerFactory,
IServiceScopeFactory scopeFactory,
IOptions<IdentityOptions> optionsAccessor)
: base(loggerFactory)
{
_scopeFactory = scopeFactory;
_options = optionsAccessor.Value;
}
protected override TimeSpan RevalidationInterval => TimeSpan.FromMinutes(30);
private ClaimsPrincipal GetAnonymousClaim()
{
var anonymous = new ClaimsIdentity();
return new ClaimsPrincipal(anonymous);
}
/// <summary>
/// A HttpContext compatible method to call GetAuthenticationStateAsync
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public async Task<AuthenticationState> GetAuthenticationStateAsync(HttpContext context)
{
//If anonymous
if (context.User == null || context.User.Identity == null || context.User.Identity.IsAuthenticated == false)
{
return new AuthenticationState(GetAnonymousClaim());
}
var currentAuthState = new AuthenticationState(context.User);
var cancellationToken = new CancellationToken();
if (await ValidateAuthenticationStateAsync(currentAuthState, cancellationToken))
{
//If the current authstate is validated
return currentAuthState;
}
//If any other case (invalid)
return new AuthenticationState(GetAnonymousClaim());
}
protected override async Task<bool> ValidateAuthenticationStateAsync(
AuthenticationState authenticationState, CancellationToken cancellationToken)
{
// Get the user manager from a new scope to ensure it fetches fresh data
var scope = _scopeFactory.CreateScope();
try
{
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<TUser>>();
return await ValidateSecurityStampAsync(userManager, authenticationState.User);
}
finally
{
if (scope is IAsyncDisposable asyncDisposable)
{
await asyncDisposable.DisposeAsync();
}
else
{
scope.Dispose();
}
}
}
private async Task<bool> ValidateSecurityStampAsync(UserManager<TUser> userManager, ClaimsPrincipal principal)
{
var user = await userManager.GetUserAsync(principal);
if (user == null)
{
return false;
}
else if (!userManager.SupportsUserSecurityStamp)
{
return true;
}
else
{
var principalStamp = principal.FindFirstValue(_options.ClaimsIdentity.SecurityStampClaimType);
var userStamp = await userManager.GetSecurityStampAsync(user);
return principalStamp == userStamp;
}
}
}
然后在您的控制器中,您只需拨打:
var authState = await _authStateProvider.GetAuthenticationStateAsync(HttpContext);
当然,通过DI获得您自己的AuthenticationStateProvider实现。
这也可以通过GetAuthenticationStateAsync管理您自己的逻辑来增强,因为此方法是可重写的。唯一的缺点是,当您处于常规(预期(Blazor逻辑中时,以及当您处于控制器中时,您必须管理自己,并且必须从其他地方检索HttpContext。
当然,有HttpContextAccessor选项,但最好只在需要时使用参数调用自己的实现。