In this blog post we will explore how we can use the new source generator feature in .NET 5 to automatically generate an API for a system using the MediatR library and the CQRS pattern.
The mediator pattern
The mediator pattern is an established way to decouple modules within an application. In a web-based application, it is commonly used to decouple the front end from the business logic.
On the .NET platform, the MediatR library is one of the most popular implementations of this pattern. As the picture below shows, the mediator acts as a middleman between the sender and the receivers of commands sent. The sender does not know and doesn’t care who is handling the commands.
With MediatR we implement a command as a class that implements the IRequest<T> interface, where T represents the return type.
In this example we have a command CreateUser that will return a string back to the caller:
public class CreateUser : IRequest<string>
{
public string id { get; set; }
public string Name { get; set; }
}
To send the command from an ASP.NET Core API to the MediatR, we can use this code:
[Route("api/[controller]")]
[ApiController]
public class CommandController : ControllerBase
{
private readonly IMediator _mediator;
public CommandController(IMediator mediator)
{
_mediator = mediator;
}
[HttpPost]
public async Task<string> Get(CreateUser command)
{
return await _mediator.Send(command);
}
}
</string>
On the receiving side, the implementation is also quite straightforward: create a class that implements the IRequestHandler<T,U> interface. In this case we have a handler that handles the CreateUser command and returns a string back to the caller:
public class CommandHandlers : IRequestHandler<createuser, string="">
{
public Task<string> Handle(CreateUser request,
CancellationToken cancellationToken)
{
return Task.FromResult("User Created");
}
}
Each handler class can handle multiple commands. A rule is that you should always have only one handler for a specific command. If you want to send a message to many subscribers, then you should use the built-in notification feature in MediatR, but we will not use that feature in this example.
CQRS
Command Query Responsibility Segregation is, at its core, a very simple pattern. It says that we should separate the implementation of commands (writes) from the queries (reads) in our system.
With CQRS we go from this:
To this:
CQRS is often associated with event sourcing, but using event sourcing is not a requirement to use CQRS and just using CQRS on its own will give us plenty of architectural advantages. Why is this? Because the needs of the write and query sides are often so different, they deserve separate implementations.
Visit our site CQRS.nu to read more about how to apply CQRS with Event Sourcing. On that site you will also learn how to write well-tested systems using command and events instead of the classic CRUD pattern.
Combining Mediator + CQRS
Combining these two patterns in the sample application allows us to create an architecture that looks like this:
Command and queries
With MediatR we have no clear separation between commands and queries, because both will implement the IRequest<T> interface. To better separate them, we will introduce the following helper interfaces:
public interface ICommand<T> : IRequest<T>
{
}
public interface IQuery<T> : IRequest<T>
{
}
Here are a sample command and query that use these two interfaces:
public record CreateOrder : ICommand<string>
{
public int Id { get; set; }
public int CustomerId { get; set; }
}
public record GetOrder : IQuery<order>
{
public int OrderId { get; set; }
}
To further improve our code, we can use the new C#9 record feature. Internally, it is still a class, but we get quite a lot of boilerplate code generated for us, including equality, GetHashCode, ToString...
Commands and queries in the front end
To actually receive commands and queries from the outside, we need to create an ASP.NET Core API. These action methods will take the incoming HTTP commands and pass them along to MediatR for further handling. A controller may look like this:
[Route("api/[controller]")]
[ApiController]
public class CommandController : ControllerBase
{
private readonly IMediator _mediator;
public CommandController(IMediator mediator)
{
_mediator = mediator;
}
[HttpPost]
public async Task<string> CreateOrder([FromBody] CreateOrder command)
{
return await _mediator.Send(command);
}
[HttpPost]
public async Task<order> GetOrder([FromBody] GetOrder command)
{
return await _mediator.Send(command);
}
}
MediatR will then pass the commands and queries along to the various handlers that will handle them and return a response. Applying the CQRS pattern, we will use separate classes for the command and query handlers.
public class CommandHandlers : IRequestHandler<CreateOrder, string="">
{
public Task<string> Handle (CreateOrder request, CancellationToken ct)
{
return Task.FromResult ("Order created");
}
}
public class QueryHandlers : IRequestHandler<GetOrder, Order="">
{
public Task<Order> Handle (GetOrder request, CancellationToken ct)
{
return Task.FromResult (new Order()
{ Id = 2201,
CustomerId = 1234,
OrderTotal = 9.95m,
OrderLines = new List<OrderLine>() });
}
}
Source generators
This is a new feature in the Roslyn compiler that allows us to hook into the compiler and generate additional code as part of the compilation.
At a very high level, you can see it as follows:
First the compiler compiles your C# source code and produces a syntax tree.
The source generator can then examine this syntax tree and generate new C# source code.
This new source code is then compiled and added to the final output.
It is important to know that a source generator can never modify existing code, it can only add new code to your application. There are plenty of good videos introducing source generators on YouTube, so we won’t repeat that here.
Combining source generators, MediatR and CQRS
For each command and query that we implement, we must write a corresponding ASP.NET Core action method.
This means that if we have 50 commands and queries in our system, we need to create 50 action methods. This will of course be pretty tedious, repetitive and error prone.
But would it not be cool if, based on the command/query alone, we could generate the API code as part of the compilation? Like this:
Meaning that if I create this command class:
/// <summary>
/// Create a new order
/// </summary>
/// <remarks>
/// Send this command to create a new order in the system for a given customer
/// </remarks>
public record CreateOrder : ICommand<string>
{
/// <summary>
/// OrderId
/// </summary>
/// <remarks>This is the customers internal ID of the order.</remarks>
/// <example>123</example>
[Required]
public int Id { get; set; }
/// <summary>
/// CustomerID
/// </summary>
/// <example>1234</example>
[Required]
public int CustomerId { get; set; }
}
Then the source generator will generate the following class as part of the compilation for us:
/// <summary>
/// This is the controller for all the commands in the system
/// </summary>
[Route("api/[controller]/[Action]")]
[ApiController]
public class CommandController : ControllerBase
{
private readonly IMediator _mediator;
public CommandController(IMediator mediator)
{
_mediator = mediator;
}
/// <summary>
/// Create a new order
/// </summary>
/// <remarks>
/// Send this command to create a new order in the system for a given customer
/// </remarks>
/// <param name="command">An instance of the CreateOrder
/// <returns>The status of the operation</returns>
/// <response code="201">Returns the newly created item</response>
/// <response code="400">If the item is null</response>
[HttpPost]
[Produces("application/json")]
[ProducesResponseType(typeof(string), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<string> CreateOrder([FromBody]CreateOrder command)
{
return await _mediator.Send(command);
}
}
}
API documentation using OpenAPI
The nice thing is that Swashbuckle, which is included in the ASP.NET Core 5 API template by default, will see these classes and generate nice OpenAPI (Swagger) documentation for us!
Show me the code!
The source code can be found on GitHub and it consists of the following:
SourceGenerator This project contains the actual source generator that will generate the API controller action methods.
SourceGenerator-MediatR-CQRS
This is a sample application that uses the source generator. Look at the project file to see how this project refers to the source generator.
Templates
This folder contains the template for the command and query classes. The source generator will insert its generated code into these templates.The template for the action methods is hard-coded in the source generator.
CommandAndQueries
Based on the commands and queries defined in this folder, the generator will generate the corresponding ASP.NET endpoints.
Viewing the generated code
How can we see the generated source code? By adding these lines to the API project file, we can tell the compiler to write the generated source code to a folder of our choice:
<EmitCompilerGeneratedFiles>
True
</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>
$(BaseIntermediateOutputPath)\GeneratedFiles
</CompilerGeneratedFilesOutputPath>
This means that you can find the generated code in this directory:
\obj\GeneratedFiles\SourceGenerator\SourceGenerator.MySourceGenerator
In this folder you will find the following two files:
Conclusion
This proof of concept shows that we can remove a lot of boilerplate code that we would have to write and maintain by introducing the concept of source generators. I am not a compiler engineer and my approach in the source generator is probably not 100% optimal (or even 100% correct), but still it shows that anyone can create their own source generator without much trouble. The hardest part, that I am sure they are working on, is the debugging experience, which could be improved.
About the author
When Tore Nestenius is not busy answering questions on StackOverflow he works as a trainer, teaching topics like .NET, C#, Web Security, Software Architecture, DDD/CQRS/Event Sourcing, IdentityServer and OpenID Connect, and more. Besides teaching he also offers consulting services and coaching for developer organizations all over the world.
Some of Tore’s recent training classes:
Securing ASP.NET using OpenID Connect and IdentityServer
IdentityServer in Production
Applied Domain-Driven Design / CQRS in .NET
Some of Tore’s recent blog posts:
Storing the ASP.NET Core Data Protection Key Ring in Azure Key Vault
Exploring the non-nullable type warnings in C# 8
Resources
By Tore Nestenius
Comments