【问题标题】:ASP.NET Core Dependency Injection inside Startup.ConfigureStartup.Configure 中的 ASP.NET Core 依赖注入
【发布时间】:2017-09-10 15:39:08
【问题描述】:

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

在我的Startup 类中,我的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),
  });

  // ...
}

CustomCookieAuthenticationEvents 类定义如下:

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
  2. 在第一次请求时在只读属性中获取IMyService(单例模式)

tl;博士

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

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

有什么建议可以让我目前的解决方案更好

【问题讨论】:

  • Service.Configure 是什么?
  • 这是一个错字。我的意思是Startup.Configure

标签: asp.net dependency-injection configuration asp.net-core startup


【解决方案1】:

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

app.ApplicationServices.GetRequiredService<CustomCookieAuthenticationEvents>()

【讨论】:

  • 你是对的。我不知道为什么我认为ConfigureConfigureServices 之前被调用。但我很高兴它不是。
  • 此方法使用服务定位器模式,您不需要这样做,您只需将所需的内容添加到 Startup.Configure 方法签名中,它将被注入到方法中
  • 你是对的,我刚刚展示了如果你真的需要它,如何从 Configure 方法中获得依赖;)
【解决方案2】:

当您在中间件中解析服务时,您应该非常小心。当您使用/需要/要求范围服务(即使用 DbContext)时,您当前的方法(以及 @arnaudauroux 建议的方法)可能会导致困难。

当服务注册为scoped 时,通过app.ApplicationServices 解析会产生静态(单例)服务(瞬态每次调用都会解析,因此它们不受影响)。最好在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 类中传递任何依赖项。 HttpContext.RequiredServices 是专门为此类类制作的(任何其他都可以通过构造函数注入解决,但不能通过中间件和 http 上下文相关管道解决,因为没有其他方法可以正确解析中间件中的范围服务 - 中间件实例是静态的,仅实例化一次每个请求)

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

【讨论】:

  • CustomCookieAuthenticationEvents 是一个单例,所以如果我在构造函数时通过依赖注入传递它就可以了。但可能会将您的方法用于范围变量。
  • @alesc:是的,CustomCookieAuthenticationEvents 是单例,但IMyService 它的依赖关系可能不是单例。这就是我想要的。这就是为什么你应该解决来自HttpContext.RequestServices的请求时间依赖项
  • 您可能会遇到的一个问题是,通过这种用法,作用域服务会变成单例,因为当它第一次由app.ApplicationServices 解决时,它会存储在父容器中(其生命周期等于应用程序的生命周期,因为父容器仅在应用程序启动时创建和处置)。当您现在通过HttpRequest.RequestServices 解析该服务时,您在子容器上工作,子容器在父容器查找表中查找。如果它在那里找到一个作用域/单例,它会使用它而不是创建它。因此它不会在请求结束时处理它
  • @Cubelaster:EF Core 的常见/推荐种子模式可以在 this answer 中看到,而不是在 Startup 中
  • @Cubelaster:作用域容器在 program.cs (或者更确切地说是扩展方法)中解析,然后是它的服务。完成后,范围(以及由它初始化的所有服务)将被释放。注意带有using (var scope = webhost.Services.GetService&lt;IServiceScopeFactory&gt;().CreateScope()) { ... }的行
猜你喜欢
  • 2018-01-16
  • 2019-02-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多