问题描述
我希望能够通过依赖项注入传递取消令牌,而不是每次都作为参数传递。这是东西吗?
我们有一个asp.net-core 2.1应用程序,在这里我们将来自控制器的调用传递到迷宫般的异步库,处理程序和其他服务中,以满足我们服务的金融科技监管领域的拜占庭式需求。
在请求的顶部,我可以声明我想要取消令牌,然后我将得到一个:
[HttpPost]
public async Task<IActionResult> DoSomeComplexThingAsync(object thing,CancellationToken cancellationToken) {
await _someComplexLibrary.DoThisComplexThingAsync(thing,cancellationToken);
return Ok();
}
现在,我想成为一名优秀的异步程序员,并确保将我的cancellationToken
传递给整个调用链中的每个异步方法。我想确保将其传递给EF,System.IO流等。我们具有您期望的所有常用存储库模式和消息传递实践。我们试图使我们的方法简明扼要,并负有单一责任。 “ Fowler”一词显然引起了我的技术主管。因此,我们的类大小和函数体很小,但是我们的调用链非常非常深。
这意味着每个层,每个功能都必须交出该死的令牌:
private readonly ISomething _something;
private readonly IRepository<WeirdType> _repository;
public SomeMessageHandler(ISomething<SomethingElse> something,IRepository<WeirdType> repository) {
_something = something;
_repository = repository;
}
public async Task<SomethingResult> Handle(ComplexThing request,CancellationToken cancellationToken) {
var result = await DoMyPart(cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
result.somethingResult = await _something.DoSomethingElse(result,cancellationToken);
return result;
}
public async Task<SomethingResult> DoMyPart(ComplexSubThing request,CancellationToken cancellationToken) {
return await _repository.someEntityFrameworkThingEventually(request,cancellationToken);
}
根据我们域复杂性的需要,这是无限的。 CancellationToken
在我们的代码库中的出现次数似乎比任何其他术语都多。即使我们声明了一百万个对象类型,我们的arg列表通常也已经太长(即不止一个)。现在,在每个arg列表,每个方法的说明中,我们都有这个多余的取消令牌伙伴。
我的问题是,由于Kestrel和/或管道首先给了我令牌,如果我能拥有这样的东西,那就太好了
private readonly ISomething _something;
private readonly IRepository<WeirdType> _repository;
private readonly ICancellationToken _cancellationToken;
public SomeMessageHandler(ISomething<SomethingElse> something,ICancellationToken cancellationToken) {
_something = something;
_repository = repository;
_cancellationToken = cancellationToken;
}
public async Task<SomethingResult> Handle(ComplexThing request) {
var result = await DoMyPart(request);
_cancellationToken.ThrowIfCancellationRequested();
result.somethingResult = await _something.DoSomethingElse(result);
return result;
}
public async Task<SomethingResult> DoMyPart(ComplexSubThing request) {
return await _repository.someEntityFrameworkThingEventually(request);
}
然后,这将通过DI组合传递出去,当我有一些明确需要令牌的东西时,我可以这样做:
private readonly IDatabaseContext _context;
private readonly ICancellationToken _cancellationToken;
public IDatabaseRepository(IDatabaseContext context,ICancellationToken cancellationToken) {
_context = context;
_cancellationToken = cancellationToken;
}
public async Task<SomethingResult> DoDatabaseThing() {
return await _context.EntityFrameworkThing(_cancellationToken);
}
我疯了吗?我是否只是在每个该死的时间都传递该死的令牌,并赞扬那些异步神给予的赏金?我应该重新训练成为美洲驼农民吗?他们看起来不错。甚至问这种异端?我现在应该悔改吗?我认为要使async / await正常工作,令牌必须位于func decl中。所以,也许是美洲驼
解决方法
我认为你的想法很好,我认为你不需要后悔或忏悔,这是个好主意,我也考虑过,我得出了解决方案
public abstract class RequestCancellationBase
{
public abstract CancellationToken Token { get; }
public static implicit operator CancellationToken(RequestCancellationBase requestCancellation) =>
requestCancellation.Token;
}
public class RequestCancellation : RequestCancellationBase
{
private readonly IHttpContextAccessor _context;
public RequestCancellation(IHttpContextAccessor context)
{
_context = context;
}
public override CancellationToken Token => _context.HttpContext.RequestAborted;
}
注册应该是这样的
services.AddSingleton<IHttpContextAccessor,HttpContextAccessor>();
services.AddScoped<RequestCancellationBase,RequestCancellation>();
现在你可以在任何你想要的地方注入 RequestCancellationBase
,
更好的是你可以直接将它传递给每个需要 CancellationToken
的方法,这是因为 public static implicit operator CancellationToken(RequestCancellationBase requestCancellation)
这个解决方案对我有帮助,希望对你也有帮助
,首先,有3种注入范围:单例,有范围和瞬态。其中两个排除使用共享令牌。
添加有AddSingleton
的DI服务存在于所有请求中,因此任何取消令牌都必须传递给特定方法(或整个应用程序)。
AddTransient
的 DI服务可能会按需实例化,并且可能会出现以下问题:为已取消的令牌创建新实例。他们可能需要某种方式将当前令牌传递到[FromServices]
或进行其他一些库更改。
但是,对于AddScoped
,我认为是有办法的,this answer to my similar question为我提供了帮助-您无法将令牌本身传递给DI,但可以传递IHttpContextAccessor
。
因此,在Startup.ConfigureServices
或您用来注册IRepository
使用的扩展名的方法中:
// For imaginary repository that looks something like
class RepositoryImplementation : IRepository {
public RepositoryImplementation(string connection,CancellationToken cancellationToken) { }
}
// Add a scoped service that references IHttpContextAccessor on create
services.AddScoped<IRepository>(provider =>
new RepositoryImplementation(
"Repository connection string/options",provider.GetService<IHttpContextAccessor>()?.HttpContext?.RequestAborted ?? default))
每个HTTP请求将检索一次IHttpContextAccessor
服务,并且?.HttpContext?.RequestAborted
将返回相同的CancellationToken
,就像您从控制器操作或将其添加到操作的参数中。