有没有办法将作用域服务注入自定义控制器激活器(或页面模型激活器提供程序)?
Is there a way to inject a scoped service into a custom Controller Activator (or Page Model Activator Provider)?
我正在开发 ASP.NET 核心应用程序并使用自定义 PageModelActivatorProvider
to create my razor pages. This app has user's and implements Identity using AddIdentityCore
。我还添加了 SignInManager
和身份验证:
builder.Services.AddIdentityCore<ApplicationUser>(options =>
{
options.SignIn.RequireConfirmedEmail = true;
options.User.RequireUniqueEmail = true;
})
.AddSignInManager<CustomSignInManager>();
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignOutScheme = IdentityConstants.ApplicationScheme;
})
.AddCookie(IdentityConstants.ApplicationScheme, options =>
{
options.LoginPath = new PathString("/Account/Login");
options.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync
};
});
我创建了一个自定义 UserStore
并将其作为范围服务添加到我的 Program.cs
文件中:
string connectionString = builder.Configuration.GetConnectionString("connectionString");
builder.Services.AddSingleton<IPageModelActivatorProvider, WebUIPageModelActivatorProvider>();
builder.Services.AddScoped<IUserRepository>(_ => new UserRepository(connectionString));
builder.Services.AddScoped<IUserStore<ApplicationUser>, UserStore>();
UserManager
和 SignInManager
classes 分别作为作用域服务添加到 AddIdentityCore
和 AddSignInManager
方法中。
我的 PageModelActivatorProvider
class 创建了一个 LoginModel
页面模型,它需要一个 UserManager
和一个 SignInManager
。这两个都是作用域服务,但是我的 PageModelActivatorProvider
被注册为单例服务,所以我不能通过构造函数注入来注入 UserManager
和 SignInManager
。当我尝试使用 IServiceProvider
到 GetRequiredService
时,出现以下异常:
'Cannot resolve scoped service 'Microsoft.AspNetCore.Identity.IUserStore`1[DataAccess.ApplicationUser]' from root provider.'
我得到了 UserManager
和 SignInManager
相同的异常,因为它们都被注册为作用域服务,我试图在单例服务中访问它们。
public sealed class WebUIPageModelActivatorProvider : IPageModelActivatorProvider, IDisposable
{
private readonly IServiceProvider _services;
public WebUIPageModelActivatorProvider(IServiceProvider services)
{
// Create Singleton components
_services = services;
}
public Func<PageContext, object> CreateActivator(CompiledPageActionDescriptor descriptor)
{
return (context) => _CreatePageModelType(context, descriptor.ModelTypeInfo!.AsType());
}
public Action<PageContext, object>? CreateReleaser(CompiledPageActionDescriptor descriptor)
{
return (context, pageModel) => (pageModel as IDisposable)?.Dispose();
}
private object _CreatePageModelType(PageContext context, Type pageModelType)
{
// Scoped components for Identity Core
IUserStore<ApplicationUser> userStore = _services.GetRequiredService<IUserStore<ApplicationUser>>();
UserManager<ApplicationUser> userManager = _services.GetRequiredService<UserManager<ApplicationUser>>()!;
SignInManager<ApplicationUser> signInManager = _services.GetRequiredService<SignInManager<ApplicationUser>>()!;
// Create Transient components
switch (pageModelType.Name)
{
case nameof(IndexModel):
return new IndexModel();
case nameof(LoginModel):
return new LoginModel(userManager, signInManager, Logger<LoginModel>());
default: throw new NotImplementedException(pageModelType.FullName);
}
}
public void Dispose()
{
// Release singleton components here, if needed
}
private ILogger<T> Logger<T>() => this._loggerFactory.CreateLogger<T>();
接下来我尝试创建一个 IServiceScope
来解析我的范围服务:
...
private object _CreatePageModelType(PageContext context, Type pageModelType)
{
using (var scope = _services.CreateScope())
{
// Scoped components for Identity Core
IUserStore<ApplicationUser> userStore = scope.ServiceProvider.GetRequiredService<IUserStore<ApplicationUser>>();
UserManager<ApplicationUser> userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>()!;
SignInManager<ApplicationUser> signInManager = scope.ServiceProvider.GetRequiredService<SignInManager<ApplicationUser>>()!;
// Create Transient components
switch (pageModelType.Name)
{
case nameof(IndexModel):
return new IndexModel();
case nameof(LoginModel):
return new LoginModel(userManager, signInManager, Logger<LoginModel>());
default: throw new NotImplementedException(pageModelType.FullName);
}
}
}
...
此方法成功创建了作用域服务并将它们注入到我的 LoginModel
中,但是,在它们被注入之后,它们被处理掉了,我收到了以下异常:
ObjectDisposedException: Cannot access a disposed object. Object name: 'UserManager`1'.
有没有办法将作用域服务注入自定义 PageModelActivatorProvider
?
如评论中所述,将 IHttpContextAccessor
注入单例服务,并使用 httpContextAccessor.HttpContext.RequestServices
获取当前请求的 IServiceProvider
,您可以使用它来解析范围服务。
public sealed class WebUIPageModelActivatorProvider : IPageModelActivatorProvider, IDisposable
{
private readonly IHttpContextAccessor _httpContextAccessor;
public WebUIPageModelActivatorProvider(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
}
private object _CreatePageModelType(PageContext context, Type pageModelType)
{
IServiceProvider services = _httpContextAccessor.HttpContext.RequestServices;
// Scoped components for Identity Core
IUserStore<ApplicationUser> userStore = services.GetRequiredService<IUserStore<ApplicationUser>>();
UserManager<ApplicationUser> userManager = services.GetRequiredService<UserManager<ApplicationUser>>()!;
SignInManager<ApplicationUser> signInManager = services.GetRequiredService<SignInManager<ApplicationUser>>()!;
...
我正在开发 ASP.NET 核心应用程序并使用自定义 PageModelActivatorProvider
to create my razor pages. This app has user's and implements Identity using AddIdentityCore
。我还添加了 SignInManager
和身份验证:
builder.Services.AddIdentityCore<ApplicationUser>(options =>
{
options.SignIn.RequireConfirmedEmail = true;
options.User.RequireUniqueEmail = true;
})
.AddSignInManager<CustomSignInManager>();
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignOutScheme = IdentityConstants.ApplicationScheme;
})
.AddCookie(IdentityConstants.ApplicationScheme, options =>
{
options.LoginPath = new PathString("/Account/Login");
options.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync
};
});
我创建了一个自定义 UserStore
并将其作为范围服务添加到我的 Program.cs
文件中:
string connectionString = builder.Configuration.GetConnectionString("connectionString");
builder.Services.AddSingleton<IPageModelActivatorProvider, WebUIPageModelActivatorProvider>();
builder.Services.AddScoped<IUserRepository>(_ => new UserRepository(connectionString));
builder.Services.AddScoped<IUserStore<ApplicationUser>, UserStore>();
UserManager
和 SignInManager
classes 分别作为作用域服务添加到 AddIdentityCore
和 AddSignInManager
方法中。
我的 PageModelActivatorProvider
class 创建了一个 LoginModel
页面模型,它需要一个 UserManager
和一个 SignInManager
。这两个都是作用域服务,但是我的 PageModelActivatorProvider
被注册为单例服务,所以我不能通过构造函数注入来注入 UserManager
和 SignInManager
。当我尝试使用 IServiceProvider
到 GetRequiredService
时,出现以下异常:
'Cannot resolve scoped service 'Microsoft.AspNetCore.Identity.IUserStore`1[DataAccess.ApplicationUser]' from root provider.'
我得到了 UserManager
和 SignInManager
相同的异常,因为它们都被注册为作用域服务,我试图在单例服务中访问它们。
public sealed class WebUIPageModelActivatorProvider : IPageModelActivatorProvider, IDisposable
{
private readonly IServiceProvider _services;
public WebUIPageModelActivatorProvider(IServiceProvider services)
{
// Create Singleton components
_services = services;
}
public Func<PageContext, object> CreateActivator(CompiledPageActionDescriptor descriptor)
{
return (context) => _CreatePageModelType(context, descriptor.ModelTypeInfo!.AsType());
}
public Action<PageContext, object>? CreateReleaser(CompiledPageActionDescriptor descriptor)
{
return (context, pageModel) => (pageModel as IDisposable)?.Dispose();
}
private object _CreatePageModelType(PageContext context, Type pageModelType)
{
// Scoped components for Identity Core
IUserStore<ApplicationUser> userStore = _services.GetRequiredService<IUserStore<ApplicationUser>>();
UserManager<ApplicationUser> userManager = _services.GetRequiredService<UserManager<ApplicationUser>>()!;
SignInManager<ApplicationUser> signInManager = _services.GetRequiredService<SignInManager<ApplicationUser>>()!;
// Create Transient components
switch (pageModelType.Name)
{
case nameof(IndexModel):
return new IndexModel();
case nameof(LoginModel):
return new LoginModel(userManager, signInManager, Logger<LoginModel>());
default: throw new NotImplementedException(pageModelType.FullName);
}
}
public void Dispose()
{
// Release singleton components here, if needed
}
private ILogger<T> Logger<T>() => this._loggerFactory.CreateLogger<T>();
接下来我尝试创建一个 IServiceScope
来解析我的范围服务:
...
private object _CreatePageModelType(PageContext context, Type pageModelType)
{
using (var scope = _services.CreateScope())
{
// Scoped components for Identity Core
IUserStore<ApplicationUser> userStore = scope.ServiceProvider.GetRequiredService<IUserStore<ApplicationUser>>();
UserManager<ApplicationUser> userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>()!;
SignInManager<ApplicationUser> signInManager = scope.ServiceProvider.GetRequiredService<SignInManager<ApplicationUser>>()!;
// Create Transient components
switch (pageModelType.Name)
{
case nameof(IndexModel):
return new IndexModel();
case nameof(LoginModel):
return new LoginModel(userManager, signInManager, Logger<LoginModel>());
default: throw new NotImplementedException(pageModelType.FullName);
}
}
}
...
此方法成功创建了作用域服务并将它们注入到我的 LoginModel
中,但是,在它们被注入之后,它们被处理掉了,我收到了以下异常:
ObjectDisposedException: Cannot access a disposed object. Object name: 'UserManager`1'.
有没有办法将作用域服务注入自定义 PageModelActivatorProvider
?
如评论中所述,将 IHttpContextAccessor
注入单例服务,并使用 httpContextAccessor.HttpContext.RequestServices
获取当前请求的 IServiceProvider
,您可以使用它来解析范围服务。
public sealed class WebUIPageModelActivatorProvider : IPageModelActivatorProvider, IDisposable
{
private readonly IHttpContextAccessor _httpContextAccessor;
public WebUIPageModelActivatorProvider(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
}
private object _CreatePageModelType(PageContext context, Type pageModelType)
{
IServiceProvider services = _httpContextAccessor.HttpContext.RequestServices;
// Scoped components for Identity Core
IUserStore<ApplicationUser> userStore = services.GetRequiredService<IUserStore<ApplicationUser>>();
UserManager<ApplicationUser> userManager = services.GetRequiredService<UserManager<ApplicationUser>>()!;
SignInManager<ApplicationUser> signInManager = services.GetRequiredService<SignInManager<ApplicationUser>>()!;
...