In this article, we are going to provide a brief introduction to the CQRS pattern, and how the .NET library MediatR helps us build software with this architecture.

The source code for this article can be found on the CQRS and MediatR in ASP.NET Core.

This article is divided into the following sections:

Let’s start.

CQRS and the Mediator Pattern

The MediatR library was built to facilitate two primary software architecture patterns: CQRS and the Mediator pattern. Whilst similar, let’s spend a moment understanding the principles behind each pattern.

CQRS

CQRS stands for “Command Query Responsibility Segregation”. As the acronym suggests, it’s all about splitting the responsibility of commands (saves) and queries (reads) into different models.

If we think about the commonly used CRUD pattern (Create-Read-Update-Delete), usually we have the user interface interacting with a datastore responsible for all four operations. CQRS would instead have us split these operations into two models, one for the queries (aka “R”), and another for the commands (aka “CUD”).

The following image illustrates how this works:

CQRS Diagram

As we can see, the Application simply separates the query and command models. The CQRS pattern makes no formal requirements of how this separation occurs. It could be as simple as a separate class in the same application (as we’ll see shortly with MediatR), all the way up to separate physical applications on different servers. That decision would be based on factors such as scaling requirements and infrastructure, so we won’t go into that decision path today.

The key point being is that to create a CQRS system, we just need to split the reads from the writes.

What problem is this trying to solve?

Well, a common reason is often when we design a system, we start with the data storage. We perform database normalization, add primary and foreign keys to enforce referential integrity, add indexes, and generally ensure the “write system” is optimized. This is a common setup for a relational database such as SQL Server or MySQL. Other times, we think about the read use cases first, then try and add that into a database, worrying less about duplication or other relational DB concerns (often “document databases” are used for these patterns).

Neither approach is wrong. But the issue is that it’s a constant balancing act between reads and writes, and eventually one side will “win out”. All further development means both sides need to be analyzed and often one is compromised.

CQRS allows us to “break free” from these considerations, and give each system the equal design and consideration it deserves, without worrying about the impact of the other system. This has tremendous benefits on both performance and agility, especially if there are separate teams working on these systems.

Trade-offs

CQRS sounds great in principle, but as with anything in software, there are always trade-offs.

A few of these might include:

  • Managing separate systems (if the application layer is split)
  • Data becoming stale (if the database layer is split)
  • The complexity of managing multiple components

It’s ultimately up to our specific use cases. Good development practices would encourage us to “keep it simple” (KISS) and therefore only employ these patterns when a need arises Otherwise it’s simply premature optimization.

In the next section, let’s discuss a similar pattern called Mediator.

Mediator Pattern

The Mediator pattern is simply defining an object that encapsulates how objects interact with each other. Instead of having two or more objects take a direct dependency on each other, they instead interact with a “mediator”, who is in charge of sending those interactions to the other party:

Mediator Diagram

We can see in the image above, SomeService sends a message to the Mediator, and the Mediator then invokes multiple services to handle the message. There is no direct dependency between any of the blue components.

The reason the Mediator pattern is useful is the same reason patterns like Inversion of Control is useful. It enables “loose coupling”, as the dependency graph is minimized and therefore code is simpler and easier to test. In other words, the fewer considerations a component has, the easier it is to develop and evolve. 

We saw in the previous image how the services have no direct dependency, and the producer of the messages doesn’t know who or how many things are going to handle it. This is very similar to how a message broker works in the “publish/subscribe” pattern. If we wanted to add another handler we could, and the producer wouldn’t have to be modified.

Now that we’ve been over some theory, let’s talk about how MediatR makes all these things possible.

How MediatR facilitates CQRS and Mediator Patterns

You can think of MediatR as an “in-process” Mediator implementation, that helps us build CQRS systems. All communication between the user interface and the data store happens via MediatR.

The term “in process” is an important limitation here. Since it’s a .NET library that manages interactions within classes on the same process, it’s not an appropriate library to use if we wanted to separate the commands and queries across two systems. In those circumstances, a better approach would be to a message broker such as Kafka or Azure Service Bus.

