Please see the synchronous code below:
public class PersonHandler : IRequestHandler<Person, Person>
{
public Task<Person> Handle(Person request, CancellationToken cancellationToken)
{
request.ID = 1;
request.Name = "Brian";
request.Age = 53;
return Task.FromResult(request);
}
}
and the calling code below:
var response2 = mediator.Send(new Person());
This works as expected. Please see the asynchronous code below:
public class PersonHandlerAsync : IRequestHandler<Person, Person>
{
public async Task<Person> Handle(Person request, CancellationToken cancellationToken)
{
request.ID = 1;
request.Name = "Brian";
request.Age = 53;
var result = await Task.FromResult(request);
await Task.Delay(30000);
return result;
}
}
and the calling code below:
var response = Task.Run(() => mediator.Send(new Person()));
This code works as expected.
Is it possible to have two handlers for the same class (Person) i.e. one synchronous and the other asynchronous? If I put both handler classes in my code, then both the mediator.send lines i.e. mediator.Send(new Person()); and Task.Run(() => mediator.Send(new Person())); call the synchronous method.
How can I tell mediatr, which handler class to use? i.e. PersonHandler for synchronous calls and PersonHandlerAsync for asynchronous calls.
You can't do it how you've specified.
Declare whatever flavor of handler you need - sync, async or cancellable async. From the IMediator side, the interface is async-only, designed for modern hosts.
However, one workaround would be to create a PersonRequest and PersonRequestAsync class, inherited from Person class:
public class Person
{
public int Id {get; set;}
public string Name {get; set;}
public int Age {get; set;}
}
//sync request
public class PersonRequest : Person, IRequest<Person>
{ }
//async request
public class PersonRequestAsync : Person, IRequest<Person>
{ }
Then, your handlers could look something like this:
public class PersonHandler : IRequestHandler<PersonRequest, Person>
{
public Task<Person> Handle(Person request, CancellationToken cancellationToken)
{
request.ID = 1;
request.Name = "Brian";
request.Age = 53;
return Task.FromResult(request);
}
}
And your async handler as follows:
public class PersonHandlerAsync : IRequestHandler<PersonRequestAsync, Person>
{
public async Task<Person> Handle(PersonRequestAsync request, CancellationToken cancellationToken)
{
request.ID = 1;
request.Name = "Brian";
request.Age = 53;
//not sure what this is? Hopefully it's just here for demo purposes!
var result = await Task.FromResult(request);
await Task.Delay(30000);
return result;
}
}
This effectively ties PersonRequestAsync to PersonHandlerAsync
Related
Pretty straightforward question. I want to be able to delete multiple IDs, just like the example below.
public async Task<ActionResult> Delete(List<int> id)`
Snippet
[HttpDelete("{id:int}")]
public async Task<ActionResult> Delete(int id)
{
await Mediator.Send(new DeleteRoomCommand { Id = id }).ConfigureAwait(false);
return NoContent();
}
public class DeleteRoomCommand : IRequest
{
public long Id { get; set; }
}
public class DeleteRoomCommandHandler : IRequestHandler<DeleteRoomCommand>
{
private readonly IApplicationDbContext _context;
public DeleteRoomCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<Unit> Handle(DeleteRoomCommand request, CancellationToken cancellationToken)
{
var entity = await _context.Rooms.FindAsync(request.Id).ConfigureAwait(false);
if (entity == null)
{
throw new NotFoundException(nameof(Room), request.Id);
}
_context.Rooms.Remove(entity);
await _context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
return Unit.Value;
}
}
You seem to be asking two questions:
How do I write a new command and handler for MediatR that will accept and pass an enumerable of IDs instead of just one?
How do I write a DbContext call to handle that enumerable?
The IRequest object is just a simple class that contains the data you need to process it. You can create a new pluralized command class that accepts an enumerable of IDs, and a new handler designed to handle that case. You'll need to tweak this based on your own testing.
public class DeleteRoomsCommand : IRequest
{
public IEnumerable<long> Ids { get; set; }
}
public class DeleteRoomsCommandHandler : IRequestHandler<DeleteRoomsCommand>
{
private readonly IApplicationDbContext _context;
public DeleteRoomCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<Unit> Handle(DeleteRoomsCommand request, CancellationToken cancellationToken)
{
var entities = await _context.Rooms.Where(r => request.Ids.Contains(r.Id)); // .ConfigureAwait(false);
_context.Rooms.RemoveRange(entities);
await _context.SaveChangesAsync(cancellationToken); // .ConfigureAwait(false);
return Unit.Value;
}
}
Your new call is instantiated the same, taking the IDs received by your controller and assigning them to a new MediatR command:
await Mediator.Send(new DeleteRoomsCommand { Ids = ids }); //.ConfigureAwait(false);
Your new method could accept the list of IDs via query parameter or via the body, depending on your need or convention.
public class RollingRequests
{
private const int DefaultNumSimultaneousRequests = 10;
private readonly HttpClient _client; // Don't worry about disposing see https://stackoverflow.com/questions/15705092/do-httpclient-and-httpclienthandler-have-to-be-disposed
private readonly HttpCompletionOption _httpCompletionOption;
private readonly int _numSimultaneousRequests;
public RollingRequests() : this(DefaultNumSimultaneousRequests)
{
}
public RollingRequests(int windowSize) : this(new HttpClient(), windowSize)
{
}
public RollingRequests(HttpClient client, int numSimultaneousRequests, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead)
{
_client = client;
_numSimultaneousRequests = numSimultaneousRequests;
_httpCompletionOption = httpCompletionOption;
}
public async Task ExecuteAsync(List<string> urls, CancellationToken cancellationToken, Action<HttpResponseHeaders, string> requestCallback = null)
{
var nextIndex = 0;
var activeTasks = new List<Task<Tuple<string, HttpResponseMessage>>>();
var startingIndex = Math.Min(_numSimultaneousRequests, urls.Count);
for (nextIndex = 0; nextIndex < startingIndex; nextIndex++)
{
activeTasks.Add(RequestUrlAsync(urls[nextIndex], cancellationToken));
}
while (activeTasks.Count > 0)
{
var finishedTask = await Task.WhenAny(activeTasks).ConfigureAwait(false);
activeTasks.Remove(finishedTask);
var retryUrl = await ProcessTask(await finishedTask, requestCallback).ConfigureAwait(false);
// If retrying, add the URL to the end of the queue
if (retryUrl != null)
{
urls.Add(retryUrl);
}
if (nextIndex < urls.Count)
{
activeTasks.Add(RequestUrlAsync(urls[nextIndex], cancellationToken));
nextIndex++;
}
}
}
private async Task<string> ProcessTask(Tuple<string, HttpResponseMessage> result, Action<HttpResponseHeaders, string> requestCallback = null)
{
var url = result.Item1;
using (var response = result.Item2)
{
if (!response.IsSuccessStatusCode)
{
return url;
}
if (requestCallback != null)
{
string content = null;
if (_httpCompletionOption == HttpCompletionOption.ResponseContentRead)
{
content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}
requestCallback(response.Headers, content);
}
return null;
}
}
private async Task<Tuple<string, HttpResponseMessage>> RequestUrlAsync(string url, CancellationToken ct)
{
var response = await _client.GetAsync(url, _httpCompletionOption, ct).ConfigureAwait(false);
return new Tuple<string, HttpResponseMessage>(url, response);
}
}
This is a class that allows for X simultaneous requests to be on-going at once. When I am unit-testing this class and I moq the HttpClient giving each request a 1 second delay the initial activeTasks.Add is taking 5 seconds if I have 5 requests, suggesting to me that RequestUrlAsync isn't truly async.
Can anyone spot the issue?
Edit:
This is how I am sleeping the mocked client
_messageHandlerMock
.Protected()
.Setup<Task<HttpResponseMessage>>(MethodToMoq, ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.Callback(() => Thread.Sleep(1000))
.ReturnsAsync(callback)
.Verifiable();
I tested your class RollingRequests with actual urls and works as expected. Then I replaced await _client.GetAsync(... with await Task.Delay(1000) and continued working as expected. Then replaced the same line with Thread.Sleep(1000) and replicated your problem.
Moral lesson: avoid blocking the current thread when running asynchronous code!
(It would be easier to answer if you had provided a Minimal, Reproducible Example)
Mixing Thread.Sleep with asynchronous code isn't a good idea because it is a blocking call.
Mocking internals should also be avoided.
Here's a simple example of a test that takes about 1 second to execute 10 requests:
async Task Test()
{
var httpClient = new HttpClient(new TestHttpMessageHandler());
var ticks = Environment.TickCount;
await Task.WhenAll(Enumerable.Range(0, 10).Select(_ => httpClient.GetAsync("https://stackoverflow.com/")));
Console.WriteLine($"{Environment.TickCount - ticks}ms");
}
class TestHttpMessageHandler : HttpMessageHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
await Task.Delay(1000);
return new HttpResponseMessage();
}
}
So I've recently started to learn about using the MediatR library with ASP.NET Core Web API and I'm unsure how to go about returning a NotFound() when a DELETE/PUT/PATCH request has been made for an unexisting resource.
If we take DELETE for example, here is my controller action:
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
await Mediator.Send(new DeleteCourseCommand {Id = id});
return NoContent();
}
The Command:
public class DeleteCourseCommand : IRequest
{
public int Id { get; set; }
}
The Command Handler:
public class DeleteCourseCommandHandler : IRequestHandler<DeleteCourseCommand>
{
private readonly UniversityDbContext _context;
public DeleteCourseCommandHandler(UniversityDbContext context)
{
_context = context;
}
public async Task<Unit> Handle(DeleteCourseCommand request, CancellationToken cancellationToken)
{
var course = await _context.Courses.FirstOrDefaultAsync(c => c.Id == request.Id, cancellationToken);
if (course != null)
{
_context.Courses.Remove(course);
var saveResult = await _context.SaveChangesAsync(cancellationToken);
if (saveResult <= 0)
{
throw new DeleteFailureException(nameof(course), request.Id, "Database save was not successful.");
}
}
return Unit.Value;
}
}
As you can see in the Handle method, if there is an error when saving, an exception is thrown which results in a 500 internal server error (which is correct I believe). But if the Course is not found, how can I feed this back to the Action on the Controller? Is it simply a case of invoking a Query to GET the course in the Controller Action, then return NotFound() if it doesn't exist or then invoke the Command to DELETE the Course? This would work of course but of all the examples I've been through, I haven't come across an Action which uses two Mediator calls.
MediatR supports a Request/Response pattern, which allows you to return a response from your handler class. To use this approach, you can use the generic version of IRequest, like this:
public class DeleteCourseCommand : IRequest<bool>
...
In this case, we're stating that bool will be the response type. I'm using bool here for simplicity: I'd suggest using something more descriptive for your final implementation but bool suffices for explanation purposes.
Next, you can update your DeleteCourseCommandHandler to use this new response type, like this:
public class DeleteCourseCommandHandler : IRequestHandler<DeleteCourseCommand, bool>
{
...
public async Task<bool> Handle(DeleteCourseCommand request, CancellationToken cancellationToken)
{
var course = ...
if (course == null)
return false; // Simple example, where false means it wasn't found.
...
return true;
}
}
The IRequestHandler being implemented now has two generic types, the command and the response. This requires updating the signature of Handle to return a bool instead of Unit (in your question, Unit isn't being used).
Finally, you'll need to update your Delete action to use the new response type, like this:
public async Task<IActionResult> Delete(int id)
{
var courseWasFound = await Mediator.Send(new DeleteCourseCommand {Id = id});
if (!courseWasFound)
return NotFound();
return NoContent();
}
I like returning events from my commands. The command is telling your application what the client wants it to do. The response is what it actually did.
BTW—it's said that command handlers should return anything. That's really only true in a fully async environment where the command won't be completed until sometime after the response to the client that it's accepted. In that case, you would return Task<Unit> and publish these events. The client would get them via some other channel, like a SignalR hub once they were raised. Either way, events are the best way to tell a client what's going on in your application.
Start by defining an interface for your events
public interface IEvent
{
}
Then, create events for each of the things that can happen in a command. You can include information in them if you'd want to do something with that information or just leave them empty if the class itself is enough.
public class CourseNotFoundEvent : IEvent
{
}
public class CourseDeletedEvent : IEvent
{
}
Now, have your command return an event interface.
public class DeleteCourseCommand : IRequest<IEvent>
{
}
Your handler would look something like this:
public class DeleteCourseCommandHandler : IRequestHandler<DeleteCourseCommand, IEvent>
{
private readonly UniversityDbContext _context;
public DeleteCourseCommandHandler(UniversityDbContext context)
{
_context = context;
}
public async Task<IEvent> Handle(DeleteCourseCommand request, CancellationToken cancellationToken)
{
var course = await _context.Courses.FirstOrDefaultAsync(c => c.Id == request.Id, cancellationToken);
if (course is null)
return new CourseNotFoundEvent();
_context.Courses.Remove(course);
var saveResult = await _context.SaveChangesAsync(cancellationToken);
if (saveResult <= 0)
{
throw new DeleteFailureException(nameof(course), request.Id, "Database save was not successful.");
}
return new CourseDeletedEvent();
}
}
Finally, you can use pattern matching on your web API to do things based on the event that gets returned.
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
var #event = await Mediator.Send(new DeleteCourseCommand {Id = id});
if(#event is CourseNotFoundEvent)
return NotFound();
return NoContent();
}
I managed to solve my problem through some more examples I found. The solution is to define custom Exceptions such as NotFoundException and then throw this in the Handle method of the Query/Command Handler. Then in order for MVC to handle this appropriately, an implementation of ExceptionFilterAttribute is needed to decide how each Exception is handled:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
if (context.Exception is ValidationException)
{
context.HttpContext.Response.ContentType = "application/json";
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
context.Result = new JsonResult(
((ValidationException)context.Exception).Failures);
return;
}
var code = HttpStatusCode.InternalServerError;
if (context.Exception is NotFoundException)
{
code = HttpStatusCode.NotFound;
}
context.HttpContext.Response.ContentType = "application/json";
context.HttpContext.Response.StatusCode = (int)code;
context.Result = new JsonResult(new
{
error = new[] { context.Exception.Message }
});
}
}
Startup Class:
services.AddMvc(options => options.Filters.Add(typeof(CustomExceptionFilterAttribute)));
Custom Exception:
public class NotFoundException : Exception
{
public NotFoundException(string entityName, int key)
: base($"Entity {entityName} with primary key {key} was not found.")
{
}
}
Then in the Handle method:
if (course != null)
{
_context.Courses.Remove(course);
var saveResult = await _context.SaveChangesAsync(cancellationToken);
if (saveResult <= 0)
{
throw new DeleteFailureException(nameof(course), request.Id, "Database save was not successful.");
}
}
else
{
throw new NotFoundException(nameof(Course), request.Id);
}
return Unit.Value;
This seems to do the trick, if anyone can see any potential issues with this please let me know!
When ovveride the IExceptionHandler, the response does not reach the DelegatingHandler when a unexpected exception occurs. How can I fix this?
In webapi 2, I want to implement a audit logger for request and response messages. I also want to add a global exception handler. However, when I replace the IExceptionHandler with my custom implementation. the response never reaches the DelegatingHandler -on exception - And thus the audit for response is lost.
in WebApiConfig
// add custom audittrail logger
config.MessageHandlers.Add(new AuditLogHandler());
// replace global exception handeling
config.Services.Replace(typeof(IExceptionHandler), new WebAPiExceptionHandler());
Custom Exception Handler
public class WebAPiExceptionHandler : ExceptionHandler
{
//A basic DTO to return back to the caller with data about the error
private class ErrorInformation
{
public string Message { get; set; }
public DateTime ErrorDate { get; set; }
}
public override void Handle(ExceptionHandlerContext context)
{
context.Result = new ResponseMessageResult(context.Request.CreateResponse(HttpStatusCode.InternalServerError,
new ErrorInformation { Message = "Iets is misgegaan", ErrorDate = DateTime.UtcNow }));
}
}
Custom Auditlogger
public class AuditLogHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.Content != null)
{
var task = await request.Content.ReadAsStringAsync();
// .. code for loggign request
}
var result = await base.SendAsync(request, cancellationToken);
// .. code for logging response
// when I do not replace WebAPiExceptionHandler, code is reachred here
// When I Do use WebAPiExceptionHandler, code is not reached here
return result;
}
}
Code for throwing exception in webapi
public class Values_v2Controller : ApiController
{
public string Get(int id)
{
throw new Exception("haha");
}
}
Dont use ExceptionHandler as base class, implement interface IExceptionHandler
public class WebAPiExceptionHandler : IExceptionHandler
{
public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
{
var fout = new ErrorInformation
{
Message = "Iets is misgegaan"
, ErrorDate = DateTime.UtcNow
};
var httpResponse = context.Request.CreateResponse(HttpStatusCode.InternalServerError, fout);
context.Result = new ResponseMessageResult(httpResponse);
return Task.FromResult(0);
}
private class ErrorInformation
{
public string Message { get; set; }
public DateTime ErrorDate { get; set; }
}
}
The problem is that ExceptionHandler only executes Handle(ExceptionHandlerContext context) method if ShouldHandle(ExceptionHandlerContext context) returns true.
Overriding bool ShouldHandle(ExceptionHandlerContext context) to always return true fix the problem for me.
public async Task<HttpResponseMessage> getOne(HttpRequestMessage request, int id)
{
return CreateResponse(async () =>
{
var category = await _unitOfWork.Categories.GetSingleAsync(id);
var categoryVm = Mapper.Map<Category, CategoryViewModel>(category);
HttpResponseMessage response = request.CreateResponse<CategoryViewModel>(HttpStatusCode.OK, categoryVm);
return response;
});
}
Base Class
protected Task<IHttpActionResult> CreateResponse(Func<IHttpActionResult, Task> function)
{
IHttpActionResult response = null;
try
{
response = function.Invoke();
}
}
Read up on Cross cutting concerns.
You are giving yourself unnecessary trouble. Your example can be reduced to :
public async Task<IHttpActionResult> getOne(int id) {
var category = await _unitOfWork.Categories.GetSingleAsync(id);
var categoryVm = Mapper.Map<Category, CategoryViewModel>(category);
return Ok(categoryVm);
}
Try to keep controller lean.
Check this answer