AspNetCore 中间件 UserManager 依赖注入

AspNetCore Middleware UserManager Dependency Injection

我有一个多层应用程序,我开始在 ASP.NET Core 1.1 中编写它,我一直在学习它。我像以前在 Web API 中完成的应用程序一样组织它,我有主机服务(网络核心应用程序)、业务层和数据库之上的数据层。业务层和数据层是 net core 标准库,但是当我想添加 entity framework 时,我必须修改数据层以使其看起来像 net core 应用程序,所以现在我有 Startup.cs 和那里的配置。这使我能够配置 entity framework 服务并在数据层中创建迁移。但是现在我有一个问题,因为我想添加 asp.net 身份。网上的每个教程都是关于在一个项目中包含所有内容的 SPA。 我已将身份添加到 Startup.cs 并且数据库生成良好

public void ConfigureServices(IServiceCollection services)
{
    var connectionString = Configuration.GetConnectionString("DefaultConnection");
    services.AddEntityFramework(connectionString);
    services.AddMyIdentity();
    services.Configure<IdentityOptions>(options =>
    {
        // Password settings
        options.Password.RequireDigit = true;
        options.Password.RequiredLength = 8;
        options.Password.RequireNonAlphanumeric = false;
        options.Password.RequireUppercase = true;
        options.Password.RequireLowercase = false;

        // Lockout settings
        options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
        options.Lockout.MaxFailedAccessAttempts = 10;


        // User settings
        options.User.RequireUniqueEmail = true;
    });
}
public void Configure(IApplicationBuilder app)
{
    app.UseIdentity();
}

但现在我需要从 class 中使用 UserManager,它不是控制器,我不知道如何处理依赖注入。 为了更好地解释,我的主机服务中有一个帐户控制器

[HttpPost]
[Route("Register")]
public async Task<IActionResult> Register([FromBody]RegisterUserDto dto)
{
    var result = await Business.Commands.Accounts.Register(dto);
    return Ok(result);
}

业务层只是调用数据层

public async static Task<ResponseStatusDto> Register(RegisterUserDto dto)
{
    // some code here        
    var identityLogon = await Data.Commands.ApplicationUsers.Register(dto);
    // some code here as well

    return new ResponseStatusDto();
}

现在的问题是,如何在Data Register方法中获取UserManager?这是一个简单的 class,它不是从控制器继承的,依赖注入不适用于此处示例中的构造函数 Core Identity

public class AccountController : Controller
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly SignInManager<ApplicationUser> _signInManager;
    private readonly IEmailSender _emailSender;
    private readonly ISmsSender _smsSender;
    private static bool _databaseChecked;
    private readonly ILogger _logger;

    public AccountController(
        UserManager<ApplicationUser> userManager,
        SignInManager<ApplicationUser> signInManager,
        IEmailSender emailSender,
        ISmsSender smsSender,
        ILoggerFactory loggerFactory)
    {
        _userManager = userManager;
        _signInManager = signInManager;
        _emailSender = emailSender;
        _smsSender = smsSender;
        _logger = loggerFactory.CreateLogger<AccountController>();
    }

    //
    // GET: /Account/Login

那么,我如何将启动中配置的 UserManager 传递给中间件中某个随机 class 的某处?我已经看到了这个 question,但是仅将 null 值传递给 UseManager 构造函数的答案不起作用,我认为这很好。

//根据 Set 的回答进行编辑

我已经删除了所有静态引用,但我还没有完成。我已遵循 this 依赖注入说明,但我不确定如何实例化和调用 Add 方法。

我创建了一个界面

public interface IIdentityTransaction
{
    Task<IdentityResult> Add(ApplicationUser appUser, string password);
}

并实施了它

public class IdentityTransaction : IIdentityTransaction
{
    private readonly ApplicationDbContext _dbContext;

    private readonly UserManager<ApplicationUser> _userManager;
    private readonly RoleManager<IdentityRole> _roleManager;


    public IdentityTransaction(ApplicationDbContext context, UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager)
    {
        _roleManager = roleManager;
        _userManager = userManager;
        _dbContext = context;
    }

    public async Task<IdentityResult> Add(ApplicationUser applicationUser, string password)
    {
        return await _userManager.CreateAsync(applicationUser, password);
    }

}

然后我将其注入 Startup.cs

中的服务集合
services.AddScoped<IIdentityTransaction, IdentityTransaction>();

