【问题标题】:Accessing DbContext in Middleware in ASP.NET 5在 ASP.NET 5 的中间件中访问 DbContext
【发布时间】:2015-10-21 07:55:58
【问题描述】:

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

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 在验证用户和密码时使用它。

【问题讨论】:

  • 我有一些错误。请问你找到更正了吗?
  • 你想达到什么目的?

标签: c# entity-framework asp.net-core asp.net-core-mvc


【解决方案1】:

如果你通过Invoke方法注入ApplicationDbContextSignInManager&lt;ApplicationUser&gt;会怎样:

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。你真的需要吗?

【讨论】:

  • 我将如何从调用者传递这个数据库上下文? app.UseMyMiddleware(dbcontext) 之类的东西?
【解决方案2】:

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

见:Saving HttpContext Instance in Middleware, Calling services in Middleware

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

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

            DoSomething(middler);
    }

【讨论】:

    【解决方案3】:

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

    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.MyDbConnectionStringpublic static string 字段)(这是一个在Program.Main() 中通过CreateHostBuilder(args).Build() 调用的回调)。这样我就可以在应用程序的任何地方使用该连接字符串,而无需从 appsettings.json 或环境变量中重复检索它。

    【讨论】:

    • 只是为了解释反对意见......这不是一个好的模式。您正在反对 asp.net 核心的依赖注入模式。几乎在任何你想使用 DbContext 的地方,它都应该通过构造函数或参数注入来注入。这包括 Startup.Configure 方法、中间件、控制器、页面、组件等......
    • @burton,您能否分享一个指向帖子、教程或任何显示它正在“通过构造函数或参数注入”的链接?
    • DI 概述:docs.microsoft.com/en-us/aspnet/core/fundamentals/… 找不到关于动作方法参数注入的文章/文档,但您可以使用动作方法参数上的 [FromServices] 属性在控制器动作中注入服务方法。
    • 我应该说“构造函数、属性或参数注入”。
    • 谢谢你,@burton!我期待着本周晚些时候将这些知识“注入”我的大脑! :)
    猜你喜欢
    • 1970-01-01
    • 2016-02-05
    • 2021-08-31
    • 2023-01-28
    • 2021-01-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-21
    相关资源
    最近更新 更多