However for the purposes of this article, we are going to stick with a simple single-process CQRS system, so MediatR fits the bill perfectly.

Setting up an ASP.NET Core API with MediatR

Project Setup

First off, let’s open Visual Studio and create a new ASP.NET Core Web Application, selecting API as the project type.

Dependencies

Let’s install a couple of packages via the Package Manager Console.

First, the MediatR package:
PM> install-package MediatR

Next, a package that wires up MediatR with the ASP.NET DI container:
PM> install-package MediatR.Extensions.Microsoft.DependencyInjection

Startup

Let’s open up Startup.cs and add a using statement:
using MediatR;

Next, let’s modify ConfigureServices:
services.AddMediatR(typeof(Startup));

Now MediatR is configured and ready to go.

Controller

Now that we have everything installed, let’s set up a new controller that will send messages to MediatR.

In the Controllers folder, let’s add an “API Controller with read/write actions”, keeping the class name the default ValuesController.cs.

We then end up with the following class:

[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    	// GET: api/<ValuesController>
	[HttpGet]
    	public IEnumerable<string> Get()
	{
		return new string[] { "value1", "value2" };
	}

	// GET api/<ValuesController>/5
	[HttpGet("{id}")]
	public string Get(int id)
	{
		return "value";
	}

	// POST api/<ValuesController>
	[HttpPost]
	public void Post([FromBody] string value)
	{
	}

	// PUT api/<ValuesController>/5
	[HttpPut("{id}")]
	public void Put(int id, [FromBody] string value)
	{
	}

	// DELETE api/<ValuesController>/5
	[HttpDelete("{id}")]
	public void Delete(int id)
	{
	}
}

Let’s then add a constructor that initializes a IMediatR instance:

private readonly IMediator _mediator;

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

The IMediatR interface allows us to send messages to MediatR, which then dispatches to the relevant handlers. Because we already installed the dependency injection package, the instance will be resolved automatically.

Data Store

Usually, we’d want to interact with a real database. But for the purposes of this article, let’s create a fake class that encapsulates this responsibility, and simply interact with some string values.

Let’s add a new FakeDataStore class, and modify it:

public class FakeDataStore
{
	private static List<string> _values;

	public FakeDataStore()
	{
		_values = new List<string>
		{
			"a",
			"b",
			"c"
		};
	}


	public void AddValue(string value)
	{
		_values.Add(value);
	}

	public IEnumerable<string> GetAllValues()
	{
		return _values;
	}
}

Here we’re simply interacting with a static list of strings, which is enough for our purposes.

Let’s update ConfigureServices in Startup.cs to configure our DataStore as a singleton:
services.AddSingleton<FakeDataStore>();

Now that our data store is implemented, let’s set up our app for CQRS.

Separating the Commands and Queries

This article is about CQRS after all, so let’s create two new folders for this purpose: “Commands” and “Queries”.

We’ll use these folders throughout the exercise to separate our models. As mentioned earlier, we are doing “in-process” CQRS, so this is a simple way to organize that.

In the next section, we are going to talk about the most common usage of MediatR, “Requests”.

Requests with MediatR

