Mapper Source Generator Example
This project demonstrates how you can use incremental source generators in C# to generate domain transfer objects (DTOs) and mapping classes from a POCO.
Warning
This is a sample project and not intended for production use. It is meant to illustrate the concept of using source generators. Please look into using either mapster's code generator or mapperly instead.
Requirements
- .NET 8.0 SDK
What's Included
MapperSourceGen: The main project that contains the attributes for decorating classes and properties for the source generator.MapperSourceGen.Sample: A sample class library that demonstrates the use of the source generator.MapperSourceGen.SourceGenerator: The source generator that generates DTOs and mapping classes.MapperSourceGen.SourceGenerator.Tests: Unit tests for the source generator to ensure it works as expected (snapshot testing).
Feel free to inspect the .csproj files for the projects to see how they are set up and how the source generator is integrated.
Note
The source files emitted by the source generator will be under
Dependencies -> .NET 8.0 -> Source Generatorsin theMapperSourceGen.Sampleproject if using Rider.
Purpose
As projects grow, the need for transferring data between layers (like from a database to a UI) becomes essential. However, with that comes the additional complexity of maintaining DTOs and mapping classes. One of the ways this can be solved is by using source generators to automate the creation of these classes.
Source generators provide the ability to generate code at compile time, which can help reduce boilerplate code and improve maintainability and reduce the reliance on expensive reflection-based mapping.
How It Works
The project MapperSourceGen.SourceGenerator contains a source generator that scans for classes decorated with the GenerateMapperAttribute.
When it finds such a class, it generates a DTO and a mapping class for it.
Example
Let's say you have a class Order that you want to generate a DTO and mapping class for. You would decorate it with the GenerateMapperAttribute like this:
Order.cs
[Mapper]
public sealed class Order
{
[MapAlias("EntityId")] // renames Id to EntityId in the DTO
public Guid Id { get; set; }
public Guid? CustomerId { get; set; }
[MapAlias("OrderId")] // renames IncrementId to OrderId in the DTO
public int IncrementId { get; set;}
public DateTimeOffset Created { get; set;}
public DateTimeOffset? Updated { get; set; }
[MapIgnore] // this property will not be included in the DTO or mapper
public string TransactionId { get; set; }
}
The source generator will then generate a DTO class OrderDto and a mapping class OrderMapper that looks like this:
OrderMapper.g.cs
public sealed partial class OrderMapper
{
public static Order ToModel(OrderDto source)
{
ArgumentNullException.ThrowIfNull(source);
return new Order()
{
Id = source.EntityId,
CustomerId = source.CustomerId,
IncrementId = source.OrderId
Created = source.Created,
Updated = source.Updated
}
}
}
OrderDto.g.cs
public sealed partial class OrderDto
{
public Guid? CustomerId { get; set; }
public Guid EntityId { get; set; }
public int OrderId { get; set; }
public DateTimeOffset Created { get; set; }
public DateTimeOffset? Updated { get; set; }
}