In this article, we will explore how to use it. A new Source Generator feature in NET 5 that automatically generates apis for the system using the MediatR library and CQRS mode.

The mediator pattern

The mediation pattern is 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, a mediator acts as a middleman between the sender and 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 interface, where T represents the return type. In this example, we have a CreateUser class that returns a string to the caller:

public class CreateUser : IRequest<string> { public string id { get; set; } public string Name { get; set; }}Copy the code

To send commands to MediatR from the ASP.NET Core API, 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); }}Copy the code

On the receiving side, the implementation is also simple: create a class that implements the IRequestHandler<T,U> interface. In this case, we have a handler that handles 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"); }}Copy the code

Each handler class can handle multiple commands. The processing rule 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 will not use that feature in this example.

CQRS

Command Query Responsibility Segregation(CQRS) is a very simple pattern. It requires that we should separate the implementation of commands (writes) from queries (reads) in the system.

With CQRS, we start with:

Do this instead:

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

By combining these two patterns in our sample application, we can create the following schema:

The Command and Query

With MediatR, there is no obvious separation between Command and Query because both implement the IRequest interface. To better separate them, we’ll introduce the following interfaces:

public interface ICommand<T> : IRequest<T>
{
}
public interface IQuery<T> : IRequest<T>
{
}
Copy the code

Here is an example using both 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; }}Copy the code

To further improve our code, we can use the new C# 9 record feature. Internally, it’s still a class, but we generated a lot of boilerplate code for us, including Equality, GetHashCode, ToString…

Front-end Command and Query

To actually receive commands and queries from the outside, we need to create an ASP.NET Core API. These Action methods will receive incoming HTTP commands and pass them to MediatR for further processing. The controller might 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); }}Copy the code

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 our 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>() }); }}Copy the code

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:

  1. First, the compiler compiles your C# source code and generates a syntax tree.
  2. The source code generator can then examine the syntax tree and generate new C# source code.
  3. 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 method.

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 just based on Command/Query we could generate API code as part of the compilation? Something like this:

This means 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; }}Copy the code

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); }}Copy the code

Generate API documentation using OpenAPI

Luckily Swashbuckle is included in the ASP.NET Core 5 API template. By default, you’ll see these classes and generate a nice OpenAPI (Swagger) document for us!

Look at my code

He is made up of:

  • SourceGenerator

This project contains the actual source generator that will generate the API controller Action method.

  • SourceGenerator-MediatR-CQRS
  1. This is a sample application that uses a source code generator. View the project file to see how the project references the source generator.
  2. The Templates folder contains the Templates for the Command and Query classes. The source code generator inserts the generated code into these templates.
  3. CommandAndQueries based on the commands and Queries defined in this folder, the generator generates the corresponding ASP.NET endpoints.

View 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>
Copy the code

This means you can find the generated code in this directory:

\obj\GeneratedFiles\SourceGenerator\SourceGenerator.MySourceGenerator
Copy the code

In this folder you will find the following two files:

conclusion

By introducing the concept of source code generators, 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 shows that anyone can create their own source code generator without too much hassle.

Welcome to pay attention to my public number, if you have a favorite foreign language technical articles, you can recommend to me through the public number message.