我在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属性的安全性。
我目前最好的想法是拥有一个带有SetCorrelationId方法的作用域RequestContextFactory,而RequestContext注册只是调用工厂来获取实例。如果在设置id之前请求新实例,工厂可以抛出异常,以确保没有创建无id的上下文,但它不是一个好的解决方案。
如何使用依赖注入框架干净地注册只读对象,其中值取决于传入请求?
答案 0 :(得分:4)
我在编写原始问题时只想到了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);
}
}
Kestrel中间件可能看起来像这样
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(或您在请求上下文中放置的任何其他信息)的任何类的构造函数中