Implementing Mediator And CQRS in .Net 6 Web Api

In some of the previous posts, we visited the Mediator and CQRS patterns, understanding the benefits it brings to the table. In this blog post, we will look into implementing the Mediator and CQRS pattern in our .Net Core Web Api application. We would be using the Mediatr library by Jimmy Bogard for the implementation of the Mediator Pattern.

For this example, we would be using the following solution structure. We would be having the following projects.

  • CqrsAndMediatR.Api – Web Api Project, which would contain our controllers.
  • CqrsAndMediatR.Data – Contain our repositories and DbContext
  • CqrsAndMediatR.Domain – Contain the Entity definitions.
  • CqrsAndMediatR.Service – Contains the CQRS separation of Command and Query

Database

For the sake of this example, we would use In-Memory Database with Entity Framework Core. Let us begin by defining the Models in our CqrsAndMediatr.Domain project. We will keep it simple.

public interface IEntity
{
    long Id { get; set; }
}

public class Customer:IEntity
{
    public long Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set;}
    public IEnumerable<Address> AddressList { get; set; }
}

public class Address:IEntity
{
    public long Id { get; set; }
    public string HouseName { get; set; }
    public string Street { get; set; }
    public string District { get; set; }
    public string State { get; set; }
    public string Pincode { get; set; }
    public string Country { get; set; }
}


We have a Customer Model which would represent the Customer entity. Each Customer has multiple addresses associated with them, which is defined by the Address model. With the model in place, we will create the DbContext now, which would be defined in the CqrsAndMediatR.Data Project.

public class CustomerDbContext : DbContext
{
    public CustomerDbContext()
    {
    }
    public CustomerDbContext(DbContextOptions<CustomerDbContext> options) : base(options)
    {
        Database.EnsureCreated();
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<Customer>()
                    .HasData(
            new Customer { Id = 1, FirstName = "Jia", LastName = "Anu" },
            new Customer { Id = 2, FirstName = "Naina", LastName = "Anu" },
            new Customer { Id = 3, FirstName = "Sreena", LastName = "Anu" },
            new Customer { Id = 4, FirstName = "Anu", LastName = "Viswan" }
            );
    }

    public DbSet<Customer> Customer { get; set; }
}

As you can see in the code above, we have also seeded the database for some initial values. We would be using InMemory Database for the sake of example, which we would be configured as a service.

// In Program.cs
builder.Services.AddDbContext<CustomerDbContext>(o=>o.UseInMemoryDatabase(Guid.NewGuid().ToString()),ServiceLifetime.Singleton);

We have also ensured that the lifetime of the service has been set as Singleton. This is done so that the data persist across requests. If the lifetime was set as Scopped, the database would be initiated for each incoming request, and we would have trouble adding/viewing a new customer to our list.

Repository

The next step would be to implement the Repository Pattern, which would be, as you could guess, defined in the CqrsAndMediatR.Data project. We will create a generic repository that would take care of most of our needs. For the sake of this blog post, we will address a Query and a Command, but you could find the entire source code discussed in this example in my Github.

public interface IRepository<TEntity> where TEntity : class, new()
{
    IEnumerable<TEntity> GetAll();
    Task<TEntity> AddAsync(TEntity entity);
}

The generic Repository contract, as expected, exposes a method each for the Crud methods. It also has a GetAll Method. The concrete implementation of the GenericRepository is as follows.

public class GenericRepository<TEntity> : IRepository<TEntity> where TEntity : class,IEntity,new()
{
    protected readonly CustomerDbContext CustomerDbContext;

    public GenericRepository(CustomerDbContext customDbContext) => CustomerDbContext = customDbContext;

    public IEnumerable<TEntity> GetAll() => CustomerDbContext.Set<TEntity>();

    public async Task<TEntity> AddAsync(TEntity entity)
    {
        ArgumentNullException.ThrowIfNull(entity);

        await CustomerDbContext.AddAsync(entity);
        await CustomerDbContext.SaveChangesAsync();

        return entity;
    }

}

We will also define the ICustomerRepository and concrete CustomerRepository.

public interface ICustomerRepository : IRepository<Customer>
{
}

public class CustomerRepository : GenericRepository<Customer>, ICustomerRepository
{
    public CustomerRepository(CustomerDbContext customDbContext) : base(customDbContext)
    {
    }

}

With the repository in place, it is time to configure the same in the services.

builder.Services.AddTransient(typeof(IRepository<>), typeof(GenericRepository<>));
builder.Services.AddTransient<ICustomerRepository, CustomerRepository>();

