CQRS & MediatR in a .NET 8 Web API 🚀


Hey Reader,

Today, we're going to dive into CQRS (Command and Query Responsibility Segregation) and the Mediator pattern using a .NET Web API. We'll keep things simple by not using clean architecture for this example. If you're interested in CQRS and the Mediator pattern, this tutorial is for you. You can either watch the video tutorial on YouTube or keep scrolling if you prefer reading. Let's get started!

YouTube Video Tutorial

Check out the video below for the full tutorial.

video preview

What is CQRS?

CQRS stands for Command and Query Responsibility Segregation. It involves separating the operations that modify data (commands) from those that read data (queries). Here’s a brief overview:

  1. Command: An operation that changes data (e.g., create, update, delete).
  2. Query: An operation that reads data.

What is the Mediator Pattern?

The Mediator pattern is used to reduce the dependencies between objects. Instead of objects communicating directly, they communicate through a mediator. In a .NET application, we can use the Mediator library to implement this pattern.

Setting Up the Project

First things first, let's create a new Web API project in Visual Studio with .NET 8 and install the necessary NuGet packages, which would be MediatR and Microsoft.EntityFrameworkCore.InMemory if you want to use an in-memory database for this example project.

Implementing CQRS and the Mediator Pattern

Set Up the Data Model and In-Memory Database

We're going to build a simple video game API where we can create players and query single players by their Id. So we need a Player class first.

Configure the in-memory database and MediatR

Next comes the database context. You can configure it in the following way:

Don't forget to register the context in the Program.cs. While we're already at it, we can also add and configure the MediatR package there as well.

Now it's time to create commands and queries.

Implement Commands and Handlers

A command represents an action that changes the state of the application. It is used to create, update, or delete data. Create a Features folder to organize our commands and queries.

For this command, we're using the IRequest<int> interface. It is part of MediatR and indicates that this command will return an integer upon completion. In this case, it will return the Id of the newly created player. Name and Level are the properties needed to create a player. They are passed to the command when it is instantiated.

Corresponding to that command comes the command handler:

This handler implements the IRequestHandler<CreatePlayerCommand, int> interface, indicating it handles CreatePlayerCommand requests and returns an int (the Id of the newly created player). We're using the VideoGameAppDbContext instance to interact with the database and in the Handle method, a new Player object is created with the name and level provided in the CreatePlayerCommand request, added to the context, and saved to the database. Finally, it returns the Id of the created player.

Next comes the query.

Implement Queries and Handlers

First, we add the GetPlayerByIdQuery.

It takes an int Id parameter and implements IRequest<Player?>, indicating that it will return a Player object or null.

Next, we need a handler to process this query:

It implements IRequestHandler<GetPlayerByIdQuery, Player?> and so indicates that it handles GetPlayerByIdQuery requests and, again, returns a Player object or null. In the Handle method, we use the FindAsync method of the context to retrieve the player by their Id and return the player.

Finally, we need to use the command and the query in our controller:

Implement the Controller

The controller's constructor injects an ISender parameter from MediatR, which is used in the CreatePlayer POST method to send a CreatePlayerCommand and return the new player's Id, and in the GetPlayerById GET method to send a GetPlayerByIdQuery, returning the player or a 404 status if not found.

Summary

Commands are used to modify the application's state. They encapsulate the data needed for the operation and are handled by command handlers which contain the logic to perform the action.

Queries are used to retrieve data without modifying the state. They encapsulate the criteria for the data retrieval and are handled by query handlers which contain the logic to fetch the data.

By using the CQRS and Mediator patterns, we achieve a clear separation of concerns, making our codebase easier to maintain and extend. Each handler focuses on a specific responsibility, and the controller remains clean, only coordinating the sending of commands and queries to their respective handlers.


If you have any questions or need more details, just hit reply to this email. I'm here to provide more tutorials and answer any queries you might have.

Thank you so much for your support. You help keep this channel growing. Stay safe!

Take care & happy coding,

Patrick

P.S. If you're ready to level up your .NET and Blazor skills, check out the .NET Web Academy. You'll find in-depth courses, all my YouTube source codes, and a wonderful community of like-minded developers. 🚀


Patrick God

Become a .NET & Blazor expert with weekly tutorials featuring best practices and the latest improvements, right in your inbox.

Read more from Patrick God
video preview

Hey Reader, You sit down to build an API, add some authentication, and think, “JWT should do the trick.” But then you wonder: What happens if my JWT gets stolen? How do I keep users logged in without compromising security? Sound familiar? In my latest video, I show you how to implement secure JWT authentication with short-lived tokens and refresh tokens in .NET. This setup minimizes risk while keeping the user experience seamless. No fluff - just practical, real-world guidance for building...

Hey Reader, Tired of factories and messy if-else logic in your projects? Since .NET 8, Keyed Services make it easy to handle multiple implementations. No more clutter - just clean, scalable code. In my new video, you’ll see how to dynamically pick services using a key and write code that’s easy to extend. It’s a fresh, modern approach to dependency injection that’ll have you rethinking how you design your APIs. 👉 Watch the video here Wishing you a fantastic start to the new year filled with...

video preview

Hey Reader, You sit down to write an API, and before you know it, you’re knee-deep in controllers, attributes, and boilerplate. Sound familiar? What if you could skip all that and still build a fully functional API - fast? That’s where Minimal APIs in .NET come in. They’re clean and lightweight and make building APIs feel like a breeze. In my latest video, I show you how to set up endpoints step by step and structure everything neatly so your code stays easy to read and scale. No fluff - just...