Solución:
Si realmente no necesita el UserManager
en el constructor, puede almacenar una referencia al IServiceProvider
en lugar de:
private IHttpContextAccessor _contextAccessor { get; set; }
public ApplicationUser ApplicationUser { get; set; }
private IServiceProvider _services;
public ApplicationContext(DbContextOptions<ApplicationContext> options,
IHttpContextAccessor contextAccessor, IServiceProvider services)
: base(options)
{
_contextAccessor = contextAccessor;
var user = _contextAccessor.HttpContext.User;
_services = services;
}
Luego, más tarde, cuando realmente necesite el ApplicationUser
, llame por ejemplo GetRequiredService<ApplicationUser>()
(definido en Microsoft.Extensions.DependencyInjection
):
var manager = _services.GetRequiredService<UserManager<ApplicationUser>>();
var user = manager.Users.FirstOrDefault(u => u.Id == _userManager.GetUserId(user));
Por supuesto, puede utilizar un Lazy<T>
para cargar de forma diferida el administrador o el usuario la primera vez y luego almacenar una referencia a él.
En general, @poke tiene razón sobre la reestructuración para evitar tales dependencias circulares, pero dejar esta respuesta aquí en caso de que alguien más tenga un problema similar y la refactorización no es una opción.
Las dependencias circulares suelen ser una señal de un diseño de aplicación inadecuado, que debe revisarse. Como ya mencioné en los comentarios, tener un base de datos El contexto que depende del administrador de usuarios no parece ser una buena idea. Esto me hace suponer que el contexto de su base de datos no demasiado y probablemente viola el principio de responsabilidad única.
Con solo mirar las dependencias del contexto de su base de datos, ya está agregando demasiado estado específico de la aplicación allí: no solo tiene una dependencia en el administrador de usuarios, sino también en el acceso al contexto HTTP; y está resolviendo el contexto HTTP también inmediatamente en el constructor (que generalmente no es la mejor idea).
De su extracto de código, parece que desea recuperar el usuario actual para su uso posterior. Si desea utilizar esto, por ejemplo, para filtrar consultas para el usuario, entonces debería pensar si es realmente una buena idea integrarlo estáticamente en la instancia de contexto de la base de datos. Considere aceptar un ApplicationUser
métodos internos en lugar de. De esa manera, se deshace de todas esas dependencias, hace que el contexto de su base de datos sea mejor comprobable (ya que el usuario ya no es un estado del contexto), y también aclara la responsabilidad única del contexto:
public IList<Thing> GetThings (ApplicationUser user)
{
// just an example…
return Things.Where(t => t.UserId == user.Id).ToList();
}
Tenga en cuenta que esto es además Inversión de control. En lugar de tener el contexto de la base de datos activamente recuperar el usuario que debe consultar (lo que agregaría otra responsabilidad, violando el SRP), espera que se le pase al usuario que debe consultar, moviendo el control al código de llamada.
Ahora, si consulta cosas para el usuario actual con mucha frecuencia, puede resultar algo molesto resolver el usuario actual en un controlador y luego pasarlo al contexto de la base de datos. En ese caso, crea un servicio para no repetirte más. Ese servicio puede depender del contexto de la base de datos y otras cosas para descubrir al usuario actual.
Pero simplemente aclarando el contexto de su base de datos no debería hacer es suficiente para solucionar esta dependencia circular.
Muchas gracias a Toby por la solución. También puedes usar Lazy<IMyService>
para evitar llamar _services.GetRequiredService<UserManager<ApplicationUser>>()
cada vez que quieras usarlo.
private IHttpContextAccessor _contextAccessor { get; set; }
public ApplicationUser ApplicationUser { get; set; }
private Lazy<UserManager<ApplicationUser>> _userManager;
public ApplicationContext(DbContextOptions<ApplicationContext> options,
IHttpContextAccessor contextAccessor, IServiceProvider services)
: base(options)
{
_contextAccessor = contextAccessor;
var user = _contextAccessor.HttpContext.User;
_userManager = new Lazy<UserManager<ApplicationUser>>(() =>
services.GetRequiredService<UserManager<ApplicationUser>>());
}
y cuando quieras usarlo solo di:
_userManager.value.doSomeThing();