Skip to main content
.NET

How to implement CQRS using MediatR in an ASP.NET Core Web API (.NET 6)

Christian Schou

In this article, I will briefly discuss what CQRS is, some advantages and disadvantages of using this design pattern and then we will practically implement CQRS with MediatR in a demo project.

What is CQRS?

CQRS is an abbreviation of what we would refer to as Command Query Responsibility Segregation. CQRS is a design pattern that allows an application’s developers to separate the command and query logic easily. Below is a brief explanation of what I mean by Query and Command logic.

What is MediatR?

By adding MediatR to our application we can reduce dependencies between our objects making the application less coupled = easier to maintain. MediatR allows for in-process messaging (not allowing direct communication between objects). Every request contains a handler. The handler is the one responsible for processing the request sent from the service calling MediatR. Each request sent to MediatR is assigned its very own handler. Below is a very simple diagram I have made to illustrate the concept of using MediatR.

MediatR Concept
MediatR Concept

Query and Command logic

Before we deep-dive into the actual implementation I would like to make sure we are both on the same side when talking about queries and commands in the application.

Queries

When we do a query against a database, we would like to retrieve a set of data we can work with. A standard select query looks like the following:

SELECT Name, Age FROM Users

Commands

When dealing with the CQRS pattern, standard commands are Insert, Update, and Delete commands. This kind of logic is placed at the business level in the application separating it from the other layers. Because they are business operations, they will not return any data in the application. Below are three examples of the above-mentioned commands in plain SQL.

Insert

The insert command will always add new data to our database. This can be achieved with the following command:

INSERT INTO Users (Id, Name, Age)
VALUES(3, 'Christian', 26)

Update

Update commands will take the Key Identifier and update the data in relation to that key. If we were to update the second user in our database, an update command would look like the following.

UPDATE Users
SET Name = 'Kenya'
WHERE Id = 1

Delete

If we would like to get rid of some data we would perform a delete command. Let’s say we would like to remove the user with id 4 from our database.

DELETE FROM Users
WHERE Id = 4

Pros and Cons of Using CQRS and MediatR

Pros of CQRS

  • If you run an application with a lot of database transactions, you want to make sure that these transactions would run as fast as possible – optimization first. When you implement CQRS in your application each of the queries and commands would be assigned their own handler. CQRS comes in handy as each statement is easier to optimize as we separate each query (get) and command (insert, update, delete).
  • CQRS makes the code easier to write as it takes out the complexity because we can separate the handlers resulting in less complex joins and business operations.
  • If you were to introduce new developers to the application it would be easier for them to get on board as the commands and queries have been separated into smaller bits.
  • If you got a huge application and the business model is complex you can assign the developers to separate parts of the business logic. This would mean that a set of developers would only be working on the queries, and another set of developers would only work on either INSERT, UPDATE or DELETE commands.

Cons of CQRS

If you run a simple small application it might be overkill, as I think it would raise the complexity of the application too much. Also when the business logic is non-complex, CQRS can be skipped (in my opinion).

Pros of MediatR

  • Your application will be easier to maintain and new developers will be easier to onboard as the onboarding would require less time and resources.
  • Ever had/maintained an application with a shit ton of dependency injection because you got some heavy coupling? MediatR makes it possible to only reference the MediatR class and will reduce the coupling between the controller and the services they invoke.
  • If you need it you can easier apply new or different configurations in your project without having to change a lot of code which can be very time-consuming.

Implement CQRS using MediatR in an ASP.NET Core Web API

Start by creating a new project based on the ASP.NET Core Web API template in Visual Studio. In this demo, we will focus on movies. At the end of this article, we will be able to create new movies in our database or select new users from the database.

#1 – Create a new ASP.NET Core Web API

Create new project
Create new project

Follow the instructions and name the application just the way you want. I have enabled HTTPS support in mine and no docker support.

#2 – Initial Cleanup of API

Because the API is made using a template, we have to do some initial cleanup. The first thing we have to do is delete the Weather Forecast class and controller in our project.

Delete Weather Forecast class and controller
Delete Weather Forecast class and controller

Let’s move on to creating the models and enums we are going to use. You can see the current changed files at this commit – Add project files.