Mediator And CQRS

Finally, it is time to implement the Mediator and CQRS Patterns. As discussed earlier, we would be using Mediatr library for implementing the Mediator Pattern. This, as mentioned earlier, would be done in the CqrsAndMediatr.Services project. We will have two separate folders for holding the Commands and Queries as shown in the image below

Let us now define the CreateCustomerCommand, which would be used to create a new customer.

public class CreateCustomerCommand : IRequest<Customer>
{
    public Customer Customer { get; set; }
}

The class implements the MediatR.IRequest<T> interface from the MediatR library. The library depends on the interface for the implementation of the Mediator pattern. The CreateCustomerCommand, as expected contain a single property Customer – which defines the new Customer instance which needs to be created.

Now that we have the command, we will define the Handler for the same.

public class CreateCustomerCommandHandler : IRequestHandler<CreateCustomerCommand, Customer>
{
    private readonly ICustomerRepository _customerRepository;
    public CreateCustomerCommandHandler(ICustomerRepository customerRepository) => _customerRepository = customerRepository;

    public async Task<Customer> Handle(CreateCustomerCommand request, CancellationToken cancellationToken)
    {
        return await _customerRepository.AddAsync(request.Customer);
    }
}

The class CreateCustomerCommandHandlerimplements the MediatR.IRequestHandler<TInput,TResponse> interface. It also accepts an instance of ICustomerRepository it its constructor. This would be used to execute the command to add the new customer.

Similarly, let us define a Query – the GetAllCustomer command and its corresponding handler. Remember to add them in the Query subfolder, in order to keep a physical separation.

public class GetAllCustomersQuery : IRequest<List<Customer>>
{
}

public class GetAllCustomersQueryHandler : IRequestHandler<GetAllCustomersQuery, List<Customer>>
{
    private readonly ICustomerRepository _customerRepository;
    public GetAllCustomersQueryHandler(ICustomerRepository customerRepository) => _customerRepository = customerRepository;
    public async Task<List<Customer>> Handle(GetAllCustomersQuery request, CancellationToken cancellationToken)
    {
        return _customerRepository.GetAll().ToList();
    }
}


Nothing fancy or different here from the CreateCustomerCommand, but it is quite beneficial to separate your commands and queries as CQRS pattern has already shown us.

We will configure the MediatR using our Services, as we had done earlier with previous services.

builder.Services.AddMediatR(typeof(GetAllCustomersQuery).Assembly);

Controllers

With our repositories and Command/Query along with corresponding handlers defined, it is time for us to complete the chain by defining the Controllers.

[ApiController]
[Route("Customer")]
public class CustomerController : Controller
{
    private readonly IMediator _mediator;
    private readonly IMapper _mapper;
    public CustomerController(IMediator mediator, IMapper mapper)
    {
        (_mediator, _mapper) = (mediator, mapper);
    }

    [HttpGet]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    [Route("GetAll")]
    public async Task<ActionResult<List<GetAllCustomersResponseDto>>> GetAll()
    {
        try
        {
            var response = await _mediator.Send(new GetAllCustomersQuery());
            return _mapper.Map<List<GetAllCustomersResponseDto>>(response);
        }
        catch (Exception ex)
        {
            return BadRequest(ex.Message);
        }
    }


    [HttpPost]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    [Route("CreateCustomer")]
    public async Task<ActionResult<CreateCustomerResponseDto>> Create(CreateCustomerRequestDto createCustomerModel)
    {
        try
        {
            var customer = _mapper.Map<Customer>(createCustomerModel);
            var response = await _mediator.Send(new CreateCustomerCommand
            {
                Customer = customer
            });

            return _mapper.Map<CreateCustomerResponseDto>(response);
        }
        catch (Exception ex)
        {
            return BadRequest(ex.Message);
        }
    }

}


We have two endpoints in our CustomerController, one each for Creating and retrieving Customers. The part we need to concentrate on in the above code is how we are using the Mediator instance.

Note that we are not communicating directly with the Repositories here. Instead, we have injected an instance of the Mediator in the constructor and used it to send the command to the handler. Depending on the Command Type, the corresponding handler would be used by the Mediatr library as a part of the Mediator Design pattern implementation. This ensures a loosely coupled implementation.

I have also used Automapper to map the incoming/outgoing Dtos to our internal models, but for the brevity of this post, have skipped the explanation of the same. If you are interested in checking out the entire code in my Github.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s