但是如何从 IdentityTransaction 服务调用 Add 方法?

我无法实例化它,也无法在构造函数上使用依赖注入,因为它只是循环我的问题。 @Set 提到

or pass UserManager userManager as parameter to method pass it from where?

我想我已经很接近了,但是我遗漏了一些东西。 我试过使用

    IIdentityTransaction it = services.GetRequiredService<IIdentityTransaction>();

但是 IServiceProvider 服务是空的,我也不知道从哪里得到它。

ASP.NET 核心中的 DI 对于控制器和非控制器 class 使用 "constructor injection" 方法相同。

您遇到问题,因为 Register 方法是 static,因此无法访问实例 variables/properties。你需要

  • 使Register方法非静态
  • 或将UserManager<ApplicationUser> userManager作为参数传递给方法

一般来说,您应该避免将静态 classes 用于业务逻辑,因为它们无助于正确测试您的代码和产生代码耦合。通过 internet/SO 搜索,你会发现很多关于为什么静态不好的话题。

使用 DI 在您的控制器中获取 Data.Commands.ApplicationUsers class 的实例。如果您的应用程序只需要此 class 的一个实例 - 请为其使用单例生命周期。


更新。再次,使用构造函数注入:修改你的 "Data Layer" class 以便它可以获得 IIdentityTransaction 的实例作为构造函数参数:

public class YourDataLayerClass : IYourDataLayerClass
{
    private IIdentityTransaction _identityTransaction;
    public YourDataLayerClass(IIdentityTransaction identityTransaction)
    {
       _identityTransaction = identityTransaction;
    }

    public void MethodWhereYouNeedToCallAdd()
    {
        _identityTransaction.Add(...);
    }
}

IYourDataLayerClass 实例的想法相同:注册依赖项

services.AddScoped<IYourDataLayerClass, YourDataLayerClass>();

然后依赖于它的 class(在你的情况下是中间件,如果我理解正确的话)应该通过构造函数接收该实例:

public class YourMiddleware
{
    private IYourDataLayerClass _yourDataLayerClass;
    public YourMiddleware(IYourDataLayerClass yourDataLayerClass)
    {
       _yourDataLayerClass = yourDataLayerClass;
    }
    ...
}

是的,你们很亲近。

首先,要么从 IdentityTransaction 构造函数中删除上下文参数,因为在您的代码中删除它似乎没有用。或者如果你打算以后使用它,在DI容器中声明:

services.AddScoped<ApplicationDbContext, ApplicationDbContext>();

第二,您只需在控制器的构造函数中添加 IIdentityTransaction 作为依赖项,并从其依赖项中删除 SignInManager 和 UserManager,因为最终您不会在控制器中直接使用它们:

public class AccountController : Controller
{
    private readonly IEmailSender _emailSender;
    private readonly ISmsSender _smsSender;
    private static bool _databaseChecked;
    private readonly ILogger _logger;
    IIdentityTransaction _identityTransaction;

    public AccountController(
        IEmailSender emailSender,
        ISmsSender smsSender,
        ILoggerFactory loggerFactory,
        IIdentityTransaction identityTransaction)
    {
        _emailSender = emailSender;
        _smsSender = smsSender;
        _logger = loggerFactory.CreateLogger<AccountController>();
        _identityTransaction = identityTransaction;
    }

如果控制器之间需要额外的业务层(IBusinessLayer),同样的流程,启动时在DI容器中声明class,在业务class构造函数中添加IIdentityTransaction作为依赖,并将控制器的依赖项从 IIdentityTransaction 更新为 IBusinessLayer。

更精确。

services.AddScoped<IIdentityTransaction, IdentityTransaction>();

这段代码不注入实例或依赖项。它在 DI 容器中声明了一个接口及其关联的实现,因此可以在需要时稍后注入。当实际创建需要它们的对象时,将注入实际实例。 IE。控制器在实例化时会注入其依赖项。

 IIdentityTransaction it = services.GetRequiredService<IIdentityTransaction>();

您在此处尝试执行的操作称为依赖定位器模式,通常被视为反模式。你应该坚持通过构造函数进行依赖注入,它更干净。

关键是在启动时声明DI容器中的所有东西,甚至是你自定义的business/data层classes,永远不要再自己实例化它们,并在任何[=中将它们声明为必需的依赖项31=]es 的构造函数需要它们。