【问题标题】:Injecting Service in Middleware in ASP.NET Core在 ASP.NET Core 的中间件中注入服务
【发布时间】:2016-11-05 13:57:05
【问题描述】:

我想根据 HTTP 标头值注入服务。所以我有 2 个类 - DbDataProvider 和 InMemDataProvider,它们都是从 IDataProvider 实现的。每当进行 API 调用时,客户端都会传递一个标头,以确定是需要 DbDataProvider 还是需要 InMemDataProvider。我该如何做到这一点?所以简而言之,我需要在其中一个中间件的 ServiceCollection 中注入服务。这可能吗?

问题是在 Startup 类的 ConfigureService 方法中我无法获取 HttpContext。我已经编写了一个中间件,我可以使用它来获取 HTTP 上下文,但是如何在那里注入服务?

【问题讨论】:

  • 防止基于运行时条件注入服务,就像你应该 not inject runtime data itself 到你的应用程序组件中一样。取而代之的是一个复合组件,它被注入两个实现并在运行时将调用转发给实现。工厂几乎不是正确的解决方案。

标签: c# asp.net-core dependency-injection asp.net-core-middleware


【解决方案1】:

没有简单或干净的方法可以做到这一点。您不能在ConfigureServices 方法之外修改IServiceCollection。但即使你可以,它也没有用,因为在调用Configure 之前已经构建了容器。

您可以做的是创建一个工厂类并将其注册为作用域。

public interface IDataProviderFactory
{
    bool UseInMemoryProvider { get; set; }
    IDataProvider Create();
}

public class DataProviderFactory : IDataProviderFactory
{
    private readonly IServiceProvider provider;

    public bool UseInMemoryProvider { get; set; }

    public DataProviderFactory(IServiceProvider provider) 
    {
        this.provider = provider;
    }

    public IDataProvider Create()
    {
        if(UseInMemoryProvider) 
        {
            return provider.RequestService<InMemoryDataProvider>();
        }

        return provider.RequestService<DbDataProvider>();
    }
}

然后在你的中间件中:

public class MyMiddleware
{
    public void Invoke(HttpContext context) 
    {
        var dataProviderFactory = context.RequestServices.RequestService<IDataProviderFactory>();

        // Your logic here
        if(...)
        {
            dataProviderFactory.UseInMemoryStore = true;
        } 
    }
}

在您的控制器/服务中:

public class MyController : Controller 
{
    private readonly IDataProvider dataProvider;

    public MyController(IDataProviderFactory dataProviderFactory)
    {
        dataProvider = dataProviderFactory.Create();
    }
}

【讨论】:

    【解决方案2】:

    您可以在 Startup.cs 的 DI 配置中实现此目的。

    它们的键是services.AddHttpContextAccessor(),它允许您访问 HttpContext。

    services.AddHttpContextAccessor();
    
    services.AddScoped<DbDataProvider>();
    services.AddScoped<InMemDataProvider>();
    services.AddScoped<IDataProvider>(ctx =>
    {
        var contextAccessor = ctx.GetService<IHttpContextAccessor>();
        var httpContext = contextAccessor.HttpContext;
    
        // Whatever the header is that you are looking for
        if (httpContext.Request.Headers.TryGetValue("Synthetic", out var syth))
        {
            return ctx.GetService<InMemDataProvider>();
        }
        else
        {
            return ctx.GetService<DbDataProvider>();
        }
    });
    

    【讨论】:

      【解决方案3】:

      Tsen 的上述回答是正确的。你应该实现一个工厂。

      但除此之外,您还可以将工厂方法注册到服务集合。像这样:

      Services.AddTransient(serviceProvider => serviceProvider.GetService<IDataProviderFactory>().Create())
      

      这会注册您的 IDataProvider。在 Create 中,您应该评估该 HTTP 标头值,以便它返回正确的 IDataProvider 实例。然后在任何需要它的类中,您都可以通过构造函数简单地请求 IDataProvider,容器将提供正确的实现。

      【讨论】:

      • 这将导致始终返回相同的实例,因为它是单例的;)这里的另一个问题是您无法直接访问 HttpContextIHttpContextAccessor 不是默认情况下注册(出于性能原因),因为在每个请求上创建它都有一点开销,如果他的其他服务不需要控制器之外的 HttpContext ,他不应该仅仅因为一个原因而注册它。 github.com/aspnet/Announcements/issues/190
      • 我的意思是短暂的(更新的答案)。我们确实已经手动注册了 IHttpContextAccessor。我要对他们自己说。
      猜你喜欢
      • 1970-01-01
      • 2020-09-15
      • 2021-09-05
      • 2018-07-22
      • 1970-01-01
      • 2018-08-27
      • 2021-11-28
      • 2018-11-20
      • 1970-01-01
      相关资源
      最近更新 更多