依赖注入循环依赖.NET Core 2.0

Posted

技术标签:

【中文标题】依赖注入循环依赖.NET Core 2.0【英文标题】:Dependency Injection circular dependency .NET Core 2.0 【发布时间】:2018-03-03 09:40:17 【问题描述】:

我希望我的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]'。

谁能指出我做错了什么?

【问题讨论】:

UserManager&lt;ApplicationUser&gt;ApplicationContext 在您的示例中彼此具有显式依赖关系,从而导致循环依赖关系。在解析ApplicationContext 时,它必须创建一个需要ApplicationContextUserManager&lt;ApplicationUser&gt;。你知道这是怎么回事吗? database 上下文中依赖用户管理器听起来不是一个好主意。您可能应该有一个服务,而不是依赖于数据库上下文和用户管理器。 @rory 您已经可以在应用程序上下文中访问Users。使用当前请求的用户 ID 直接查询。根本不需要引用用户管理器。 UserManager&lt;T&gt; 依赖于 UserStore&lt;T&gt;,它依赖于使用 ASP.NET Core Identity 注册的数据库上下文,而这恰好是依赖于用户管理器的数据库上下文。 @rory 您还应该保持ApplicationContext 构造函数精简,不要尝试访问用户或在其中进行查询。提取用户并在目标方法中进行查询,因为届时请求将完全实现。它基本上应该只有_contextAccessor = contextAccessor;,其余的应该在其中一个crud操作上完成。 【参考方案1】:

如果您在构造函数中实际上不需要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&lt;ApplicationUser&gt;()(定义在Microsoft.Extensions.DependencyInjection):

var manager = _services.GetRequiredService<UserManager<ApplicationUser>>();
var user = manager.Users.FirstOrDefault(u => u.Id == _userManager.GetUserId(user));

当然,您可以使用Lazy&lt;T&gt; 第一次延迟加载管理器或用户,然后存储对它的引用。

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

【讨论】:

谢谢@Tobias J 这是我在互联网上找到的实现和理解的最简单的答案。非常感谢。【参考方案2】:

循环依赖通常是应用程序设计不当的标志,应该对其进行修改。正如我在 cmets 中已经提到的,拥有一个依赖于用户管理器的 数据库 上下文似乎不是一个好主意。这让我假设您的数据库上下文太多并且可能违反了single-responsibility principle。

仅查看数据库上下文的依赖关系,您已经在其中添加了太多特定于应用程序的状态:您不仅依赖于用户管理器,还依赖于 HTTP 上下文访问器;并且您也在构造函数中立即解析 HTTP 上下文(这通常不是最好的主意)。

从您的代码摘录看来,您希望检索当前用户以供以后使用。例如,如果您想使用它来过滤用户的查询,那么您应该考虑将它静态地烘焙到数据库上下文实例中是否真的是一个好主意。考虑改为接受ApplicationUser inside methods。这样,您就摆脱了所有这些依赖关系,使您的数据库上下文更好地可测试(因为用户不再是上下文的 state),并且您还使上下文的单一责任更加清晰:

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 创建一个服务。然后,该服务可以依赖数据库上下文和其他内容来确定当前用户。

但是仅仅从它不应该做的事情中清除你的数据库上下文就足以修复这种循环依赖。

【讨论】:

【参考方案3】:

非常感谢 Toby 提供的解决方案。您也可以使用Lazy&lt;IMyService&gt; 来防止每次您想使用它时调用_services.GetRequiredService&lt;UserManager&lt;ApplicationUser&gt;&gt;()

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

【讨论】:

以上是关于依赖注入循环依赖.NET Core 2.0的主要内容,如果未能解决你的问题,请参考以下文章

详解.NET Core 依赖注入生命周期

ASP.NET Core 依赖注入基本用法

ASP.NET Core中的依赖注入:依赖注入(DI)

ASP.NET Core 依赖注入(DI)

[ASP.NET Core 3框架揭秘] 依赖注入:一个Mini版的依赖注入框架

ASP.Net Core 使用啥依赖注入框架?