Add project files. · Christian-Schou/CQRS-and-MediatR-demo@0a31b0f
A demo on how to implement CQRS in an ASP.NET Core Web API using MediatR - Add project files. · Christian-Schou/CQRS-and-MediatR-demo@0a31b0f

#3 – Create Core and Domain Models

For demo purposes, I will not create this application using clean architecture separating the parts of the application into separate projects. Everything will be inside the API project.

First, we have to create a few folders. Follow this structure:

└───MediatR_Demo
    ├───Controllers
    ├───Core
    │   └───Enums
    ├───Domain
    │   └───Entities
    │       └───Movie
    └───Properties

Inside the Enums folder, create a new file named MovieGenre.cs and create one inside Movie named Movie.cs.

visual studio solution explorer, cqrs project, mediatr implementation
Structure of application at step #3

Our movie model /entity will be very basic for the demo. I have added the following properties to the movie model.

using MediatR_Demo.Core.Enums;

namespace MediatR_Demo.Domain.Entities.Movie
{
    public class Movie
    {
        public long Id { get; set; }
        public string? Title { get; set; }
        public string? Description { get; set; }
        public MovieGenre Genre { get; set; }
        public int? Rating { get; set; }
    }
}

The enum holding our movie genres looks like this:

namespace MediatR_Demo.Core.Enums
{
    public enum MovieGenre
    {
        Action,
        Comedy,
        Drama,
        Fantasy,
        Horror,
        Mystery,
        Romance,
        Thriller,
        Western
    }
}

You can see the changes I made for this part of the tutorial here:

Create Core and Domain Models · Christian-Schou/CQRS-and-MediatR-demo@8aea18f
A demo on how to implement CQRS in an ASP.NET Core Web API using MediatR - Create Core and Domain Models · Christian-Schou/CQRS-and-MediatR-demo@8aea18f

#4 – Create and Migrate Database using EF Core

With our model in place, let’s move on to the part where we can store some data about the movies.

4.1 – Install required packages

I will use Entity Framework Core (Code First) to connect and create the database for the application. The database is hosted on my local development machine. Let’s install the required packages using the NuGet Package Manager.

SQL Server

https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.SqlServer/
### Package Manager
Install-Package Microsoft.EntityFrameworkCore.SqlServer

### .NET CLI
dotnet add package Microsoft.EntityFrameworkCore.SqlServer

### PackageReference
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="VERSION-HERE" />

Tools

https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.Tools
### Package Manager
Install-Package Microsoft.EntityFrameworkCore.Tools

### .NET CLI
dotnet add package Microsoft.EntityFrameworkCore.Tools

### PackageReference
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="VERSION-HERE" />

EF Core

https://www.nuget.org/packages/Microsoft.EntityFrameworkCore
### Package Manager
Install-Package Microsoft.EntityFrameworkCore

### .NET CLI
dotnet add package Microsoft.EntityFrameworkCore

### PackageReference
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="VERSION-HERE" />
Install SqlServer using NuGet Package Manager
Install SqlServer using NuGet Package Manager
Install Tools using NuGet Package Manager
Install Tools using NuGet Package Manager

4.2 – Setup Database Connection

With the packages installed and ready to use, we have to configure the database connection string and tell our application to add a new database context at startup.

Go to appsettings.json and create a new section for your connection string. Mine looks like this using a trusted connection:

