AspNetCore中间件UserManager依赖注入

3

我有一个多层应用程序,开始时使用ASP.NET Core 1.1编写,但我仍在学习过程中。我像以前做的Web API一样组织了它,我有主机服务(net core app),业务层和数据层,这些都在数据库之上。业务和数据层是net core标准库,但当我想添加实体框架时,我不得不修改数据层,使其看起来像net core应用程序,因此现在我有了Startup.cs和配置。这使我能够配置实体框架服务并在数据层创建迁移。但现在我有一个问题,因为我想添加asp.net身份验证。网上的每个教程都是关于将所有内容放在一个项目中的SPAs。 我已经将身份验证添加到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();
}

但现在我需要从不是控制器的类中使用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?它是一个简单的类,没有从控制器继承,也不像这里的例子那样可以通过构造函数进行依赖注入。请参考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传递给中间某个随机类?我看到这个问题,但是直接将null值传递给UseManager构造函数的答案既不起作用,也不好。//根据Set的答案进行编辑。 我已经删除了所有的静态引用,但我还没有完全解决问题。我遵循了这个依赖注入说明,但是我不知道如何实例化并调用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方法呢?

enter image description here

我无法实例化它,也不能在构造函数中使用依赖注入,因为这只会让我的问题循环。@Set提到:

或将UserManager userManager作为参数传递给方法 从哪里传递?

我认为我很接近答案,但是还缺少一些东西。 我已经尝试过使用:

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

但是,服务(即IServiceProvider)为空,我也不知道从哪里获取它。
2个回答

2

在ASP.NET Core中,对于控制器和非控制器类,DI采用“构造函数注入”方法实现。

您遇到的问题是Register方法是static的,因此无法访问实例变量/属性。您需要:

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

通常应避免使用静态类来处理业务逻辑,因为它们不能很好地测试代码并产生代码耦合。搜索互联网/SO,您会发现很多关于静态的坏处的主题。

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


更新。再次使用构造函数注入:修改您的“数据层”类,以便可以将IIdentityTransaction的实例作为构造函数参数获取:

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

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

同样的想法适用于 IYourDataLayerClass 实例:注册依赖项。
services.AddScoped<IYourDataLayerClass, YourDataLayerClass>();

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

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

@Pierre Murasso 给出了类似的答案,但我不确定我完全理解了。如果我按照您的建议,则尝试实例化 YourMiddleware 类的 Business 层将要求我为构造函数提供 IYourDataLayerClass。而我在 Business 层中没有它。我在 Business 中添加它作为构造函数参数,那么 Host Account Controller 需要提供它吗?然后呢?我在 Host 中添加对 Data Layer 的引用,在 Host Startup 中进行配置并从那里推送? - Милан

1

是的,你非常接近了。

首先,要么从IdentityTransaction构造函数中删除context参数,因为在你的代码片段中它似乎是无用的。或者如果你计划稍后使用它,请在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容器中声明该类,将IIdentityTransaction添加为业务类构造函数的依赖项,并将控制器的依赖关系从IIdentityTransaction更新为IBusinessLayer。
还有一些更精确定义。
services.AddScoped<IIdentityTransaction, IdentityTransaction>();

这段代码并不注入实例或依赖项。它在 DI 容器中声明一个接口及其关联的实现,以便在需要时可以注入。实际的实例是在实际创建需要它们的对象时注入的。即控制器在实例化时会注入其依赖项。
 IIdentityTransaction it = services.GetRequiredService<IIdentityTransaction>();

你在这里尝试的是依赖定位器模式,通常被视为反模式。你应该坚持使用构造函数进行依赖注入,这样更加清晰简洁。
关键是在启动时将所有内容都声明在 DI 容器中,包括你自定义的业务/数据层类,不再手动实例化它们,并在需要它们的任何类的构造函数中声明它们作为必需的依赖项。

1
我建议每个层在其构造函数中声明其所需的依赖关系:Controller --> Business,Business --> DataLayer。接口/实现应在主机应用程序的startup.cs中的DI容器中声明。 - Pierre Murasso
你应该在主机的startup.cs中设置所有内容。 - Pierre Murasso
ApplicationDbContext必须在数据层中声明,因为从不同的程序集引用数据模型存在问题。直到我按照上述教程操作后,我才解决了这些问题。我认为我不应该从主机到数据进行引用(在构建类似Web API的项目时没有这样做),但如果这是一个解决方法,我需要在此阶段实施,那就可以。 - Милан
1
如果你在谈论程序集引用,那么间接地有一个引用图,即主机-->业务-->数据,数据层DLL最终会出现在主机的bin目录下,以便在运行时加载。即使是使用Web API,这种行为也是一样的。 - Pierre Murasso
为什么你认为它们来自Nuget包?我猜不是。他定义了两个接口(EmailSender和ISmsSender)及其实现。 - Pierre Murasso
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接