Clean Controllers with Dynamic Dispatch
The mediator pattern keeps the clutter down
Broken promises
How many times have you heard a fellow dev utter, “I prefer very little code in a controller”, but then deliver a PR with a ton of code in a route? Admittedly I’m guilty of it to, so don’t sweat it. I feel it happens a lot because it’s just too convenient to drop some code into an action because the canvas is right there for you to start painting (with code).
Besides cluttering up your controllers with business logic, it’s also easy to leak web-related things into classes that handle business logic. I believe that a web controller exists to handle the request/response/auth-y stuff only, and not the business logic. In order to force myself to keep them separate, I prefer a mechanism to pass my business request to while leaving the web portion behind until the logic returns an answer.
Enter the mediator pattern
Rather than explicitly calling a domain service, the meditator pattern expects you to “send” a request which is then received and processed in a “handler”. From the perspective of the sender, it has no idea what will handle the request. From the perspective of the receiving handler, it has no idea where the request originated from. The mediator is the Wizard of Oz directing traffic and only it knows.
In this way anything “controller” related (e.g. request, HTTP, auth, URL encodings, etc) will stay in the controller, and the handler has no idea what an HTTP context is. In fact, when we separate these concerns, it makes it even easier to invoke our business logic regardless if it came from a web request off the message queue.
MediatR
So how do we use the mediator pattern in a web app? We’ll use a simple Nuget package written by Jimmy Bogard called MediatR. MediatR has a few features, but the main one we’ll look at is forwarding requests from the controller to a handler. Before we look at it, let’s look at a controller without using the mediator pattern. For our example we’re going to use the sample code Microsoft provides in a new WebAPI project:
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}Obviously we can see the sample “business logic” happening directly in the controller. Most of us wouldn’t do this, rather we’d call out to a domain service instead. Instead of showing the use of a domain service, let’s see what MediatR would look like. We’ll be moving the magic numbers to the endpoint arguments to illustrate passing the arguments along:
[HttpGet]
[Route("mediatr")]
public async Task<IEnumerable<WeatherForecast>> GetMediatr(int rangeBegin, int rangeEnd, int temperatureBegin, int temperatureEnd)
{
var result = await _mediator.Send(new GetWeatherForecastRequest
{
RangeBegin = rangeBegin,
RangeEnd = rangeEnd,
TemperatureBegin = temperatureBegin,
TemperatureEnd = temperatureEnd
});
return result.Forecasts;
}In the example above, we’re now forwarding the request from the controller to a handler which will process the business logic. We’ll let the controller still handle any sort of response codes and necessary auth. We will now look at the handler itself:
public class GetWeatherForecastHandler : IRequestHandler<GetWeatherForecastRequest, GetWeatherForecastResponse>
{
...
public Task<GetWeatherForecastResponse> Handle(GetWeatherForecastRequest request, CancellationToken cancellationToken)
{
var result = Enumerable.Range(request.RangeBegin, request.RangeEnd).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(request.TemperatureBegin, request.TemperatureEnd),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
return Task.FromResult(new GetWeatherForecastResponse
{
Forecasts = result
});
}
}A handler is defined by implementing an IRequestHandler<TRequest, TResponse> where TRequest is an IRequest<TResponse>. This interface also requires the dev to implement an async Handle(…) method.
Annoyingly, the Handle method doesn’t have an async suffix.
What’s not as obvious, is you are defining a request, a response and a handler object. The request and responses are simply POCO’s:
public class GetWeatherForecastRequest : IRequest<GetWeatherForecastResponse>
{
public int RangeBegin { get; set; }
public int RangeEnd { get; set; }
public int TemperatureBegin { get; set; }
public int TemperatureEnd { get; set; }
}public class GetWeatherForecastResponse
{
public IEnumerable<WeatherForecast> Forecasts { get; set; }
}So what did we gain for all that?
You get separation between the controller and the part of your app that should be handling business logic. No longer are you tempted to put business logic in your controller. Also your controller has no idea what class will execute the logic since typically you’d be calling a domain service in your controller.
MediatR lowers the coupling between objects. It also has the ability to publish other types of messages, but I’ll leave it to you to read the docs.
Finally if your app handles queue messages, you can now just publish the message details through MediatR and it will get routed to the same handler that any HTTP request would also route to.
Use the mediator pattern to keep your controllers for network concerns and delegates the business logic to something agnostic to the internet. You can get a complete working example using MediatR here.
Happy coding!


Nice article. I use Mediatr quite extensively, it’s great