I'd like to be able to pass cancellation tokens via dependency injection instead of as parameters every time. Is this a thing?
We have an asp.net-core 2.1 app, where we pass calls from controllers into a maze of async libraries, handlers and other services to fulfil the byzantine needs of the fintech regulatory domain we service.
At the top of the request, I can declare that I want a cancellation token, and I'll get one:
[HttpPost]
public async Task<IActionResult> DoSomeComplexThingAsync(object thing, CancellationToken cancellationToken) {
await _someComplexLibrary.DoThisComplexThingAsync(thing, cancellationToken);
return Ok();
}
Now, I want to be a good async programmer and make sure my cancellationToken gets passed to every async method down through the call chain. I want to make sure it gets passed to EF, System.IO streams, etc. We have all the usual repository patterns and message passing practices you'd expect. We try to keep our methods concise and have a single responsibility. My tech lead gets visibly aroused by the word 'Fowler'. So our class sizes and function bodies are small, but our call chains are very, very deep.
What this comes to mean is that every layer, every function, has to hand off the damn 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);
}
This goes on ad infinitum, as per the needs of our domain complexity. It seems like CancellationToken appears more times in our codebase than any other term. Our arg lists are often already too long (i.e. more than one) as it is, even though we declare a million object types. And now we have this extra little cancellation token buddy hanging around in every arg list, every method decl.
My question is, since Kestrel and/or the pipeline gave me the token in the first place, it'd be great if I could just have something like this:
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);
}
This would then get passed around via DI composition, and when I had something that needs the token explicitly I could do this:
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);
}
Am I nuts? Do I just pass the damn token, every damn time, and praise the async gods for the bounty that has been given? Should I just retrain as a llama farmer? They seem nice. Is even asking this some kind of heresy? Should I be repenting now? I think for async/await to work properly, the token has to be in the func decl. So, maybe llamas it is
First of all, there are 3 injection scopes: Singleton, Scoped and Transient. Two of those rule out using a shared token.
DI services added with AddSingleton exist across all requests, so any cancellation token must be passed to the specific method (or across your entire application).
DI services added with AddTransient may be instantiated on demand and you may get issues where a new instance is created for a token that is already cancelled. They'd probably need some way for the current token to be passed to [FromServices] or some other library change.
However, for AddScoped I think there is a way, and I was helped by this answer to my similar question - you can't pass the token itself to DI, but you can pass IHttpContextAccessor.
So, in Startup.ConfigureServices or the extension method you use to register whatever IRepository use:
// 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))
That IHttpContextAccessor service will be retrieved once per HTTP request, and that ?.HttpContext?.RequestAborted will return the same CancellationToken as if you had called this.HttpContext.RequestAborted from inside a controller action or added it to the parameters on the action.
I think you are thinking in a great way, I do not think you need to regret or repent.
This is a great idea, I also thought about it, and I implement my own solution
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;
}
and the registration should be like this
services.AddHttpContextAccessor();
services.AddScoped<RequestCancellationBase, RequestCancellation>();
now you can inject RequestCancellationBase wherever you want, and the better thing is that you can directly pass it to every method that expects CancellationToken this is because of public static implicit operator CancellationToken(RequestCancellationBase requestCancellation)
this solution helped me, hope it is helpful for you also
Why is it recommended for a middleware to be async in ASP.NET Core?
E.g. in this tutorial it is recommended to make the middleware custom and I can not understand the reason behind it.
public class MyMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public MyMiddleware(RequestDelegate next, ILoggerFactory logFactory)
{
_next = next;
_logger = logFactory.CreateLogger("MyMiddleware");
}
public async Task Invoke(HttpContext httpContext)
{
_logger.LogInformation("MyMiddleware executing..");
await _next(httpContext); // calling next middleware
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class MyMiddlewareExtensions
{
public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<MyMiddleware>();
}
}
According to the documentation, that is by design
The middleware class must include:
A public constructor with a parameter of type RequestDelegate.
A public method named Invoke or InvokeAsync. This method must:
Return a Task.
Accept a first parameter of type HttpContext.
Reference Write custom ASP.NET Core middleware
My understanding is that the pipeline has been designed to be async by default.
RequestDelegate which is the core of asp.net core's pipe line requires a Task in order to allow a high-performance, and modular HTTP request pipeline.
public delegate System.Threading.Tasks.Task RequestDelegate(HttpContext context);
From Comments: credit to #ScottChamberlain
The reason is that with how async was built in to asp.net core it allows for more throughput of web requests for the same hardware when comparing to a non async version.
I have an application and pass some dependency injection things (logger for example):
ILog _logger;
_logger = new NLogService.NLogService();
if (serviceType == typeof(OCRAPIController))
return new OCRAPIController(_logger);
Domain.ILog _logger;
public OCRAPIController(Domain.ILog logger)
{
if (logger == null)
throw new ArgumentNullException(nameof(logger));
_logger = logger;
}
and then use it:
_logger.AddLog(...);
it works and works fine. Problem is with attributes. I.e. I have the following attribute:
public class ReportAPIAccessAttribute : ActionFilterAttribute
{
public override async Task OnActionExecutingAsync(HttpActionContext actionContext, System.Threading.CancellationToken cancellationToken)
{
and I want to log something. Of course, I can't pass ILog as parameter for constructor. I can create static method as wrapper around ILog and call it or even create NLogService.NLogService() directly in attribute. But I don't like both approaches because I want to have only one place in application, where I call NLogService directly. I can create ServiceLocator for attribute, but then other developers (or even I when I forget) can call ServiceLocator in places, where DI parameter should be called...
How to solve this problem correctly?
Is it safe to store an instance of HttpContext in a middleware?
Example:
public class TestMiddleware
{
private readonly RequestDelegate next;
private HttpContext context;
public TestMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
this.context = context;
I would like to use it in other private methods to work on it, so I can either pass it around as parameter to those function or use it as shown in the example.
But is it thread safe?
But is it thread safe?
No it's not, because middleware are necessarily singletons. If you store a specific HttpContext in a shared field, it will be potentially reused during another request (which would be terrible).
I have a custom Authentication Middelware which uses my custom service injected in the constructor.
In MyAuthenticationHandler I am calling a method of MyService which sets a property value.
_myService.SetCompany(company);
company is loaded in the authentication handler and is not null. However when I try to access the value from the controller I find that MyService has been reinitialized.
This is how it's set in Startup.cs
services.AddScoped<IMyService, MyFactory>();
Middleware is only initialized once, when you register. You need to resolve your dependency in the Invoke method.
public class MyMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public MyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var service = context.RequestServices.GetService<IMyService>();
service.SetCompany("My Company");
await _next.Invoke(context);
}
}
Now the service is properly resolved per request, rather than per application life time.
Edit:
i.e. in order to have your middleware be called after the authorization middelware is called you'd do something like this in your Configure(IAppBuilder app) method:
app.UseCookieAuthentication(options => { ... });
app.UseJwtBearerAuthentication(options => { ... });
app.UseMiddleware<MyMiddleware>(options => { ... });
Then on a request, first the cookie middleware will be called. If it can handle the scheme and it fails, then following middlewares won't be executed. If it can't handle the scheme, next one will be called (jwt bearer). If that passes, the next middleware (MyMiddleware) will be called.
In other words, when your MyMiddleware.Invoke() method is being called, the user has been authenticated.
You can inject the service directly in the Invoke signature.
From the official doc here:
If you must share a scoped service between your middleware and other types, add these services to the Invoke method's signature. The Invoke method can accept additional parameters that are populated by dependency injection.
So in your case:
public async Task Invoke(HttpContext context, IMyService service)
{
service.SetCompany("My Company");
await _next.Invoke(context);
}
will work.