Chèn mã thông báo hủy
Tôi muốn có thể chuyển mã thông báo hủy thông qua tiêm phụ thuộc thay vì dưới dạng tham số mọi lúc. Đây có phải là một điều?
Chúng tôi có một ứng dụng asp.net-core 2.1, nơi chúng tôi chuyển các cuộc gọi từ bộ điều khiển vào một mê cung các thư viện không đồng bộ, trình xử lý và các dịch vụ khác để đáp ứng các nhu cầu của miền pháp quy fintech mà chúng tôi phục vụ.
Ở đầu yêu cầu, tôi có thể tuyên bố rằng tôi muốn có mã thông báo hủy và tôi sẽ nhận được:
[HttpPost]
public async Task<IActionResult> DoSomeComplexThingAsync(object thing, CancellationToken cancellationToken) {
await _someComplexLibrary.DoThisComplexThingAsync(thing, cancellationToken);
return Ok();
}
Bây giờ, tôi muốn trở thành một lập trình viên không đồng bộ giỏi và đảm bảo rằng tôi cancellationTokenđược chuyển đến mọi phương thức không đồng bộ thông qua chuỗi cuộc gọi. Tôi muốn đảm bảo rằng nó được chuyển tới các luồng EF, System.IO, v.v. Chúng tôi có tất cả các mẫu kho lưu trữ thông thường và các phương pháp chuyển thư mà bạn mong đợi. Chúng tôi cố gắng giữ cho các phương pháp của mình ngắn gọn và có một trách nhiệm duy nhất. Người dẫn đầu về công nghệ của tôi được đánh thức rõ ràng bởi từ 'Fowler'. Vì vậy, kích thước lớp và cơ quan chức năng của chúng ta nhỏ, nhưng chuỗi cuộc gọi của chúng ta rất rất sâu.
Điều này có nghĩa là mọi lớp, mọi chức năng, đều phải xử lý mã thông báo chết tiệt:
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);
}
Điều này diễn ra trong phạm vi quảng cáo, tùy theo nhu cầu về độ phức tạp miền của chúng tôi. Có vẻ như nó CancellationTokenxuất hiện nhiều lần trong codebase của chúng tôi hơn bất kỳ thuật ngữ nào khác. Danh sách đối số của chúng ta thường đã quá dài (tức là nhiều hơn một), mặc dù chúng ta khai báo một triệu kiểu đối tượng. Và bây giờ chúng ta có thêm một người bạn mã thông báo hủy bỏ nhỏ này quanh quẩn trong mọi danh sách đối số, mọi phương thức khai báo.
Câu hỏi của tôi là, vì Kestrel và / hoặc đường ống đã cung cấp cho tôi mã thông báo ngay từ đầu, sẽ thật tuyệt nếu tôi có thể có một cái gì đó như thế này:
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);
}
Sau đó, điều này sẽ được chuyển qua thành phần DI và khi tôi có thứ gì đó cần mã thông báo rõ ràng, tôi có thể làm điều này:
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);
}
Tôi điên à? Có phải tôi chỉ chuyển mã thông báo chết tiệt, mỗi lần chết tiệt và ca ngợi các vị thần không đồng bộ về tiền thưởng đã được trao không? Tôi có nên đào tạo lại thành một nông dân llama không? Họ có vẻ tốt. Thậm chí hỏi điều này là một số loại dị giáo? Bây giờ tôi có nên ăn năn không? Tôi nghĩ để async / await hoạt động bình thường, mã thông báo phải ở trong khai báo func. Vì vậy, có lẽ nó là lạc đà không bướu
Trả lời
Trước hết, có 3 phạm vi tiêm: Singleton, Scoped và Transient. Hai trong số đó loại trừ việc sử dụng mã thông báo được chia sẻ.
Các dịch vụ DI được thêm vào AddSingletontồn tại trên tất cả các yêu cầu, vì vậy mọi mã thông báo hủy phải được chuyển đến phương thức cụ thể (hoặc trên toàn bộ ứng dụng của bạn).
Các dịch vụ DI được thêm vào có AddTransientthể được khởi tạo theo yêu cầu và bạn có thể gặp sự cố khi một phiên bản mới được tạo cho một mã thông báo đã bị hủy. Họ có thể cần một số cách để mã thông báo hiện tại được chuyển đến [FromServices]hoặc một số thay đổi thư viện khác.
Tuy nhiên, AddScopedtôi nghĩ rằng có một cách và tôi đã được giúp đỡ bởi câu trả lời này cho câu hỏi tương tự của tôi - bạn không thể chuyển chính mã thông báo sang DI, nhưng bạn có thể vượt qua IHttpContextAccessor.
Vì vậy, trong Startup.ConfigureServiceshoặc phương pháp mở rộng bạn sử dụng để đăng ký bất kỳ mục IRepositoryđích sử dụng nào:
// 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))
IHttpContextAccessorDịch vụ đó sẽ được truy xuất một lần cho mỗi yêu cầu HTTP và điều đó ?.HttpContext?.RequestAbortedsẽ trả về giống CancellationTokennhư khi bạn đã gọi this.HttpContext.RequestAbortedtừ bên trong một hành động bộ điều khiển hoặc thêm nó vào các tham số trên hành động.