MediatR Requests are a very simple request-response style messages, where a single request is handled by a single handler in a synchronous manner (synchronous from the request point of view, not C# internal async/await). Good use cases here would be returning something from a database or updating a database.

There are two types of requests in MediatR. One that returns a value, and one that doesn’t. Often this corresponds to reads/queries (returning a value) and writes/commands (usually doesn’t return a value).

We’ll use the FakeDataStore we created earlier to implement some MediatR requests.

First, let’s create a request that returns all the values from our FakeDataStore.

GetValuesQuery

Since this is a query, let’s add a class called GetValuesQuery to the “Queries” folder, and implement it:

public class GetValuesQuery
{
	public class Query : IRequest<IEnumerable<string>> { }

	public class Handler : RequestHandler<Query, IEnumerable<string>>
	{
		private readonly FakeDataStore _db;

		public Handler(FakeDataStore db)
		{
			_db = db;
		}

		protected override IEnumerable<string> Handle(Query request)
		{
			return _db.GetAllValues();
		}
	}
}

There’s a bit going on here, so let’s break it down a bit:

  • First, we create an inner class called Query, which implements IRequest<IEnumerable<string>>. This simply means our request will return a list of strings.
  • Next, we create another inner class called Handler, which inherits from RequestHandler<Query, IEnumerable<string>>. This means this class will handle the Query, in this case, returning the list of strings.
  • In our Handler class, we implement a single method called Handle, which returns the values from our FakeDataStore.

It’s worth pointing out a few additional notes:

  • We place both the query and handler in the same class as the inner classes. But, we can certainly put these in separate classes or projects, if we prefer. However, putting it in the same classes keeps it simple and easy to discover, instead of navigating around the codebase.
  • We are keeping things synchronous here, again for simplicity, and due to how we are implementing an in-memory list of values. However, MediatR fully supports async/await by changing the method signature.

Calling and Testing our Request

To call our request, we just need to modify the Get() method in our ValuesController:

[HttpGet]
public async Task<IEnumerable<string>> Get()
{
	return await _mediator.Send(new Queries.GetValuesQuery.Query());
}

That’s how simple it is to send a request to MediatR. Notice we’re not taking a dependency on FakeDataStore, or have any idea on how the query is handled. This is one of the principles of the Mediator pattern, and we can see it implemented first hand here with MediatR.

Now let’s make sure everything is working as expected.

First, let’s hit CTRL+F5 to build and run our app, and we should see the “/weatherforecast” response in our browser.

Let’s then fire up Postman and create a new request:

MediatR - Postman request for GetValues

If we then hit “Send”, we see the response:

MediatR - Postman response for GetValues

Fantastic! This proves that MediatR is working correctly, as the values we see are the ones initialized by our FakeDataStore. We’ve just implemented our first “Query” in CQRS 🙂

In the next section, let’s talk about the other type of MediatR request, being the one that doesn’t return a value, ie a “Command”.

MediatR Commands

To create our first “Command”, let’s add a request that takes a single value and updates our FakeDataStore.

Inside our “Commands” folder, let’s add a class called AddValueCommand:

public class AddValueCommand
{
	public class Command : IRequest 
	{
		public string Value { get; set; }    
	}

	public class Handler : RequestHandler<Command>
	{
		private readonly FakeDataStore _db;

		public Handler(FakeDataStore db)
		{
			_db = db;
		}

		protected override void Handle(Command request)
		{
			_db.AddValue(request.Value);
		}
	}

Let’s explain things:

  • First, we create the Command class , implementing IRequest. The class has a single property for Value, which will represent the value we are adding. Notice this time the IRequest signature doesn’t have a type parameter. This is because we aren’t returning a value.
  • Next, we create the Handler class, which inherits from the RequestHandler<Command> class. This is a similar implementation to our previous GetValuesQuery. We’re simply saying this class will handle the Command request.
  • We then implement the Handle(Command request) method, adding our value to our FakeDataStore.

Calling and Testing our Request

Let’s now call our Command by modifying the Post method in ValuesController:

[HttpPost]
public async Task Post([FromBody] string value)
{
	await _mediator.Send(new Commands.AddValueCommand.Command
	{
		Value = value
	});
}

Again very similar to our Get method. But this time, we are setting a value on our Command, and we don’t return a value.

To test our command, let’s run our app again and add a new request to Postman:

MediatR - Postman request for AddValue

If we then hit “Send”, we get a 200 OK status response, but as we know there’s nothing returned from the Command. 

To test it actually worked, let’s run our GetValues request again:

MediatR - Postman request for GetValues

If we hit “Send”, we see our newly added value:

MediatR - Postman request for GetValues with new value

This proves that our Command is working correctly, by sending a message to MediatR with our new value and updating the state. We can then see our Query model (e.g GetValues) has been updated with our new value. 

While this may seem simple in theory, let’s try to think beyond the fact we’re simply updating an in-memory list of strings. What we are doing is communicating to a datastore via simple message constructs, without having any idea on how it’s being implemented. The commands and queries could be pointing to different data stores. They don’t know how their request will be handled, and they don’t care

At this point, let’s give ourselves a pat on the back, as we now have a fully-functioning ASP.NET Core API implementing the CQRS + Mediator patterns with MediatR. 🙂

Now let’s go even further, and in the next section explore another MediatR topic called “Notifications”.

MediatR Notifications

So for we’ve only seen a single request being handled by a single handler. However, what if we want to handle a single request by multiple handlers? 

That’s where notifications come in. In these situations, we usually have multiple independent operations that need to occur after some event. 

Examples might be:

  • Sending an email
  • Invalidating a cache

To demonstrate this, we will update the AddValueCommand flow we created previously to publish a notification and have it handled by two handlers.

Sending an email and invalidating a cache is out of the scope of this article, but to demonstrate the behavior of notifications, let’s instead simply update our fake values list to signify that something was handled.

Updating our FakeDataStore

Let’s open up our FakeDataStore and add a new method:

public void EventOccured(string value, string evt)
{
	var indexOfValue = _values.FindIndex(val => val.StartsWith(value));
	_values[indexOfValue] = $"{_values[indexOfValue]}, event: {evt}";
}

Very simply, we are looking for a particular value and updating it to signify an event that occurred on it.

Now that we’ve modified our store, in the next section let’s create the notification and handlers.

Creating the Notification and Handlers

Let’s define a notification message that encapsulates the event we would like to define.

First, let’s add a new folder called Notifications:

Adding a Notifications folder

Inside that folder, let’s add a class called ValueAddedNotification:

public class ValueAddedNotification : INotification 
{
	public string Value { get; set; }
}

public class EmailHandler : INotificationHandler<ValueAddedNotification>
{
	private readonly FakeDataStore _fakeDataStore;

	public EmailHandler(FakeDataStore fakeDataStore)
	{
		_fakeDataStore = fakeDataStore;
	}

	public Task Handle(ValueAddedNotification notification, CancellationToken cancellationToken)
	{
		_fakeDataStore.EventOccured(notification.Value, "Email sent");
		return Task.CompletedTask;
	}
}

public class CacheInvalidationHandler : INotificationHandler<ValueAddedNotification>
{
	private readonly FakeDataStore _fakeDataStore;

	public CacheInvalidationHandler(FakeDataStore fakeDataStore)
	{
		_fakeDataStore = fakeDataStore;
	}

	public Task Handle(ValueAddedNotification notification, CancellationToken cancellationToken)
	{
		_fakeDataStore.EventOccured(notification.Value, "Cache invalidated");
		return Task.CompletedTask;
	}
}

Let’s break things down:

  1. First, we create a class called ValueAddedNotification which implements INotification, with a single property called Value. This is the equivalent of IRequest we saw earlier, but for Notifications.
  2. Next, we create two handlers called EmailHandler and CacheInvalidationHandler that essentially do the same thing:
    1. Implement INotificationHandler<ValueAddedNotification>, signifying it can handle that event
    2. Call the EventOccured method on FakeDataStore, specifying the event that occurred

In real-world use cases, these would be implemented differently, likely taking external dependencies and doing something meaningful, but here we are just trying to demonstrate the behavior of Notifications.

Triggering the Notification

Next, we need to actually trigger our notification.

Let’s open up ValuesController and modify the Post method:

[HttpPost]
public async Task Post([FromBody] string value)
{
	await _mediator.Send(new Commands.AddValueCommand.Command
	{
		Value = value
	});

	await _mediator.Publish(new Notifications.ValueAddedNotification { Value = value });
}

In addition to sending MediatR the AddValueCommand request, we are now sending MediatR our ValueAddedNotification, this time using the Publish method.

If we wanted to, we could have done this directly in the handler for AddValueCommand, but let’s place it here for simplicity.

Testing our Notifications

To test out things are working, let’s run our application and again run the request to GetValues:

MediatR - Notifications - Getting values

As we expect, we have the three values we initialize in the FakeDataStore constructor.

Now, let’s run the other Postman request to add a new value:

MediatR - Notifications - Adding a value

We see the request succeeds. Now, let’s run the Get Values request again:

MediatR - Notifications - Viewing our events

As expected, when we added “someValue” both events fired off and edited the value. While being a contrived example, the key takeaway here is that we can fire an event and have it handled many times, without the producer knowing any different.

If we wanted to extend our workflow to do an additional task, we could simply add a new handler. We wouldn’t need to modify the notification itself or the publishing of said notification, which again touches on the earlier points of extensibility and separation of concerns.

In the final section, we’ll talk about something new in MediatR 3.0, called Behaviors.

Building MediatR Behaviors

Often when we build applications, we have a number of cross-cutting concerns. These include authorization, validating, and logging.

Instead of repeating this logic throughout our handlers, we can make use of Behaviors. Behaviors are very similar to ASP.NET Core middleware, in that they accept a request, perform some action, then (optionally) pass along the request.

Let’s look at implementing a MediatR behavior that does logging for us.

Creating our Behavior

First, let’s add another solution folder called “Behaviors”:

Adding the Behaviors folder

Next, let’s add a class inside the folder called LoggingBehavior:

public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
	private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;

	public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
	{
		_logger = logger;
	}

	public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
	{
		_logger.LogInformation($"Handling {typeof(TRequest).Name}");
		var response = await next();
		_logger.LogInformation($"Handled {typeof(TResponse).Name}");
		return response;
	}
}

Let’s explain our code:

  1. We first define a LoggingBehavior class, taking two type parameters TRequest and TResponse, and implementing the  IPipelineBehavior<TRequest, TResponse> interface. Simply put, this behavior can operate on any request.
  2. We then implement the Handle method, logging before and after we call the next() delegate.

This logging handler can then be applied to any request, and will log output before and after it is handled.

Registering our Behavior

To register our behavior, let’s add a line to ConfigureServices in Startup:

services.AddSingleton(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));

