我在Azure service Fabric中有一个无状态服务,我使用Microsoft.Extensions。DependencyInjection,尽管同样的问题存在于任何其他DI框架中。在我的Program.cs中,我创建了一个ServiceCollection,添加了所有的注册(除了一个),创建了服务提供者,并将其传递给了服务的构造函数。任何具有外部入口的服务方法都将创建一个新的服务范围并调用主业务逻辑类。问题是,我想要具有作用域生命周期的类之一需要一个值,该值是请求本身的输入参数。下面是我想要实现的代码片段。
internal sealed class MyService : StatelessService, IMyService
{
private IServiceProvider _serviceProvider;
private IServiceScopeFactory _scopeFactory;
public MyService(StatelessServiceContext context, IServiceProvider serviceProvider)
: base(context)
{
_serviceProvider = serviceProvider;
_scopeFactory = _serviceProvider.GetRequiredService<IServiceScopeFactory>();
}
public async Task<MyResponse> ProcessAsync(MyRequest request, string correlationId, CancellationToken cancellationToken)
{
using (var scope = _scopeFactory.CreateScope())
{
var requestContext = new RequestContext(correlationId);
//IServiceCollection serviceCollection = ??;
//serviceCollection.AddScoped<RequestContext>(di => requestContext);
var businessLogic = scope.ServiceProvider.GetRequiredService<BusinessLogic>();
return await businessLogic.ProcessAsync(request, cancellationToken);
}
}
}
取消令牌已经到处传递,包括不直接使用它的类,这样它就可以传递给使用它的依赖项,我想避免对请求上下文做同样的事情。
同样的问题存在于我的MVC api。我可以创建中间件,它将从HTTP报头中提取关联id,因此API控制器不需要像我的服务结构服务那样处理它。我可以让它工作的一种方法是通过给RequestContext
一个默认构造函数,并有一个可变的相关id。但是,在请求期间不更改关联id绝对是至关重要的,所以我真的希望在上下文类上具有get-only属性的安全性。
我目前最好的想法是有一个范围RequestContextFactory,它有一个SetCorrelationId方法,RequestContext注册只是调用工厂来获得一个实例。如果在id设置之前请求新实例,工厂可以抛出异常,以确保没有创建无id的上下文,但这感觉不是一个好的解决方案。
我如何干净地注册只读对象与依赖注入框架,其中的值取决于传入的请求?
我只是在编写原始问题时才有了RequestContextFactory的想法,最后我花时间测试了这个想法。它实际上比我预期的代码少,而且工作得很好,所以这将是我现在的解决方案。但是,厂名是错的。我不知道该怎么称呼它。
首先定义上下文和工厂类。我甚至在工厂中添加了一些验证检查,以确保它按照我期望的方式工作:
public class RequestContext
{
public RequestContext(string correlationId)
{
CorrelationId = correlationId;
}
public string CorrelationId { get; }
}
public class RequestContextFactory
{
private RequestContext _requestContext;
private bool _used = false;
public void SetContext(RequestContext requestContext)
{
if (_requestContext != null || requestContext == null)
{
throw new InvalidOperationException();
}
_requestContext = requestContext;
}
public RequestContext GetContext()
{
if (_used || _requestContext == null)
{
throw new InvalidOperationException();
}
_used = true;
return _requestContext;
}
}
然后,将注册添加到您的DI容器:
services.AddScoped<RequestContextFactory>();
services.AddScoped<RequestContext>(di => di.GetRequiredService<RequestContextFactory>().GetContext());
最后,Service Fabric服务方法看起来像这样
public async Task<MyResponse> ProcessAsync(MyRequest request, string correlationId, CancellationToken cancellationToken)
{
using (var scope = _scopeFactory.CreateScope())
{
var requestContext = new RequestContext(correlationId);
var requestContextFactory = scope.ServiceProvider.GetRequiredService<RequestContextFactory>();
requestContextFactory.SetContext(requestContext);
var businessLogic = scope.ServiceProvider.GetRequiredService<BusinessLogic>();
return await businessLogic.ProcessAsync(request, cancellationToken);
}
}
红隼中间件可以是这样的
public async Task Invoke(HttpContext httpContext)
{
RequestContext requestContext = new RequestContext(Guid.NewGuid().ToString());
var factory = httpContext.RequestServices.GetRequiredService<RequestContextFactory>();
factory.SetContext(requestContext);
httpContext.Response.Headers["X-CorrelationId"] = requestContext.CorrelationId;
await _next(httpContext);
}
然后做正常的事情,并添加一个RequestContext参数到任何类的构造函数,需要获得相关id(或任何其他信息你放在请求上下文中)