MediatR and Blazor Server?
March 14, 2019 · 4 minute read · Tags: blazor
Note: Razor Components is now called Blazor Server. Bear that in mind as you read on!
With MediatR we can create ASP.NET controllers which stick to their core responsibilities (handling incoming requests, returning responses etc.) and delegate to MediatR to “trigger” business logic (commands and queries).
public class UserController {
private mediator;
public UserController(IMediator mediator){
this.mediator = mediator;
}
public async Task<IActionResult> ListAll(){
var forecasts = await mediator.Send(new ListAll.Query());
return View(forecasts);
}
}
In this case, we fire off the query to list all users then return the results with our View.
Whilst investigating Razor Components it lead me to wonder if MediatR would work in this context too.
By way of example, here’s a Razor component (taken from the the default new starter project I touch on here) whose sole purpose is to retrieve and display weather forecasts.
@page "/fetchdata"
@using WebApplication11.Services
@inject WeatherForecastService ForecastService
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
@functions {
WeatherForecast[] forecasts;
protected override async Task OnInitAsync()
{
forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
}
}
This code will run on the server. On load it will invoke the ForecastService
to retrieve weather forecasts, passing in today’s date.
Unlike the ASP.NET MVC approach, we don’t need to return data with a view, we can just update the forecasts
property and the ASP.NET Core will update the UI using the new data.
Adding MediatR to the mix
You’ll need to bring in a couple of packages to add MediatR to the project.
Install-Package MediatR
Install-Package MediatR.Extensions.Microsoft.DependencyInjection
This means we can now register any MediatR handlers we have in our project with one call in startup.cs
.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddNewtonsoftJson();
// add this
services.AddMediatR();
services.AddRazorComponents();
}
To migrate the forecasts logic over to MediatR we’ll need to create an equivalent handler, here’s mine…
public class ListAll
{
public class Query : IRequest<Model>
{
public DateTime StartDate { get; set; }
}
public class Model
{
public IEnumerable<WeatherForecast> Forecasts { get; set; }
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF { get; set; }
public string Summary { get; set; }
}
}
public class QueryHandler : IRequestHandler<Query, Model>
{
private static string[] Summaries = new[] {
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm",
"Balmy", "Hot", "Sweltering", "Scorching" };
public async Task<Model> Handle(
Query request,
CancellationToken cancellationToken)
{
var rng = new Random();
var forecasts = Enumerable.Range(1, 5).Select(index =>
new Model.WeatherForecast
{
Date = request.StartDate.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
});
return new Model { Forecasts = forecasts };
}
}
}
To invoke our handler we’ll need to call mediator.send
and to do that we we need to inject an instance of IMediator into our Razor Component, just as we would our MVC controllers.
@page "/fetchdata"
<!-- add this -->
@using MediatR
@inject IMediator Mediator
Now we have access to Mediator
so we need to modify our component’s code to call Mediator
instead of ForecastService
.
@functions {
ListAll.Model model;
protected override async Task OnInitAsync()
{
model = await Mediator.Send(new ListAll.Query {
StartDate = DateTime.Now
});
}
}
I’ve removed the forecasts
property and replaced it with a property for the Model returned by our Mediator call (ListAll.Model
).
This model includes a Forecasts
property so now we just need to tweak the razor template to look there for the forecasts.
<h1>Weather forecast</h1>
@if (model == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in model.Forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
Gotchas
The only thing that really caught me out when setting this up was that I originally called the ListAll
class (which holds our MediatR request and response) List
.
However, this then clashes with the Razor Component itself because it is also called List
and both classes exist in the same namespace (folder).
If anyone can think of a way round this whilst still keeping the function and handler in the same folder, let me know!
Save yourself some keystrokes
If you’re going to use MediatR for most/all of your components and pages, you can skip adding @inject IMediator Mediator
to every individual .razor file by adding it to _ViewImports
file instead.
@using MediatR
@inject IMediator Mediator
Now every component or page will have access to a Mediator
property.
I’ve put the code on Github so check that out to see how this all comes together