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
Related
I'm looking for a solution to use INotification in MediatR, what I'm trying to do is handling the commits and changes in INotificationHandler, instead of IRequestHandler.
Does it make sense to do so?
Product-> AddProduct->ProductWasAdded.
public class AddProductCommandHandler : IRequestHandler<AddProductCommand, Result<ProductTypeId>>
{
private readonly DbContext _writeContext;
private readonly IMediator _mediator;
public AddProductCommandHandler( DbContext writeContext, IMediator mediator )
{
_writeContext = writeContext;
_mediator = mediator;
}
public async Task<Result<ProductTypeId>> Handle( AddProductCommand request, CancellationToken cancellationToken )
{
//Logics ommitedfor bravety
await _mediator.Publish( new ProductWasAddedEvent(product), cancellationToken );
}
}
and in INotificationHandler:
public class ProductWasAddedEvent:INotification
{
public Product Product { get; }
public ProductWasAddedEvent(Product product)
{
Product= product;
}
}
Finally in INotificationHandler:
public class ProductEvents:INotificationHandler<ProductWasAddedEvent>
{
private readonly DbContext _writeContext;
public ProductEvents( DbContext writeContext )
{
_writeContext = writeContext;
}
public async Task Handle( ProductWasAddedEvent notification, CancellationToken cancellationToken )
{
await _writeContext.Products.AddAsync( notification.Product, cancellationToken );
await _writeContext.SaveChangesAsync( cancellationToken );
}
}
I guess it highly depends on whether you're handling the "events" in separate microservices or perhaps if you plan on sending them through a real message bus (eg. RabbitMQ, Kafka).
If you're handling everything in memory in a single process, you might be good with your current approach.
However, resiliency and fault tolerance are not exactly guaranteed. For that you might want to look at the Outbox pattern. It would let you to create a single transaction on your DB, persist your data AND the messages/events you want to dispatch. Then a separate background service will take care of sending them.
I have a user controller like this:
public class UserController : ControllerBase
{
private readonly IUserService _userService;
public UserController(IUserService userService)
{
_userService = userService;
}
public async Task<User> Get(int id, CancellationToken cancellationToken)
{
return await _userService.GetUserById(id, cancellationToken);
}
}
and a user service:
public class UserService : IUserService
{
public async Task<User> GetUserById(int id, CancellationToken cancellationToken)
{
return await _dbContext.Users.Where(a => a.Id == id).FirstOrDefaultAsync(cancellationToken);
}
}
In UserService I have an async method that returns a user by Id.
My question is, do we need to use async/await keyword in controller or is using async/await in UserService enough?
public class UserController : ControllerBase
{
private readonly IUserService _userService;
public UserController(IUserService userService)
{
_userService = userService;
}
public Task<User> Get(int id, CancellationToken cancellationToken)
{
return _userService.GetUserById(id, cancellationToken);
}
}
If you're only awaiting one thing, as the last line of an async method, and returning the result directly or not at all (i.e. not doing anything non-trivial with the result), then yes you can elide the await, by removing the async and await parts; this avoids a bit of machinery, but it means that if the method you're calling faults synchronously (specifically: it throws an exception rather than returning a task that reports a fault state), then the exception will surface slightly differently.
Avoiding this state machine can matter if you're in an inner loop, for example in the middle of IO code that gets called many many times per operation and you need to optimize it, but: that doesn't apply here - you're at the top of a controller. Honestly, it doesn't need optimizing: just use the async/await: it'll be more consistently correct.
Yes, in order to consume the service method asynchronously, you'll need to make the Controller asynchronous also.
It's "Async All the Way" ...
https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming#async-all-the-way
I'm developing a middleware which I would like to have an optional dependency on a internal logging library. In another words, if MyLoggingService is registered, great!, else, life goes on and ill log to console.
But by declaring public async Task Invoke(HttpContext httpContext, MyLoggingService logger), I get a runtime error saying that it was not registred. I tried setting a default value to null but that didn't work. Also, because its a middleware, I can't overload the Invoke method.
Is there a solution other than requesting the service collection and resolving the dependency myself?
The answer is incredibly simple:
public async Task Invoke(HttpContext httpContext, MyLoggingService logger = null)
Instead of making dependencies optional, consider:
Programming to an abstraction, e.g. IMyLoggingService
Register a Null Object implementation
For instance:
public class CustomMiddleware1 : IMiddleware
{
private readonly IMyLoggingService logger;
public CustomMiddleware1(IMyLoggingService logger) => this.logger = logger;
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
this.logger.Log("Before");
await next(context);
this.logger.Log("After");
}
}
Null Object implementation:
public sealed class NullMyLoggingService : IMyLoggingService
{
public void Log(LogEntry e) { }
}
Registrations:
services.AddSingleton<IMyLoggingService>(new NullMyLoggingService());
app.Use<CustomMiddleware1>();
The call to AddSingleton<IMyLoggingService>(new NullMyLoggingService()) ensures a registration for IMyLoggingService always exists. This prevents complexity in consumers, who would otherwise have to add conditional logic for the case that the logger isn't present.
This null implementation can be replaced by simply adding a second IMyLoggingService after the first:
services.AddScoped<IMyLoggingService, DbMyLoggingService>();
app.Use<CustomMiddleware1>();
After living under a rock for 2 years employment wise, I am now confronted with Blazor at my new Workplace and have a lot of catch up to do after doing mostly ASP.NET Framework MVC prior to the 2 Years.
Trying myself on Blazor server side, I tried to apply my past knowledge which included cancellationtokens for async operations and i couldn't find much information about them in combination with Blazor.
Are they still a Best Practice or did they became Obsolete at some point?
I did found this previously asked question which recommends creating a tokensource on the OnInitializedAsync() method and cancelling it on Dispose() which i honestly find a bit crude.
(I would need to implement this for each page and you know... DRY)
I also found this Article about advanced Scenarios on Microsoft Docs that explains how to implement a Circuit Handler, which honestly is a bit beyond me right now and most likely way out of scope for my little home-project.
In comparison, in asp.net Framework MVC i would build a Controller like this:
namespace SampleWebsite.Controllers
{
public class SampleController : ApiController
{
private readonly MyEntities _entities = new MyEntities();
public async Task<IHttpActionResult> MyAsyncApi(CancellationToken cancellationToken)
{
var result = _entities.MyModel.FirstOrDefault(e => e.Id == 1, cancellationToken: cancellationToken);
return OK(result);
}
}
}
The CancellationToken will be injected by asp.net Framework / Core and is directly linked to the current context connection-pipe.
Hence, if the user closes the connection, the token becomes invalid.
I would have assumed that for asp.net core and blazor where dependency-injections is a big part of it, this would be the case here too, but i could not find any documentation about this here.
So, should cancellationtokens still be used at this point or does Microsoft do some magic in the background for asynchronous tasks? And if yes, what would be the best implementation?
EDIT:
Here would be my Setup to clarify:
The Blazor-Component:
#page "/Index"
#inject IIndexService Service
#* Some fancy UI stuff *#
#code {
private IEnumerable<FancyUiValue> _uiValues;
protected override async Task OnInitializedAsync()
{
_uiValues = await Service.FetchCostlyValues();
}
}
And the Injected Service-Class that does the heavy lifting:
public interface IIndexService
{
Task<IEnumerable<FancyUiValue>> FetchCostlyValues();
}
public class IndexService : IIndexService
{
public async Task<IEnumerable<FancyUiValue>> FetchCostlyValues()
{
var uiValues = await heavyTask.ToListAsync(); // <-- Best way to get a cancellationtoken here?
return uiValues;
}
}
My question is, what would be the best way to get a token in the specificed part of the code or would it be irrelevant because the Server would kill all running tasks when the connection (as example) ends?
After 2 years of experience with Blazor, i figured that the only reliable way to pass an CancellationToken to a Task within an Object of a longer Lifetime (e.g. Singleton or Scoped Service) is the combination of IDisposeable and CancellationTokenSource
#page "/"
#implements IDisposable
*# Razor Stuff *#
#code
{
private CancellationTokenSource _cts = new();
protected override async Task OnInitializedAsync()
{
await BusinessLogicSingleton.DoExpensiveTask(_cts.Token);
}
#region IDisposable
public void Dispose()
{
_cts.Cancel();
_cts.Dispose();
}
#endregion
}
On repeated use or just to comply to the DRY-Rule, you can also inherit from the ComponentBase Class and then use that Class for your Components that require to pass a CancellationToken:
public class CancellableComponent : ComponentBase, IDisposable
{
internal CancellationTokenSource _cts = new();
public void Dispose()
{
_cts.Cancel();
_cts.Dispose();
}
}
#page "/"
#inherits CancellableComponent
#* Rest of the Component *#
I also found that while you could Inject the IHttpContextAccessor and use the HttpContext.RequestAborted token which is the same that will be generated and injected in your ASP.Net MVC Method Calls, as of the current .Net6 Version it will never fire even after the Connection to the Client is severed and the providing HttpContext is disposed.
This may be a case for the Developer-Team on Github as i do see UseCases for it where the User is allowed to exit the Component while the Task keeps on going until the User leaves the Website completely.
(For such cases, my recommended Workaround would be to write your own CircuitHandler that will give you Events for when a Circuit is removed.)
Instead of adding a CancellationTokenSource to all components manually, you can create a base component that expose a CancellationToken and use this base component automatically in all components of the project
Implement your ApplicationComponentBase
public abstract class ApplicationComponentBase: ComponentBase, IDisposable
{
private CancellationTokenSource? cancellationTokenSource;
protected CancellationToken CancellationToken => (cancellationTokenSource ??= new()).Token;
public virtual void Dispose()
{
if (cancellationTokenSource != null)
{
cancellationTokenSource.Cancel();
cancellationTokenSource.Dispose();
cancellationTokenSource = null;
}
}
}
Then add #inherits ApplicationComponentBase to _Imports.razor file
In page a call:
await Task.Delay(50000, CancellationToken);
Then try to navigate to another page, the task you called will be cancelled
I have a lot of commands and queries and most of them need same interfaces DI'ed to do different things. Is it possible to some how reduce this clutter that each and every one of my handler needs and it is repeated over and over?
public class GetCoinByIdQueryHandler : IRequestHandler<GetCoinByIdQuery, CoinModel>
{
private readonly EventsContext context;
private readonly ICacheClient cache;
private readonly ILogger logger;
private readonly IMapper mapper;
private readonly Settings settings;
public GetCoinByIdQueryHandler(
EventsContext context, ICacheClient cache, ILogger logger,
IMapper mapper, IOptions<Settings> settings)
{
this.context = context;
this.cache = cache;
this.logger = logger;
this.mapper = mapper;
this.settings = settings.Value;
}
}
This may not be directly related to Mediatr but I am looking for a more elegant way of just reducing all the common ones to maybe ONE DI'ed param.
I am using Autofac as my DI container if it makes any difference.
EDIT: possibly having base class that all the handlers inherit from and in the base class get access to all the interfaces and set them as properties on the base class, but I have no idea how to achieve this.
EDIT 2: Autofac has property injection but that seems like it is not the right approach, so people who are using Mediatr, how are you handling of repeating yourself over and over. Every open source project that uses Mediatr that I have seen, seem to not address the repeating yourself problem.
When I find myself in the situation where several handlers have many common dependencies, I look at 2 things:
whether my handlers are doing too much; and
if it's the case, whether I can refactor some of the behavior in a separate class
As an example, in the handler code you posted, there's a cache client, which could possibly mean your handler does 2 things:
executing the business logic to retrieve the coin; and
doing some logic do return an already cached coin, or caching the one you just retrieved
MediatR has the concept of behaviors which allow you to handle cross-cutting concerns in a single place; this is potentially applicable to caching, logging and exception handling. If you're familiar with ASP.NET Core middlewares, they follow the same concept, as each behavior is given:
the current request (or query in MediatR lingo); and
the next item in the pipeline, which can be either another behavior or the query handler
Let's see how we could extract the caching logic in a behavior. Now, you don't need to follow this example to a T, it's really just one possible implementation.
First, we'll define an interface that we apply to queries that need to be cached:
public interface IProvideCacheKey
{
string CacheKey { get; }
}
Then we can change GetCoinByIdQuery to implement that new interface:
public class GetCoinByIdQuery : IRequest<CoinModel>, IProvideCacheKey
{
public int Id { get; set; }
public string CacheKey => $"{GetType().Name}:{Id}";
}
Next, we need to create the MediatR behavior that will take care of caching. This uses IMemoryCache provided in ASP.NET Core solely because I don't know the definition of your ICacheClient interface:
public class CacheBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IProvideCacheKey, IRequest<TResponse>
{
private readonly IMemoryCache _cache;
public CacheBehavior(IMemoryCache cache)
{
_cache = cache;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
// Check in cache if we already have what we're looking for
var cacheKey = request.CacheKey;
if (_cache.TryGetValue<TResponse>(cacheKey, out var cachedResponse))
{
return cachedResponse;
}
// If we don't, execute the rest of the pipeline, and add the result to the cache
var response = await next();
_cache.Set(cacheKey, response);
return response;
}
}
Lastly, we need to register the behavior with Autofac:
builder
.RegisterGeneric(typeof(CacheBehavior<,>))
.As(typeof(IPipelineBehavior<,>))
.InstancePerDependency();
And there we have it, caching is now a cross-cutting concern, which implementation lives in a single class, making it easily changeable and testable.
We could apply the same pattern for different things and make the handlers only responsible for business logic.