依赖注入循环依赖.net核心2.0

时间:2017-09-21 22:24:48

标签: c# dependency-injection asp.net-core asp.net-core-mvc circular-dependency

我希望我的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();

}

错误代码:

  

检测到类型服务的循环依赖关系   &#39; Microsoft.AspNetCore.Identity.UserManager`1 [RCI.App.Models.ApplicationUser]&#39;

有人能指出我做错了吗?

3 个答案:

答案 0 :(得分:9)

如果您实际上不需要构造函数中的 <img src="@Model.Pic1" class="img-responsive" width="750" height="750"/> <img src="@Model.Pic2" class="img-responsive" width="750" height="750"/> <img src="@Model.Pic3" class="img-responsive" width="750" height="750"/> <img src="@Model.Pic4" class="img-responsive" width="750" height="750"/> <img src="@Model.Pic5" class="img-responsive" width="750" height="750"/> ,则可以改为存储对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)); 第一次延迟加载管理员或用户,然后存储对它的引用。

一般来说,@ poke对于重新架构是正确的,以避免这种循环依赖,但在这里留下这个答案以防其他人有类似的问题,并且重构不是一个选项。

答案 1 :(得分:6)

循环依赖通常是应用程序设计不当的标志,应该进行修改。正如我在评论中已经提到的,拥有依赖于用户管理器的数据库上下文似乎不是一个好主意。这使我假设您的数据库上下文太多并且可能违反了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的服务。然后,该服务可以依赖于数据库上下文和其他东西来确定当前用户。

但是只是从不应该做的事情中清除数据库上下文就足以解决这个循环依赖。

答案 2 :(得分:2)

非常感谢Toby提供的解决方案。您还可以使用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();