I am working on an ASP.NET Core app and am using a custom PageModelActivatorProvider
to create my razor pages. This app has user's and implements Identity using AddIdentityCore
. I have added a SignInManager
and authentication as well:
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
};
});
I have created a custom UserStore
and added it as a scoped service within my Program.cs
file:
string connectionString = builder.Configuration.GetConnectionString("connectionString");
builder.Services.AddSingleton<IPageModelActivatorProvider, WebUIPageModelActivatorProvider>();
builder.Services.AddScoped<IUserRepository>(_ => new UserRepository(connectionString));
builder.Services.AddScoped<IUserStore<ApplicationUser>, UserStore>();
The UserManager
and SignInManager
classes are added as scoped services within AddIdentityCore
and AddSignInManager
methods, respectively.
My PageModelActivatorProvider
class creates a LoginModel
page model, which takes a UserManager
and a SignInManager
. Both of these are scoped services, however my PageModelActivatorProvider
is registered as a singleton service, so I cannot inject the UserManager
and SignInManager
via constructor injection. When I try using the IServiceProvider
to GetRequiredService
, I get the following exception:
'Cannot resolve scoped service 'Microsoft.AspNetCore.Identity.IUserStore`1[DataAccess.ApplicationUser]' from root provider.'
I get the same exception for UserManager
and SignInManager
because they are all registered as scoped services and I am trying to access them within a singleton service.
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>();
Next I tried creating an IServiceScope
to resolve my scoped services:
...
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);
}
}
}
...
This method successfully created the scoped services and injected them into my LoginModel
, however, after they were injected, they were disposed of and I received the following exception:
ObjectDisposedException: Cannot access a disposed object. Object name: 'UserManager`1'.
Is there a way to inject a scoped service into a custom PageModelActivatorProvider
?
CodePudding user response:
As discussed in the comments, inject the IHttpContextAccessor
into the singleton service, and use httpContextAccessor.HttpContext.RequestServices
to get the IServiceProvider
for the current request which you can use to resolve your scoped services.
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>>()!;
...