我希望我的ApplicationContext
构造函数将UserManager
作为参数,但是依赖注入有问题。
码:
public class ApplicationContext : IdentityDbContext<ApplicationUser>
{
private IHttpContextAccessor _contextAccessor { get; set; }
public ApplicationUser ApplicationUser { get; set; }
private UserManager<ApplicationUser> _userManager;
public ApplicationContext(DbContextOptions<ApplicationContext> options, IHttpContextAccessor contextAccessor, UserManager<ApplicationUser> userManager)
: base(options)
{
_contextAccessor = contextAccessor;
var user = _contextAccessor.HttpContext.User;
_userManager = userManager;
ApplicationUser = _userManager.Users.FirstOrDefault(u => u.Id == _userManager.GetUserId(user));
}
}
在startup.cs中
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddDbContext<ApplicationContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), b => b.MigrationsAssembly("RCI.App")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationContext>()
.AddDefaultTokenProviders();
services.AddAuthentication();
services.AddMvc();
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
services.AddTransient<IHttpContextAccessor, HttpContextAccessor>();
services.AddOptions();
}
错误代码:
检测到类型为“Microsoft.AspNetCore.Identity.UserManager`1 [RCI.App.Models.ApplicationUser]”的服务的循环依赖关系。
谁能指出我做错了什么?
循环依赖通常是应用程序设计不当的标志,应该进行修改。正如我在评论中已经提到的,拥有依赖于用户管理器的数据库上下文似乎不是一个好主意。这使我假设你的数据库上下文做得太多,可能违反了single-responsibility principle。
只是查看数据库上下文的依赖关系,您已经在其中添加了太多特定于应用程序的状态:您不仅依赖于用户管理器,还依赖于HTTP上下文访问器;并且您也在构造函数中立即解析HTTP上下文(这通常不是最好的主意)。
从您的代码摘录中,您似乎想要检索当前用户以供以后使用。如果您想使用它来过滤用户的查询,那么您应该考虑将其静态烘焙到数据库上下文实例中是否真的是个好主意。考虑接受内部方法中的ApplicationUser
。这样,您可以摆脱所有这些依赖关系,使您的数据库上下文更易于测试(因为用户不再是上下文的状态),并且您还可以更清楚地了解上下文的单一责任:
public IList<Thing> GetThings (ApplicationUser user)
{
// just an example…
return Things.Where(t => t.UserId == user.Id).ToList();
}
请注意,这也是inversion of control。它不应该让数据库上下文主动检索它应该查询的用户(这会增加另一个责任,违反SRP),而是希望传递给它应该查询的用户,将控件移动到调用代码。
现在,如果您经常查询当前用户的内容,解析控制器中的当前用户然后将其传递给数据库上下文可能会有些烦人。在这种情况下,创建一个服务no longer repeat yourself。然后,该服务可以依赖于数据库上下文和其他东西来确定当前用户。
但是,只是从它不应该做的事情中清除数据库上下文就足以解决这个循环依赖。
如果你实际上不需要构造函数中的UserManager
,你可以改为存储对IServiceProvider
的引用:
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;
}
然后,当你真正需要ApplicationUser
时,请致电,例如GetRequiredService<ApplicationUser>()
(在Microsoft.Extensions.DependencyInjection
中定义):
var manager = _services.GetRequiredService<UserManager<ApplicationUser>>();
var user = manager.Users.FirstOrDefault(u => u.Id == _userManager.GetUserId(user));
当然,您可以使用Lazy<T>
第一次延迟加载管理器或用户,然后存储对它的引用。
一般来说,@ poke对于重新构建是正确的,以避免这种循环依赖,但在这里留下这个答案以防其他人有类似的问题,并且重构不是一个选项。
非常感谢托比,寻求解决方案。您也可以使用Lazy<IMyService>
来防止每次使用它时调用_services.GetRequiredService<UserManager<ApplicationUser>>()
。
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>>());
}
当你想用它时,只说:
_userManager.value.doSomeThing();