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

Unleash Blazor's Potential

Blazor promises to make it much easier (and faster) to build modern, responsive web applications, using the tools you already know and understand.

Subscribe to my Practical ASP.NET Blazor newsletter and get instant access to the vault.

In there you'll find step-by-step tutorials, source code and videos to help you get up and running using Blazor's component model.

I respect your email privacy. Unsubscribe with one click.

    Next up

    Finally! Improved Blazor Server reconnection UX
    .NET 9 changes how your Blazor Server app behaves when server connection is lost
    .NET 9 improves JavaScript module importing for Blazor
    .NET 9 ensures your users always get the latest version of your JS modules
    How to use .NET 9 to ensure users always get the latest version of your stylesheets
    .NET 9 changes how static files are served, and it solves a long-standing problem