Implementing the Mediator Pattern in .NET Core with MediatR

Introduction

The Mediator pattern is a behavioral design pattern that promotes loose coupling by restricting direct communication between objects and forcing them to communicate through a mediator. In .NET Core, the MediatR library is a popular implementation of the Mediator pattern, making it easier to manage requests and commands in your applications. In this article, we will explore how to implement the Mediator pattern in .NET Core using MediatR.

What is the Mediator Pattern?

The Mediator pattern centralizes communication between objects, preventing them from referring to each other explicitly. This leads to a more maintainable and decoupled system. It is particularly useful in scenarios involving complex interactions between multiple objects.

Setting Up the Environment

To get started, we need to set up a .NET Core project and install the necessary NuGet packages.

Creating a .NET Core Project

Create a new .NET Core Web API project.

dotnet new webapi -n MediatorPatternDemo
cd MediatorPatternDemo

Installing MediatR and Related Packages

Install the MediatR library and its dependency, MediatR.Extensions.Microsoft.DependencyInjection.

dotnet add package MediatR
dotnet add package MediatR.Extensions.Microsoft.DependencyInjection

Implementing the Mediator Pattern

Let's implement a simple example where we use the Mediator pattern to handle a request to get a list of products.

Step 1. Define the Request and Response Models

First, define the request and response models. Create a new folder named `Models` and add the following classes.

// Models/Product.cs
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

// Models/GetProductsQuery.cs
public class GetProductsQuery : IRequest<List<Product>> { }

Step 2. Create the Handler

Create a handler that processes the `GetProductsQuery` and returns a list of products. Add a new folder named `Handlers` and create the following class.

// Handlers/GetProductsQueryHandler.cs
using MediatR;
using MediatorPatternDemo.Models;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

public class GetProductsQueryHandler : IRequestHandler<GetProductsQuery, List<Product>>
{
    public Task<List<Product>> Handle(GetProductsQuery request, CancellationToken cancellationToken)
    {
        // Simulate fetching data from a database
        var products = new List<Product>
        {
            new Product { Id = 1, Name = "Product 1", Price = 10.0m },
            new Product { Id = 2, Name = "Product 2", Price = 20.0m },
            new Product { Id = 3, Name = "Product 3", Price = 30.0m }
        };

        return Task.FromResult(products);
    }
}

Step 3. Register MediatR in the Startup Class

Register MediatR in the `Startup` class to enable dependency injection.

// Startup.cs
using MediatR;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Reflection;

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
        
        // Register MediatR
        services.AddMediatR(Assembly.GetExecutingAssembly());
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseRouting();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

Step 4. Create a Controller to Handle Requests

Create a controller that uses MediatR to handle incoming HTTP requests.

// Controllers/ProductsController.cs
using MediatR;
using MediatorPatternDemo.Models;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly IMediator _mediator;

    public ProductsController(IMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpGet]
    public async Task<ActionResult<List<Product>>> Get()
    {
        var products = await _mediator.Send(new GetProductsQuery());
        return Ok(products);
    }
}

Running the Application

Run the application using the following command.

dotnet run

Navigate to `http://localhost:5000/api/products` to see the list of products returned by the Mediator pattern.

Conclusion

The Mediator pattern, implemented with the MediatR library, helps in building decoupled and maintainable .NET Core applications. By centralizing communication between objects, it makes the system more modular and easier to manage. This article provided a simple example of how to use MediatR in a .NET Core application, showcasing the benefits of using the Mediator pattern.