Injeksi Token Pembatalan
Saya ingin meneruskan token pembatalan melalui injeksi ketergantungan alih-alih sebagai parameter setiap saat. Apakah ini sesuatu?
Kami memiliki aplikasi asp.net-core 2.1, tempat kami meneruskan panggilan dari pengontrol ke labirin pustaka asinkron, penangan, dan layanan lain untuk memenuhi kebutuhan Bizantium dari domain regulasi fintech yang kami layani.
Di bagian atas permintaan, saya dapat menyatakan bahwa saya menginginkan token pembatalan, dan saya akan mendapatkannya:
[HttpPost]
public async Task<IActionResult> DoSomeComplexThingAsync(object thing, CancellationToken cancellationToken) {
await _someComplexLibrary.DoThisComplexThingAsync(thing, cancellationToken);
return Ok();
}
Sekarang, saya ingin menjadi pemrogram asinkron yang baik dan memastikan cancellationTokenmetode saya diteruskan ke setiap metode asinkron melalui rantai panggilan. Saya ingin memastikannya diteruskan ke aliran EF, System.IO, dll. Kami memiliki semua pola repositori dan praktik pengiriman pesan yang biasa Anda harapkan. Kami mencoba untuk menjaga metode kami tetap ringkas dan memiliki satu tanggung jawab. Pimpinan teknis saya terlihat terangsang oleh kata 'Fowler'. Jadi ukuran kelas dan badan fungsi kita kecil, tetapi rantai panggilan kita sangat, sangat dalam.
Artinya, setiap lapisan, setiap fungsi, harus menyerahkan token sialan itu:
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);
}
Ini berlangsung ad infinitum, sesuai kebutuhan kompleksitas domain kami. Tampaknya CancellationTokenlebih sering muncul dalam basis kode kami daripada istilah lainnya. Daftar arg kita seringkali sudah terlalu panjang (yaitu lebih dari satu) seperti aslinya, meskipun kita mendeklarasikan sejuta tipe objek. Dan sekarang kita memiliki token pembatalan ekstra kecil yang ada di setiap daftar argumen, setiap metode yang dideklarasikan.
Pertanyaan saya adalah, karena Kestrel dan / atau pipeline memberi saya token pada awalnya, alangkah baiknya jika saya bisa mendapatkan sesuatu seperti ini:
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);
}
Ini kemudian akan diedarkan melalui komposisi DI, dan ketika saya memiliki sesuatu yang membutuhkan token secara eksplisit, saya dapat melakukan ini:
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);
}
Apakah saya gila? Apakah saya hanya memberikan token sialan itu, setiap saat, dan memuji dewa-dewa asinkron atas karunia yang telah diberikan? Haruskah saya berlatih kembali sebagai petani llama? Kelihatannya bagus. Apakah menanyakan ini semacam bid'ah? Haruskah saya bertobat sekarang? Saya pikir agar async / await berfungsi dengan benar, token harus berada di dalam func dec. Jadi, mungkin llama itu
Jawaban
Pertama-tama, ada 3 cakupan injeksi: Singleton, Scoped, dan Transient. Dua di antaranya mengesampingkan penggunaan token bersama.
Layanan DI ditambahkan dengan AddSingletonada di semua permintaan, jadi token pembatalan apa pun harus diteruskan ke metode tertentu (atau di seluruh aplikasi Anda).
Layanan DI yang ditambahkan dengan AddTransientdapat dibuat sesuai permintaan dan Anda mungkin mendapatkan masalah saat instance baru dibuat untuk token yang sudah dibatalkan. Mereka mungkin memerlukan beberapa cara agar token saat ini diteruskan [FromServices]atau beberapa perubahan perpustakaan lainnya.
Namun, karena AddScopedsaya pikir ada cara, dan saya terbantu oleh jawaban ini untuk pertanyaan serupa saya - Anda tidak dapat memberikan token itu sendiri ke DI, tetapi Anda dapat lulus IHttpContextAccessor.
Jadi, dalam Startup.ConfigureServicesatau metode ekstensi yang Anda gunakan untuk mendaftar apa pun IRepositorypenggunaan:
// 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))
Itu IHttpContextAccessorlayanan akan diambil sekali per HTTP request, dan bahwa ?.HttpContext?.RequestAbortedakan kembali sama CancellationTokenseperti jika Anda telah dipanggil this.HttpContext.RequestAborteddari dalam aksi kontroler atau ditambahkan ke parameter pada tindakan.