ASP.NET 内部的核心依赖注入 Startup.Configure

ASP.NET Core Dependency Injection inside Startup.Configure

我正在使用 Cookie 中间件对用户进行身份验证。我一直在关注 this official tutorial.

在我的 Startup class 中,我的 Configure 方法的摘录如下所示:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
  // ...

  // Cookie-based Authentication
  app.UseCookieAuthentication(new CookieAuthenticationOptions()
  {
    AuthenticationScheme = CookieAuthenticationDefaults.AuthenticationScheme,        
    AutomaticAuthenticate = true,
    AutomaticChallenge = true,
    Events = new CustomCookieAuthenticationEvents(app),
  });

  // ...
}

CustomCookieAuthenticationEventsclass定义如下:

public class CustomCookieAuthenticationEvents : CookieAuthenticationEvents
{
  private IApplicationBuilder _app;
  private IMyService _myService = null;
  private IMyService MyService
  {
    get
    {
      if(_myService != null)
      {
        return _myService;
      } else
      {
        return _myService = (IMyService) _app.ApplicationServices.GetService(typeof(IMyService));
      }
    }
  }

  public CustomCookieAuthenticationEvents(IApplicationBuilder app)
  {
    _app = app;
  }

  public override async Task ValidatePrincipal(CookieValidatePrincipalContext context)
  {
    string sessionToken = context.Principal.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Sid)?.Value;
    LogonSession response = null;

    var response = await MyService.CheckSession(sessionToken);

    if (response == null)
    {
      context.RejectPrincipal();
      await context.HttpContext.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    }
  }
}

由于依赖注入在 Startup.Configure 不可用(那时甚至还没有注册服务),我做了一些解决方法:

  1. 将 IApplicationBuilder 服务传递给 CustomCookieAuthenticationEvents class
  2. 在只读 属性(单例模式)中首次请求时获取 IMyService

tl;博士

我的解决方案可行,但丑陋。不涉及依赖注入,因为当时不可能。

问题的实质是我必须实例化CustomCookieAuthenticationEvents。据我阅读 source code,没有办法解决这个问题,因为如果我省略 options 参数,UseCookieAuthentication 会抛出异常。

任何建议如何使我当前的解决方案更好

Startup.ConfigureServices() 在 Startup.Configure() 之前调用(有关详细信息,请参阅 https://docs.microsoft.com/en-us/aspnet/core/fundamentals/startup)。所以依赖注入在那个时候是可用的 ;)
因此,您可以像这样解决您对配置方法的依赖:

app.ApplicationServices.GetRequiredService<CustomCookieAuthenticationEvents>()

在中间件内部解析服务时,您应该非常小心。当您 use/need/require 作用域服务(即 DbContext 的使用)时,您当前的方法(以及 @arnaudaroux 建议的方法)可能会导致困难。

通过 app.ApplicationServices 解析导致静态(单例)服务,当服务注册为 scoped 时(瞬态每次调用解析,因此它们不受影响)。最好在 HttpContext 请求期间在 ValidatePrincipal 方法中解析您的服务。

public override async Task ValidatePrincipal(CookieValidatePrincipalContext context)
{
    string sessionToken = context.Principal.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Sid)?.Value;
    LogonSession response = null;

    var myService = context.HttpContext.RequestServices.GetService<IMyService >();
    var response = await myService.CheckSession(sessionToken);

    if (response == null)
    {
        context.RejectPrincipal();
        await context.HttpContext.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    }
}

使用这种方法,您根本不需要在 CustomCookieAuthenticationEvents class 中传递任何依赖项。 HttpContext.RequiredServices 是专门为这样的 classes 制作的(任何其他的都可以通过构造函数注入来解决,但不能通过中间件和与 http 上下文相关的管道解决,因为没有其他方法可以正确解析中间件中的作用域服务 - Middleware实例是静态的,每个请求只实例化一次)

这样您的作用域服务就不会出现生命周期问题。 当您解析瞬态服务时,它们将在请求结束时被处理掉。而通过 app.ApplicationServices 解析的瞬时服务将在请求完成和垃圾收集触发后的某个时间点解析(意味着:您的资源将尽早释放,即请求结束时)。