1. Domain Entity Mapping

Domain Entity Mapping

For those of us who are not fans of AutoMapper and the like, this library offers a cleaner way to do manual mapping for request DTO to domain entity and back from an entity to a response DTO.

Consider the following request, response and entity classes:

public class Request
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string BirthDay { get; set; }
}

public class Response
{
    public int Id { get; set; }
    public string UserName { get; set; }
    public string FullName { get; set; }
    public int Age { get; set; }
}

public class Person
{
    public int Id { get; set; }
    public string FullName { get; set; }
    public DateOnly DateOfBirth { get; set; }
}

Mapping Logic In a Separate Class

The recommended approach is to keep the mapping logic in a class of it's own by inheriting from Mapper<TRequest, TResponse, TEntity> like so:

public class PersonMapper : Mapper<Request, Response, Person>
{
    public override Person ToEntity(Request r) => new()
    {
        Id = r.Id,
        DateOfBirth = DateOnly.Parse(r.BirthDay),
        FullName = $"{r.FirstName} {r.LastName}"
    };

    public override Response FromEntity(Person e) => new()
    {
        Id = e.Id,
        FullName = e.FullName,
        UserName = $"USR{e.Id:0000000000}",
        Age = (DateOnly.FromDateTime(DateTime.UtcNow).DayNumber - e.DateOfBirth.DayNumber) / 365,
    };
}

To use the above mapper you need to inherit your endpoint from Endpoint<TRequest, TResponse, TMapper> generic overload like so:

public class SavePerson : Endpoint<Request, Response, PersonMapper>
{
    public override void Configure()
    {
        Put("/api/person");
    }

    public override Task HandleAsync(Request r, CancellationToken c)
    {
        Person entity = Map.ToEntity(r);
        Response = Map.FromEntity(entity);
        return SendAsync(Response);
    }
}

The mapping logic can be accessed from the Map property of the endpoint class. that's all there's to it.

WARNING

Mapper classes are used as singletons for performance reasons. You should not maintain state in your mappers.

Mapper Base Class Variants

In cases your endpoint has either just a request DTO or just a response DTO, you can inherit from one of the following mapper base class variants.

  • RequestMapper<TRequest, TEntity> for EndpointWithMapper<TRequest, TMapper>

  • ResponseMapper<TResponse, TEntity> for EndpointWithoutRequest<TResponse, TMapper>

Mapping Logic In The Endpoint

If you prefer to place your mapping logic in the endpoint definition itself, you can simply use the EndpointWithMapping<TRequest,TResponse,TEntity> generic overload to implement your endpoint and override the MapToEntity() and MapFromEntity() methods like so:

public class SavePerson : EndpointWithMapping<Request, Response, Person>
{
    public override void Configure() => Put("/api/person");

    public override Task HandleAsync(Request r, CancellationToken c)
    {
        Person entity = MapToEntity(r);
        Response = MapFromEntity(entity);
        return SendAsync(Response);
    }

    public override Person MapToEntity(Request r) => new()
    {
        Id = r.Id,
        DateOfBirth = DateOnly.Parse(r.BirthDay),
        FullName = $"{r.FirstName} {r.LastName}"
    };

    public override Response MapFromEntity(Person e) => new()
    {
        Id = e.Id,
        FullName = e.FullName,
        UserName = $"USR{e.Id:0000000000}",
        Age = (DateOnly.FromDateTime(DateTime.UtcNow).DayNumber - e.DateOfBirth.DayNumber) / 365,
    };
}

Dependency Injection

Mappers are used as singletons for performance reasons. I.e. there will only ever be one instance of a mapper type. You should not maintain state in mappers. If you need to resolve scoped dependencies in your mappers, you may do so as shown here.


© FastEndpoints 2022