在 ASP.NET 核心中的中间件中注入服务
Injecting Service in Middleware in ASP.NET Core
我想注入一个基于 HTTP header 值的服务。所以我有 2 个 classes - DbDataProvider 和 InMemDataProvider,它们都是从 IDataProvider 实现的。每当进行 API 调用时,客户端都会传递一个 header 来确定是需要 DbDataProvider 还是需要 InMemDataProvider。我该如何实现?所以简而言之,我需要在其中一个中间件的 ServiceCollection 中注入服务。这可能吗?
问题是在 Startup 的 ConfigureService 方法中 class 我无法获取 HttpContext。我已经编写了一个中间件,我可以使用它来获取 HTTP 上下文,但是我该如何在其中注入服务?
没有简单或干净的方法可以做到这一点。您不能在 ConfigureServices
方法之外修改 IServiceCollection
。但是即使可以,也没有用,因为在调用 Configure
之前容器已经构建好了。
您可以做的是创建一个工厂 class 并将其注册为作用域。
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;
}
}
}
在你的 controller/services 中:
public class MyController : Controller
{
private readonly IDataProvider dataProvider;
public MyController(IDataProviderFactory dataProviderFactory)
{
dataProvider = dataProviderFactory.Create();
}
}
Tsen 上面的回答是正确的。你应该实施一个工厂。
但除此之外,您还可以向服务注册工厂方法collection。像这样:
Services.AddTransient(serviceProvider => serviceProvider.GetService<IDataProviderFactory>().Create())
这会注册您的 IDataProvider。在 Create 中,您应该评估 HTTP header 值,使其 returns 成为正确的 IDataProvider 实例。然后在任何 class 你需要它的情况下,你可以简单地通过构造函数请求 IDataProvider ,容器将提供正确的实现。
您可以在 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>();
}
});
我想注入一个基于 HTTP header 值的服务。所以我有 2 个 classes - DbDataProvider 和 InMemDataProvider,它们都是从 IDataProvider 实现的。每当进行 API 调用时,客户端都会传递一个 header 来确定是需要 DbDataProvider 还是需要 InMemDataProvider。我该如何实现?所以简而言之,我需要在其中一个中间件的 ServiceCollection 中注入服务。这可能吗?
问题是在 Startup 的 ConfigureService 方法中 class 我无法获取 HttpContext。我已经编写了一个中间件,我可以使用它来获取 HTTP 上下文,但是我该如何在其中注入服务?
没有简单或干净的方法可以做到这一点。您不能在 ConfigureServices
方法之外修改 IServiceCollection
。但是即使可以,也没有用,因为在调用 Configure
之前容器已经构建好了。
您可以做的是创建一个工厂 class 并将其注册为作用域。
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;
}
}
}
在你的 controller/services 中:
public class MyController : Controller
{
private readonly IDataProvider dataProvider;
public MyController(IDataProviderFactory dataProviderFactory)
{
dataProvider = dataProviderFactory.Create();
}
}
Tsen 上面的回答是正确的。你应该实施一个工厂。
但除此之外,您还可以向服务注册工厂方法collection。像这样:
Services.AddTransient(serviceProvider => serviceProvider.GetService<IDataProviderFactory>().Create())
这会注册您的 IDataProvider。在 Create 中,您应该评估 HTTP header 值,使其 returns 成为正确的 IDataProvider 实例。然后在任何 class 你需要它的情况下,你可以简单地通过构造函数请求 IDataProvider ,容器将提供正确的实现。
您可以在 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>();
}
});