As we discussed in earlier posts, the Mediator and Command are patterns that can be implemented in different way. And those patterns already exist in many coding frameworks. As a matter of fact, the ASP.NET WebApi request pipeline can be considered a Mediator and Command implementation, especially when building MinimalApi.
Remember that a Mediator encapsulates a set of objects and clients communicate directly with the Mediator and not the underlying code handlers. In the WebApi request pipeline, the client sends an HTTP request with data in its body, headers, routes, or query string to a URI. The client has no knowledge of how that request will be handled. ASP.NET handles taking the request, finding the mapped endpoints, and matching the HTTP URI, data, and function that handles the request. This is precisely what a mediator would do when the concept is applied to a WebApi HTTP request.
Then in MinimalApi, we define URI paths and HTTP actions that we handle on our server, using functions like MapGet, MapPost, etc. The delegate that gets called in that mapping is the equivalent of a CommandHandler. When that address and action combination is requested, the CommandHandler function is called with the request as the Command payload. Now because this is an HTTP request, it also has all of the other data associated with a request (headers, routes, query string) which can also be provided to the handler.
This is a lot of theory so far, but let’s see the concepts in action in an alternate implementation of the WeatherForecast endpoint that uses D20Tek.Mediator to define the commands.
First, let’s start with a basic WebApi project that you can create from here.
Define the Command and CommandHandler
We can use D20Tek.Mediator’s ICommand and ICommandHandler interfaces to design commands in a uniform way. We can even re-use these classes from earlier posts. For now, let me redefine the command and handler.
public sealed class GetForecasts
{
public record Command : ICommand<ForecastResponse[]>;
public class Handler : ICommandHandler<Command, ForecastResponse[]>
{
private static readonly string[] _summaries =
[
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
];
public ForecastResponse[] Handle(Command command)
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new ForecastResponse
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
_summaries[Random.Shared.Next(_summaries.Length)]
))
.ToArray();
return forecast;
}
}
}
Next, we can manually register this command handler with the dependency injection container in Program.cs.
builder.Services.AddScoped<ICommandHandler<GetForecasts.Command, ForecastResponse[]>, GetForecasts.Handler>();
Now our command is ready to be used.
Calling Command from MinimalApi Endpoint
MinimalApi have a simple mechanism for mapping a route, which we also add to our Program.cs file.
app.MapGet("/weatherforecast", ([FromServices] ICommandHandler<GetForecasts.Command, ForecastResponse[]> handler) =>
handler.Handle(new GetForecasts.Command()))
.WithName("GetWeatherForecast");
In the code above, we just inject the ICommandHandler implementation for GetForecasts as a parameter to the anonymous function. Then our code just calls handler.Handle with the appropriate command. It’s straightforward and doesn’t require the IMediator service because we are already using the request pipeline as our mediator.
While using the IMediator gives us another level of indirection, which is helpful in complex services, it may not always be appropriate or necessary to use that. In which case, you can just inject the command into the endpoint and call it. Using DI that implementation is still replaceable and easy to drive via unit tests.
We benefit from using the common definitions of ICommand and ICommandHandler because our application structure and design can be the same whether the IMediator service is used or not. Having common usage patterns makes code bases easier to navigate and learn. And we can graduate from directly injecting the ICommandHandler to using the IMediator service later when our service grows large and more unwieldy.
Using with Controllers
This concept also works with controller based WebApis. Each function in a controller can represent a route and HTTP verb. Then, that function can accept parameters and services as well. And, one of the services can be the command handler (ICommandHandler<GetForecasts.Command, ForecastResponse[]>)… just like in the MinimalApi above.
Finally, just use the command handler directly (handler.Handle(new GetForecasts.Command())).
Conclusion
In this article, we applied what we know about the Mediator and Command patterns to see how it applies to the ASP.NET WebApi request pipeline. As we can see, ASP.NET implemented its own version of the Mediator pattern in that pipeline. So in smaller WebApi projects, it makes sense to use ICommandHandler objects from within the MinimalApi mappings directly (rather than using the IMediator service). For more complex scenarios, the IMediator service makes more sense. By using the D20Tek.Mediator definitions of ICommand and ICommandHandler, you can start with a simpler implementation and graduate to use the full IMediator service as you application grows.