有没有办法将作用域服务注入自定义控制器激活器(或页面模型激活器提供程序)?

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>();

UserManagerSignInManager classes 分别作为作用域服务添加到 AddIdentityCoreAddSignInManager 方法中。

我的 PageModelActivatorProvider class 创建了一个 LoginModel 页面模型,它需要一个 UserManager 和一个 SignInManager。这两个都是作用域服务,但是我的 PageModelActivatorProvider 被注册为单例服务,所以我不能通过构造函数注入来注入 UserManagerSignInManager。当我尝试使用 IServiceProviderGetRequiredService 时,出现以下异常:

'Cannot resolve scoped service 'Microsoft.AspNetCore.Identity.IUserStore`1[DataAccess.ApplicationUser]' from root provider.'

我得到了 UserManagerSignInManager 相同的异常,因为它们都被注册为作用域服务,我试图在单例服务中访问它们。

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>>()!;

        ...