{
  "ConnectionStrings": {
    "Standard": "Server=localhost;Database=mediatr-demo;Trusted_Connection=True;"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

Before we can register this in our startup class, we have to create a database context class for our application.

4.3 – Create Application Db Context

Create a new folder named Repository at the root level with a new subfolder named Context. Inside the Context folder, you have to add a new file named ApplicationDbContex.cs.

ApplicationDbContext.cs has to contain our Movie Model to address it in the database.

using MediatR_Demo.Domain.Entities.Movie;
using Microsoft.EntityFrameworkCore;

namespace MediatR_Demo.Repository.Context
{
    public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
        {
        }

        public DbSet<Movie> Movies { get; set; }
    }
}

By now you should have an application structure that looks like this:

cqrs .net, mediatr .net
Folder structure step 4

4.4 – Register Application Db Context with Connection String

Now we have to register our ApplicationDbContext class with the connection string we added to our appsettings.json file earlier.

Go to Program.cs and add the following:

using MediatR_Demo.Repository.Context;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddDbContext(
    options => options.UseSqlServer(builder.Configuration.GetConnectionString("Standard")));

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

4.5 – Migrate and Update Database

The only thing we have to do now is create a new migration and update the database.

Go to your Package Manager Console and run the following commands:

  • Add-Migration MovieAdded
  • Update-Database

Let EF Core do its magic. Lean back and wait for a moment.

ef core migrate, entity framwork core migrate, migration, cqrs update
Update database to match the latest migration

If we open up the database and expand the tables section, we should now be able to see a table with our migration history and the table holding our movies. Awesome!

mediatr database, mediatr
MediatR demo database tables

You can see the changes to the code I made in the section of the article right here:

Create and migrate database using EF Core · Christian-Schou/CQRS-and-MediatR-demo@55a6530
A demo on how to implement CQRS in an ASP.NET Core Web API using MediatR - Create and migrate database using EF Core · Christian-Schou/CQRS-and-MediatR-demo@55a6530

#5 – Install and Configure MediatR

So far we have created the required model to hold our movies, an enum with various genres for movies, and the database context for storing our movies. Let’s move on and populate some movies in our database.

To add new movies to the database, we have to create a command because it will be an INSERT command. Later I will show you how to query the database for retrieving the movies back.

To make it easier for ourselves when implementing the CQRS design pattern, we will be using MediatR. Let’s install MediatR in our project. First, we install MediatR and then a package that will allow us to wire up MediatR with ASP.NET DI.

MediatR

### Package Manager
Install-Package MediatR

### .NET CLI
dotnet add package MediatR

### PackageReference
<PackageReference Include="MediatR" Version="VERSION-HERE" />

DI Package

### Package Manager
Install-Package MediatR.Extensions.Microsoft.DependencyInjection

### .NET CLI
dotnet add package MediatR.Extensions.Microsoft.DependencyInjection

### PackageReference
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="VERSION-HERE" />
install mediatr
Install MediatR in NuGet Package Manager

Go to Program.cs and add MediatR to the startup routine.

using MediatR;
using MediatR_Demo.Repository.Context;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddDbContext(
    options => options.UseSqlServer(builder.Configuration.GetConnectionString("Standard")));

builder.Services.AddMediatR(typeof(Program));

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

MediatR is now ready to work for us. Before we can launch the application, we have to make a small change to launchsettings.json. Update the launch URL to the following for the demo.

{
  "$schema": "https://json.schemastore.org/launchsettings.json",
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:14989",
      "sslPort": 44321
    }
  },
  "profiles": {
    "MediatR_Demo": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      "launchUrl": "swagger",
      "applicationUrl": "https://localhost:7001;http://localhost:7000",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "launchUrl": "swagger",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

You can see the changes I made to the code here:

Install and Configure MediatR · Christian-Schou/CQRS-and-MediatR-demo@15491b1
A demo on how to implement CQRS in an ASP.NET Core Web API using MediatR - Install and Configure MediatR · Christian-Schou/CQRS-and-MediatR-demo@15491b1

#6 – Create DTOs for Request and Response Abstraction

Our movie model takes a few strings and ints to describe a movie. To fill the movie table with movies, we have to fill out these data. To do this I will create data transfer objects (DTOs). Inside the Domain folder, I have created a new folder named DTOs.

As we will be both requesting and sending data, we will create two new folders (requests and responses) inside our DTO folder with subfolders for Movie. Inside each Movie folder you have to add a new file with the following names:

  • CreateMovieRequest.cs
  • CreateMovieDto.cs
Create DTOs for abstraction
Create DTOs for abstraction

6.1 – CreateMovieRequest.cs

using MediatR_Demo.Core.Enums;

namespace MediatR_Demo.Domain.DTOs.Requests.Movie
{
    public class CreateMovieRequest
    {
        public string? Title { get; set; }
        public string? Description { get; set; }
        public MovieGenre Genre { get; set; }
        public int? Rating { get; set; }
    }
}

6.2 – CreateMovieDto.cs

namespace MediatR_Demo.Domain.DTOs.Responses.Movie
{
    public class CreateMovieDto
    {
        public CreateMovieDto(long id)
        {
            Id = id;
        }

        public long Id { get; set; }
    }
}

In the next section, we will create the commands to insert new movies into our database. If you would like to see the changes I made to the code in this section, you can do it here:

Create DTOs for Request and Response Abstraction · Christian-Schou/CQRS-and-MediatR-demo@2a992c8
A demo on how to implement CQRS in an ASP.NET Core Web API using MediatR - Create DTOs for Request and Response Abstraction · Christian-Schou/CQRS-and-MediatR-demo@2a992c8

#7 – Create Movie Commands + Movie Controller

Let’s fire up the DTOs and implement the insert command, handler, and extension to make our CQRS pattern work.

7.1 – Create Commands for creating a new movie

Create a new set of folders with the following structure: ./Application/Movies/Commands/CreateMovie.

Inside the folder named CreateMovie, you have to create three new files with the following names:

  • CreateMovieCommand.cs
  • CreateMovieCommandExtensions.cs
  • CreateMovieCommandHandler.cs
Folder structure for command for creating a new movie
Folder structure for command for creating a new movie

In the sub-sections, I will go over each file and show you the code you have to add to the respective file.

7.2 – CreateMovieCommand

Our command file will get the title, description, genre, and rating. These values will be returned to our CreateMovieDto which holds the Id of our created movie. Below is the implementation:

using MediatR;
using MediatR_Demo.Core.Enums;
using MediatR_Demo.Domain.DTOs.Responses.Movie;

namespace MediatR_Demo.Application.Movies.Commands.CreateMovie
{
    public class CreateMovieCommand : IRequest<CreateMovieDto>
    {
        public CreateMovieCommand(
            string title,
            string description,
            MovieGenre genre,
            int rating)
        {
            Title = title;
            Description = description;
            Genre = genre;
            Rating = rating;
        }

        public string? Title { get; set; }
        public string Description { get; set; }
        public int? Rating { get; set; }
        public MovieGenre? Genre { get;}
    }
}

7.3 – CreateMovieCommandExtensions

Next up are the extensions. Currently, we will only implement the logic for creating a movie. I have named the method CreateMovie – it takes in the CreateMovieCommand and returns the Movie model.

using MediatR_Demo.Domain.Entities.Movie;

namespace MediatR_Demo.Application.Movies.Commands.CreateMovie
{
    public static class CreateUserCommandExtension
    {
        public static Movie CreateMovie(this CreateMovieCommand command)
        {
            var movie = new Movie
                (
                    command.Title,
                    command.Description,
                    command.Genre,
                    command.Rating
                );

            return movie;
        }
    }
}

You will now get an error on the new Movie object because Movie doesn’t have a constructor. To solve this go back to your Movie model and update it to the following:

using MediatR_Demo.Core.Enums;

namespace MediatR_Demo.Domain.Entities.Movie
{
    public class Movie
    {
        public Movie(string? title, string? description, MovieGenre? genre, int? rating)
        {
            Title = title;
            Description = description;
            Genre = genre;
            Rating = rating;
        }

        public long Id { get; set; }
        public string? Title { get; set; }
        public string? Description { get; set; }
        public MovieGenre? Genre { get; set; }
        public int? Rating { get; set; }
    }
}

7.4 – CreateMovieCommandHandler

Now for one of the most important things – the handler to carry out the operations inserting the data into our database. This method is named Handle and takes in the CreateUserCommand (our request) and a cancellation token. When done it should return the ID of the created user.

using MediatR;
using MediatR_Demo.Domain.DTOs.Responses.Movie;
using MediatR_Demo.Repository.Context;

namespace MediatR_Demo.Application.Movies.Commands.CreateMovie
{
    public class CreateUserCommandHandler : IRequestHandler<CreateMovieCommand, CreateMovieDto>
    {
        private readonly ApplicationDbContext _dbContext;

        public CreateUserCommandHandler(ApplicationDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        public async Task<CreateMovieDto> Handle(CreateMovieCommand request, CancellationToken cancellationToken)
        {
            var movie = request.CreateMovie();
            await _dbContext.Movies.AddAsync(movie);
            await _dbContext.SaveChangesAsync();

            return new CreateMovieDto(movie.Id);
        }
    }
}

We have not inherited the Handle method from the IRequestHandler interface and added our logic for the logic to be executed once a new handler is invoked. Let’s move on to the controller part, where we will send a request using IMediator.

7.5 – Add CreateMovie endpoint to Movie Controller

The first thing we have to do is create a new controller named MovieController.cs inside the Controllers folder.

The controller including the dependency injection and POST endpoint is implemented the following way:

using MediatR;
using MediatR_Demo.Application.Movies.Commands.CreateMovie;
using MediatR_Demo.Domain.DTOs.Requests.Movie;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace MediatR_Demo.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class MovieController : ControllerBase
    {
        private readonly IMediator _mediator;

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

        [HttpPost]
        public async Task<IActionResult> CreateMovie([FromBody] CreateMovieRequest request)
        {
            var movie = await _mediator.Send(new CreateMovieCommand(
                request.Title,
                request.Description,
                request.Genre,
                request.Rating));

            return Ok(movie);
        }
    }
}

I forgot to add a “?” to genre and rating” type in the CreateMovieRequest and CreateMovieCommand files. Please do that, or else you will end up with an error.

You can see all changes made to the code in this commit:

Create Movie Commands + Movie Controller · Christian-Schou/CQRS-and-MediatR-demo@3113d89
A demo on how to implement CQRS in an ASP.NET Core Web API using MediatR - Create Movie Commands + Movie Controller · Christian-Schou/CQRS-and-MediatR-demo@3113d89

#8 – Create Movie Queries

Okay, now that we can store movies in the database it would be nice to retrieve them back the other way as well. In the code below we will retrieve a list of movies and a single movie based on the movie id.

8.1 – Create DTO for retrieving movies

First, we have to create a new DTO in our Responses folder named GetMovieDto.cs.

GetMovieDto.cs in solution explorer

The DTO is used to display movie data. It will reflect our Movie model:

using MediatR_Demo.Core.Enums;

namespace MediatR_Demo.Domain.DTOs.Responses.Movie
{
    public class GetMovieDto
    {
        public long Id { get; set; }
        public string? Title { get; set; }
        public string? Description { get; set; }
        public MovieGenre? Genre { get; set; }
        public int? Rating { get; set; }
    }
}

8.2 – Create Query Requests

We will introduce the option the get both all moves and a single movie using the CQRS pattern with MediatR. Now we have to create a new folder in ./Application/Movies/Queries with two subfolders named GetMovies and GetMovie. Inside these folders, you have to create the following files:

Get Movie(s) query files in the Application, cqrs, mediatr
Get Movie(s) query files in the Application

8.3 – Get Movies

First, we will create the query for retrieving a list of GetMovieDto.

using MediatR;
using MediatR_Demo.Domain.DTOs.Responses.Movie;

namespace MediatR_Demo.Application.Movies.Queries.GetMovies
{
    public class GetMoviesQuery : IRequest<IList<GetMovieDto>>
    {
    }
}

Then we will use our extension class to map the Movie DTO to a user object.

using MediatR_Demo.Domain.DTOs.Responses.Movie;
using MediatR_Demo.Domain.Entities.Movie;

namespace MediatR_Demo.Application.Movies.Queries.GetMovies
{
    public static class GetMoviesQueryExtensions
    {
        public static GetMovieDto MapTo(this Movie movie)
        {
            return new GetMovieDto
            {
                Id = movie.Id,
                Title = movie.Title,
                Description = movie.Description,
                Genre = movie.Genre,
                Rating = movie.Rating
            };
        }
    }
}

Finally, we have to create the handler to make the operation against the database to get all of our movies into a list that we can return.

using MediatR;
using MediatR_Demo.Domain.DTOs.Responses.Movie;
using MediatR_Demo.Repository.Context;
using Microsoft.EntityFrameworkCore;

namespace MediatR_Demo.Application.Movies.Queries.GetMovies
{
    public class GetMoviesQueryHandler : IRequestHandler<GetMoviesQuery, IList<GetMovieDto>>
    {
        private readonly ApplicationDbContext _dbContext;

        public GetMoviesQueryHandler(ApplicationDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        public async Task<IList<GetMovieDto>> Handle(GetMoviesQuery request, CancellationToken cancellationToken)
        {
            var movies = await _dbContext.Movies.ToListAsync();
            var movieList = new List<GetMovieDto>();
            foreach (var movieItem in movies)
            {
                var movie = movieItem.MapTo();
                movieList.Add(movie);
            }

            return movieList;
        }
    }
}

The handler makes an async query against the database and stores all movie items in a list. For each movie item in the list, we will map the movie item to a movie dto and add that movie dto to the list and then return it.

In the controller, we will add a new endpoint for requesting all movies in the database.

using MediatR;
using MediatR_Demo.Application.Movies.Commands.CreateMovie;
using MediatR_Demo.Application.Movies.Queries.GetMovies;
using MediatR_Demo.Domain.DTOs.Requests.Movie;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace MediatR_Demo.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class MovieController : ControllerBase
    {
        private readonly IMediator _mediator;

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

        [HttpGet]
        public async Task<IActionResult> GetMovies()
        {
            var movies = await _mediator.Send(new GetMoviesQuery());
            return Ok(movies);
        }

        [HttpPost]
        public async Task<IActionResult> CreateMovie([FromBody] CreateMovieRequest request)
        {
            var movie = await _mediator.Send(new CreateMovieCommand(
                request.Title,
                request.Description,
                request.Genre,
                request.Rating));

            return Ok(movie);
        }
    }
}

8.4 – Get Movie by ID

This one is a little different as we are not asking for a list, but a specific movie. In a normal case, I would include more data, when a single movie was requested. In this example we don’t have that much data, so we will return all available data.

We have already created the files so let’s go ahead and add some logic to them.

Inside GetMovieQuery.cs you have to add the following code. This query is asking for the id of the movie you would like to retrieve from the handler.

using MediatR;
using MediatR_Demo.Domain.DTOs.Responses.Movie;

namespace MediatR_Demo.Application.Movies.Queries.GetMovie
{
    public class GetMovieQuery : IRequest<GetMovieDto>
    {
        public GetMovieQuery(long? id)
        {
            Id = id;
        }

        public long? Id { get; set; }
    }
}

Inside our extension, we are doing the same mapping as we did before in the list query.

using MediatR_Demo.Domain.DTOs.Responses.Movie;
using MediatR_Demo.Domain.Entities.Movie;

namespace MediatR_Demo.Application.Movies.Queries.GetMovie
{
    public static class GetMovieQueryExtensions
    {
        public static GetMovieDto MapTo(this Movie movie)
        {
            return new GetMovieDto
            {
                Id = movie.Id,
                Title = movie.Title,
                Description = movie.Description,
                Genre = movie.Genre,
                Rating = movie.Rating
            };
        }
    }
}

You can update it to include more or fewer details, depending on what your project is about.

Inside our handler, we will make a simple query to retrieve the movie using an ID. You can read more about querying data here at Microsoft.

Querying Data - EF Core
Overview of information on querying in Entity Framework Core
using MediatR;
using MediatR_Demo.Domain.DTOs.Responses.Movie;
using MediatR_Demo.Repository.Context;
using Microsoft.EntityFrameworkCore;

namespace MediatR_Demo.Application.Movies.Queries.GetMovie
{
    public class GetMovieQueryHandler : IRequestHandler<GetMovieQuery, GetMovieDto>
    {
        private readonly ApplicationDbContext _dbContext;

        public GetMovieQueryHandler(ApplicationDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        public async Task<GetMovieDto> Handle(GetMovieQuery request, CancellationToken cancellationToken)
        {
            var movie = await _dbContext.Movies.Where(x => x.Id == request.Id).FirstOrDefaultAsync();

            if (movie != null)
            {
                var movieItem = movie.MapTo();
                return movieItem;
            }
            return null;
        }
    }
}

In this case, we have implemented a handler that will return null if no movie were found with the specific id sent from the requesting service.

As you can see we are using EF Core to get a movie where the id is equal to the id from the GetMovieQuery request-id.

Inside the controller, we add a new endpoint with specific routing. Else swagger will complain about equal routes.

using MediatR;
using MediatR_Demo.Application.Movies.Commands.CreateMovie;
using MediatR_Demo.Application.Movies.Queries.GetMovie;
using MediatR_Demo.Application.Movies.Queries.GetMovies;
using MediatR_Demo.Domain.DTOs.Requests.Movie;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace MediatR_Demo.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class MovieController : ControllerBase
    {
        private readonly IMediator _mediator;

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

        [HttpGet]
        public async Task<IActionResult> GetMovies()
        {
            var movies = await _mediator.Send(new GetMoviesQuery());

            if (movies != null)
            {
                return Ok(movies);
            }

            return NotFound("No movies in database. Please add a movie first.");
        }

        [HttpGet("/getmovies/{id}")]
        public async Task<IActionResult> GetMovie(long id)
        {
            var movie = await _mediator.Send(new GetMovieQuery(id));

            if (movie != null)
            {
                return Ok(movie);
            }

            return NotFound($"No movie in database with ID: {id}.");

        }

        [HttpPost]
        public async Task<IActionResult> CreateMovie([FromBody] CreateMovieRequest request)
        {
            var movie = await _mediator.Send(new CreateMovieCommand(
                request.Title,
                request.Description,
                request.Genre,
                request.Rating));

            return Ok(movie);
        }
    }
}

If no movies were found, we will return a status message else we return the movie dto object. I have added the same error check on the endpoint retrieving all movies.

That’s it – now we are ready to launch the application and add some movies. If you want to see the changes I have made to the code for this section, you can follow along here at the commit:

Create Movie Queries · Christian-Schou/CQRS-and-MediatR-demo@fe13b5e
A demo on how to implement CQRS in an ASP.NET Core Web API using MediatR - Create Movie Queries · Christian-Schou/CQRS-and-MediatR-demo@fe13b5e

#9 – Test the movie API

The moment we all have been waiting for. Now, let’s fire up the API and add our first movie. I have added Top Gun 2 with Tom Cruise. See the example below:

9.1 – Add Movies to the Database

swagger, post, cqrs, mediatr
Add movie through Swagger at API

Inside our database we got the following:

ssms, query, Query movies from SSMS (SQL Management Studio)
Query movies from SSMS (SQL Management Studio)

Let’s add a few more movies. Here are some sample movies you can use to POST against the API for having some data to look at.

Doctor Strange

{
  "title": "Doctor Strange in the Multiverse of Madness",
  "description": "Doctor Strange teams up with a mysterious teenage girl from his dreams who can travel across multiverses, to battle multiple threats, including other-universe versions of himself, which thre...",
  "genre": 3,
  "rating": 7
}

Lightyear

{
  "title": "Lightyear",
  "description": "While spending years attempting to return home, marooned Space Ranger Buzz Lightyear encounters an army of ruthless robots commanded by Zurg who are attempting to steal his fuel source.",
  "genre": 0,
  "rating": 5
}

Elvis

{
  "title": "Elvis",
  "description": "Elvis is Baz Luhrmann's biopic of Elvis Presley, from his childhood to becoming a rock and movie star in the 1950s while maintaining a complex relationship with his manager, Colonel Tom Park...",
  "genre": 2,
  "rating": 8
}

9.2 – Get all movies

Get all movies from the database
Get all movies from the database with cqrs and mediatr

The endpoint for retrieving all movies works perfectly. In a real-world application, I would implement pagination, but that is beyond scope of this article.

9.3 – Get a movie by ID

Let’s see if we can also fetch a single movie. Let’s try number three from the movie database.

Get a single movie from the database
Get a single movie from the database

That went very well as we can see. All methods work and data is being added and retrieved just as expected.

Summary

By using the CQRS design pattern we can reduce the number of dependencies between objects in our application. By adding MediatR we can introduce message processing avoiding direct communication.

I hope this article has helped you gain an understanding of why the CQRS design pattern is a good thing when working with complex business models or just applications that are not “light” in terms of complexity.

If you got any issues, questions, or suggestions, please let me know in the comments below and I will get back to you ASAP. Happy coding out there! You can find the full repository here: CQRS and MediatR demo.

GitHub - Christian-Schou/CQRS-and-MediatR-demo: A demo on how to implement CQRS in an ASP.NET Core Web API using MediatR
A demo on how to implement CQRS in an ASP.NET Core Web API using MediatR - GitHub - Christian-Schou/CQRS-and-MediatR-demo: A demo on how to implement CQRS in an ASP.NET Core Web API using MediatR