Wstrzyknięcie tokena anulowania
Chciałbym móc za każdym razem przekazywać tokeny anulowania za pośrednictwem iniekcji zależności zamiast jako parametry. Czy to jest rzecz?
Mamy aplikację asp.net-core 2.1, w której przekazujemy wywołania z kontrolerów do labiryntu bibliotek asynchronicznych, programów obsługi i innych usług, aby spełnić bizantyjskie potrzeby obsługiwanej domeny regulacyjnej fintech.
Na górze żądania mogę zadeklarować, że chcę token anulowania, i otrzymam go:
[HttpPost]
public async Task<IActionResult> DoSomeComplexThingAsync(object thing, CancellationToken cancellationToken) {
await _someComplexLibrary.DoThisComplexThingAsync(thing, cancellationToken);
return Ok();
}
Teraz chcę być dobrym programistą asynchronicznym i upewnić się, że moje zostaną cancellationTokenprzekazane do każdej metody asynchronicznej w łańcuchu wywołań. Chcę się upewnić, że zostanie on przekazany do EF, strumieni System.IO itp. Mamy wszystkie typowe wzorce repozytoriów i praktyki przekazywania wiadomości, których można się spodziewać. Staramy się, aby nasze metody były zwięzłe i ponosimy jedną odpowiedzialność. Mojego kierownika technicznego wyraźnie podnieca słowo „Fowler”. Więc rozmiary naszych klas i ciała funkcyjne są małe, ale nasze łańcuchy wywołań są bardzo, bardzo głębokie.
Oznacza to, że każda warstwa, każda funkcja musi przekazać ten cholerny token:
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);
}
Trwa to w nieskończoność, zgodnie z potrzebami złożoności naszej domeny. Wydaje się, że CancellationTokenpojawia się więcej razy w naszej bazie kodów niż jakikolwiek inny termin. Nasze listy argumentów są często zbyt długie (tj. Więcej niż jeden) w obecnej postaci, mimo że deklarujemy milion typów obiektów. A teraz mamy tego dodatkowego małego kumpla tokena anulowania, który kręci się po każdej liście argumentów, każdej deklaracji metody.
Moje pytanie brzmi, skoro Kestrel i / lub rurociąg dały mi token w pierwszej kolejności, byłoby wspaniale, gdybym mógł mieć coś takiego:
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);
}
To byłoby następnie przekazywane przez kompozycję DI, a kiedy miałem coś, co wyraźnie potrzebuje tokenu, mogłem zrobić to:
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);
}
Czy jestem szalony? Czy po prostu przekazuję ten cholerny żeton za każdym razem i chwalę asynchronicznych bogów za daną nagrodę? Czy powinienem po prostu przekwalifikować się na farmera lam? Wydają się mili. Czy to w ogóle jest jakaś herezja? Czy powinienem teraz żałować? Myślę, że aby async / await działało poprawnie, token musi znajdować się w pliku func decl. Więc może to jest lamy
Odpowiedzi
Przede wszystkim są 3 zakresy wtrysku: Singleton, Scoped i Transient. Dwie z nich wykluczają używanie wspólnego tokena.
Usługi DI dodane za pomocą AddSingletonistnieją we wszystkich żądaniach, więc każdy token anulowania musi zostać przekazany do określonej metody (lub w całej aplikacji).
Usługi DI dodane za pomocą AddTransientmogą być tworzone na żądanie i mogą wystąpić problemy, gdy zostanie utworzone nowe wystąpienie dla tokenu, który został już anulowany. Prawdopodobnie potrzebowaliby jakiegoś sposobu na przekazanie bieżącego tokenu do [FromServices]lub innej zmiany w bibliotece.
Jednak AddScopedmyślę, że jest sposób i pomogła mi ta odpowiedź na moje podobne pytanie - nie możesz przekazać samego tokena do DI, ale możesz przejść IHttpContextAccessor.
Tak więc w Startup.ConfigureServiceslub metoda rozszerzenia, której używasz do rejestrowania dowolnego IRepositoryużycia:
// 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))
Ta IHttpContextAccessorusługa zostanie pobrana raz na żądanie HTTP i ?.HttpContext?.RequestAbortedzwróci to samo, CancellationTokenco w przypadku wywołania this.HttpContext.RequestAbortedz wnętrza akcji kontrolera lub dodania jej do parametrów akcji.