In this article, we’ll explore how to use it. A new Source Generator feature in ASP.NET 5 that automatically generates APIs for the system using the Mediatr library and CQRS mode.
The mediator model
Mediation patterns are a way to decouple modules in an application. In Web-based applications, it is often 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 shown in the figure below, the mediator acts as a middleman between the sender and the receiver of the command being sent. The sender does not know or care who is processing the command.
Using MediatR, we define a command that implements the iRequest <T> interface, where T represents the return type. In this example, we have a CreateUser class that will return a string to the caller:
public class CreateUser : IRequest<string> { public string id { get; set; } public string Name { get; set; }}
To send commands from the ASP.NET Core API to Mediatr, we can use the following 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); }}
On the receiving side, the implementation is also very simple: create a class that implements the IRequestHandler<T,U> interface. In this case, we have a handler that processes createUser and returns a string 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. The rule of handling is that there should always be only one handler for a particular command. If you want to send messages to many subscribers, you should use the built-in notification feature in MediaTr, but we won’t use it in this case.
CQRS
Command Query Responsibility Segregation(CQRS) is a very simple model. It requires that we should separate the implementation of commands (writes) from queries (reads) in the system.
With CQRS, we will start by doing this:
Instead do this:
CQRS is usually associated with Event Sourcing, but using CQRS does not require Event Sourcing, and using CQRS alone gives us many architectural advantages. Why is that? Because the requirements for reading and writing are often different, they require separate implementations.
Mediator + CQRS
Combining these two patterns in our sample application, we can create the following architecture:
The Command and Query
With Mediatr, there is no obvious separation between Command and Query, as both will implement the IRequest<T> interface. To better separate them, we will introduce the following interface:
public interface ICommand<T> : IRequest<T>
{}
public interface IQuery<T> : IRequest<T>
{}
Here’s an example of using 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’s still a class, but we generate a lot of boilerplate code for us, including equality, getHashCode, ToString…
Front-end Command and Query
To actually receive Command and Query from the outside, we need to create an ASP.NET Core API. These action methods will receive incoming HTTP commands and pass them to the MediatR for further processing. The controller might look something 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 then passes Command and Query to various handlers, which process 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 code generator
This is a new feature in the Roslyn compiler that allows us to hook into the compiler and generate additional code during compilation.
At a very high level, you can see it as follows:
- First, the compiler compiles your C# source code and generates a syntax tree.
- The source generator can then examine the 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 the source code generator can never modify existing code; it can only add new code to the application.
Source code generator +MediatR+CQRS
For each Command and Query we implemented, we had to write the corresponding ASP.NET Core Action methods.
This means that if we have 50 commands and queries in our system, we need to create 50 Action methods. Of course, this would be rather tedious, repetitive, and error-prone.
But wouldn’t it be cool if, based on just Command/Query, we could generate API code as part of the compilation? It’s like this:
Meaning, 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; }}
The source generator will then generate the following classes as part of the compilation:
/// <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); }}
Use OpenAPI to generate API documentation
Fortunately, SwashBuckle is included in the API template for ASP.NET Core 5. By default, it will see these classes and generate a nice OpenAPI (Swagger) document for us!
Look at my code
It’s made up like this:
- SourceGenerator
This project contains the actual source generator that will generate the API controller action method.
- SourceGenerator-MediatR-CQRS
- This is a sample application that uses a source code generator. Review the project files to see how the project references the source generator.
- Templates This folder contains the Templates for the Command and Query classes. The source code generator inserts the generated code into these templates.
- CommandAndQueries is based on the Commands and Queries defined in this folder, and the generator generates the corresponding ASP.NET endpoints.
Look at the generated code
How do 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:
objGeneratedFilesSourceGeneratorSourceGenerator.MySourceGenerator
In this folder you will find the following two files:
conclusion
By introducing the concept of a source code generator, we can remove a lot of boilerplate code that must be written and maintained. I’m not a compiler engineer, and my approach to source code generators may not be 100% optimal (or even 100% correct), but it still goes to show that anyone can create their own source code generators without too much hassle.
Welcome to pay attention to my public number, if you have a favorite foreign language technical articles, you can pass the public number message recommended to me.