Notice that we are using the <,> notation to specify the behavior that can be used for any generic type parameters

Testing our Behavior

Let’s run our application, this time by using the F5 shortcut to run in Debug mode.

Then let’s open up Postman and run the Get Values again:

MediatR - Behaviors - Running the GetValuesQuery request

If we then open the “Output” window in Visual Studio and select “Show output from: Web Application – ASP.NET Core Web Server“, we see some interesting messages:

Behaviors - Viewing our Behavior

Great! This is the logging output before and after our GetValues query handler was invoked.

The important thing here is we didn’t need to modify our existing requests or handlers. We simply added a new behavior and wired it up.

Just as easily we could add authorization and validation to our entire application, in the same manner, making behaviors a great way to handle cross-cutting concerns in a simple and concise manner.

Conclusion

In this article, we’ve gone over how MediatR can be used to implement both the CQRS and Mediator patterns in ASP.NET Core. We’ve been through requests and notifications, and how to handle cross-cutting concerns with behaviors.

MediatR provides a great starting point from an application that needs to evolve from a simple monolith into a more mature application, by allowing us to separate read and write concerns, and minimizing the dependencies between code. 

This puts us in a great position to take a number of additional possible steps:

  • Use a different database for the reads (perhaps by extending our ValueAddedNotification to add a second handler to write to a new DB, then modifying GetValuesQuery to read from this DB)
  • Split out our reads/writes into separate apps (by modifying the ValueAddedNotification to publish to Kafka/Service Bus, then having a second app read from the message bus)

We now have our app in a great position to make the above steps if the need arises, without overcomplicating things in the short term.

We hope you enjoyed this article and have a good foundation on CQRS and MediatR.

Happy coding!