Start playing with .NET 7, Minimal APIs and MediatR

Let's start playing with .NET 7, Minimal APIs and MediatR by creating a basic API to see how all these concepts are feeting together.

First step, we create an empty .NET 7 ASP.NET Core web project.

Now we add MediatR to the project using NuGet.

We create our own interface for the HTTP Request which inherit from the MediatR IRequest interface.


using MediatR;

namespace MinimalApiWithMediatRLab.Requests;

public interface IHttpRequest : IRequest<IResult>
{
}

We add an HTTP request object, let's say we want to add an endpoint to get a customer by his identifier.


namespace MinimalApiWithMediatRLab.Requests;

public class GetCustomerRequest : IHttpRequest
{
    public int Id { get; set; }
}

We create a handler to process this request and execute the behavior we want to.


using MediatR;
using MinimalApiWithMediatRLab.Requests;

namespace MinimalApiWithMediatRLab.Handlers;

internal class GetCustomerHandler : IRequestHandler<GetCustomerRequest, IResult>
{
    public async Task<IResult> Handle(GetCustomerRequest request, CancellationToken cancellationToken)
    {
        await Task.Delay(10); // To simulate some business logic

        return Results.Ok(new
        {
            Id = request.Id,
            FirstName = "Adrien",
            LastName = "Torris",
            Message = $"Hello customer #{request.Id}"
        });
    }
}

We create an extension class to configure our MediatR implementation:


using MinimalApiWithMediatRLab.Requests;
using MediatR;

internal static class MediatRExtensions
{
    internal static WebApplication MediateGet(this WebApplication app, string template) where TRequest : IHttpRequest
    {
        app.MapGet(template, async (IMediator mediator, [AsParameters] TRequest request) => await mediator.Send(request));
        return app;
    }
}

We configure our application to use MediatR:


builder.Services.AddMediatR(x => x.AsScoped(), typeof(Program));

Next we declare the endoint we want to add:


app.MediateGet("customer/{id}");

Now we can run our application and call the endpoint we created:

We have now an API that works but doesn't do much. We have only one GET method, let's implement another HTTP verb, like a POST method. To handle other verbs, you have just to map them in our extension class:


using MinimalApiWithMediatRLab.Requests;
using MediatR;

internal static class MediatRExtensions
{
    internal static WebApplication MediateGet<TRequest>(this WebApplication app, string template) where TRequest : IHttpRequest
    {
        app.MapGet(template, async (IMediator mediator, [AsParameters] TRequest request) => await mediator.Send(request));
        return app;
    }

    internal static WebApplication MediatePost<TRequest>(this WebApplication app, string template) where TRequest : IHttpRequest
    {
        app.MapPost(template, async (IMediator mediator, [AsParameters] TRequest request) => await mediator.Send(request));
        return app;
    }
}

Now we can add POST endpoint by doing exactly the same thing than for GET ones but by using the MediatePost method.

Also, to be more in a real-world scenario where there are often business logic involves, let's add a service and inject it in the handler. To continue with our customer example, let's a Customer service with a Get method:


public class Customer
{
    public int Id { get; init; }
    public string FirstName { get; init; }
    public string LastName { get; init; }

    public Customer(int id, string firstName, string lastName)
    {
        this.Id = id;
        this.FirstName = firstName;
        this.LastName = lastName;
    }
}

namespace MinimalApiWithMediatRLab.Services;

public class CustomerService : ICustomerService
{
    private static readonly Customer[] customers =
    {
        new Customer(1, "Adrien", "Torris"),
        new Customer(2, "Scott", "Parker"),
        new Customer(3, "Bruce", "Wayne"),
        new Customer(4, "Tin", "Tin")
    };

    public async Task<Customer> GetAsync(int id)
    {
        await Task.Delay(10);
        return customers.SingleOrDefault(x => x.Id == id);
    }
}

Once our service created, we must register it into the Dependency Injection to make it available for the consumers which want to use it:


builder.Services.AddSingleton();

Now that our service is ready for use, we inject the CustomerService in our HTTP request handler using the built-in dependency injection framework and use it in our Handle method:

using MediatR;
using MinimalApiWithMediatRLab.Requests;
using MinimalApiWithMediatRLab.Services;

namespace MinimalApiWithMediatRLab.Handlers;

internal class GetCustomerHandler : IRequestHandler<GetCustomerRequest, IResult>
{
    private readonly ICustomerService _customerService;

    public GetCustomerHandler(ICustomerService customerService)
    {
        _customerService = customerService;
    }

    public async Task Handle(GetCustomerRequest request, CancellationToken cancellationToken)
    {
        Customer customer = await _customerService.GetAsync(request.Id);

        return customer != null
            ? Results.Ok(customer)
            : Results.NotFound();
    }
}

The code sample of this minimal project is available on my GitHub.

September 3, 2022
  • .NET 7
  • C# 10
  • MediatR