在中间件中访问DbContext.净5

本文关键字:DbContext 访问 中间件 | 更新日期: 2023-09-27 18:18:41

我编写了我的自定义中间件,我添加在

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    //...
    app.UseAutologin();
    app.UseMvc(routes =>
    {
       //...

所以它是Mvc开始发挥作用之前的最后一个中间件。

在我的中间件的Invoke方法中,我想(间接)访问DbContext

 public async Task Invoke(HttpContext context)
  {
     if (string.IsNullOrEmpty(context.User.Identity.Name))
     {
        var applicationContext = _serviceProvider.GetService<ApplicationDbContext>();
        var signInManager = _serviceProvider.GetService<SignInManager<ApplicationUser>>();
        var result = await signInManager.PasswordSignInAsync(_options.UserName, _options.Password, true, false);
     }
     await _next(context);
  }

几乎每次我得到以下异常:

InvalidOperationException:尝试使用上下文当它被配置时。不能使用DbContext实例

OnConfiguring内部,因为它仍然在配置中。

现在,这显然是由PasswordSignInAsync方法引发的。但是我如何确保在做这些事情之前已经创建了模型呢?

也许我没有完全清楚:我不想自己使用DbContext - PasswordSignInAsync在验证用户和密码时使用它。

在中间件中访问DbContext.净5

如果通过Invoke方法注入ApplicationDbContextSignInManager<ApplicationUser>会怎么样:

public async Task Invoke(HttpContext context, ApplicationDbContext applicationContext, SignInManager<ApplicationUser> signInManager)
{
    if (string.IsNullOrEmpty(context.User.Identity.Name))
    {
        var result = await signInManager.PasswordSignInAsync(_options.UserName, _options.Password, true, false);
    }
    await _next(context);
}

这样可以从正确的作用域解析服务。我注意到你实际上没有在任何地方使用ApplicationDbContext,只使用SignInManager。你真的需要吗?

这个错误很可能发生,因为任何中间件都充当单例。您必须避免在中间件中使用成员变量。可以随意注入到Task Invoke中,但不要将注入值存储到成员对象中。

参见:在中间件中保存HttpContext实例,调用中间件中的服务

我自己能够解决这个问题,通过创建一个类,然后我可以传递到我的中间件中的其他方法:

    public async Task Invoke(HttpContext context, IMetaService metaService)
    {
            var middler = new Middler
            {
                Context = context,
                MetaService = metaService
            };
            DoSomething(middler);
    }

就这样:-

public async Task Invoke(HttpContext context)
{
    var dbContext = context.RequestServices.GetRequiredService<ClinicDbContext>();
    await _next(context);
}

这是一个简单的解决方案,非常适合我的用例。我创建了一个简单的方法,可以从应用程序中的任何地方调用,以轻松获取数据库上下文:

public class UtilsApp
{
  public static MyDbContext GetDbContext()
  {
    DbContextOptionsBuilder<MyDbContext> opts =
        new DbContextOptionsBuilder<MyDbContext();
    optionsBuilder.UseSqlServer(Program.MyDbConnectionString); // see connection string below
    
    return new MyDbContext(opts.Options);
  }
}

然后,在应用程序的任何地方使用:

MyDbContext dbContext = UtilsApp.GetDbContext();

我从Startup.ConfigureServices()中设置Program.MyDbConnectionString (public static string字段)(这是通过CreateHostBuilder(args).Build()Program.Main()中调用的回调)。这样我就可以在应用程序的任何地方使用该连接字符串,而不必从appsettings.json或环境变量中反复检索它。