How to use AutoMapper in .NET Web API

Introduction

In this article, we are going to explore how to use AutoMapper in the .NET Web API Application with examples.

What is AutoMapper?

AutoMapper is an object-to-object mapping library that helps transform one object type into another. It's mainly useful for mapping complex objects, such as Data Transfer Objects (DTOs), to domain models and vice versa. It reduces the need for manual property mapping and simplifies the code.

Why Use AutoMapper?

  • Reduces Code: Instead of writing repetitive code to map properties from one object to another, AutoMapper handles property mapping between the objects.
  • Improves Maintainability: AutoMapper centralizes the mapping definitions. When updates or additions are needed, adjust the configuration, making the code easier to manage.
  • Increases Productivity: AutoMapper speeds up development by saving time when writing and testing mapping code.
  • Standardizes Mapping: AutoMapper ensures consistent and reliable mappings, reducing errors and improving data integrity.

How to use AutoMapper?

Step 1. Install AutoMapper

Install AutoMapper and AutoMapper.Extensions.Microsoft.DependencyInjection using NuGet Package Manager or the Package Manager Console.

Install-Package AutoMapper
Install-Package AutoMapper.Extensions.Microsoft.DependencyInjection

Step 2. Create Models and DTOs

Define the models (Person) and corresponding DTOs (PersonDTO).

public class Person
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}
public class PersonDTO
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}

Step 3. Create an AutoMapper Profile:

Create a MappingProfile class inheriting from Profile to configure the mappings between models and DTOs using CreateMap.

using AutoMapper;

public class MappingProfile : Profile
{
    public MappingProfile()
    {
        CreateMap<Person, PersonDTO>();
        CreateMap<PersonDTO, Person>();
    }
}

Step 4. Register AutoMapper in Program.cs:

Register AutoMapper using the ConfigureServices() method to enable dependency injection and automatic mapping configuration.

using AutoMapper;

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.AddAutoMapper(typeof(Program));  // Register AutoMapper
}

Step 5. Use AutoMapper in a Controller

The PersonController uses AutoMapper to map between Person and PersonDTO objects in the CreatePerson() and GetPerson() action methods. The CreatePerson() method maps the PersonDTO to a Person entity for saving to the database, while the GetPerson() method maps the Person entity to a PersonDTO for returning as a response.

[ApiController]
[Route("api/[controller]")]
public class PersonController : ControllerBase
{
    private readonly IMapper _mapper;

    public PersonController(IMapper mapper)
    {
        _mapper = mapper;
    }

    [HttpPost]
    public IActionResult CreatePerson([FromBody] PersonDTO personDto)
    {
        var person = _mapper.Map<Person>(personDto);
        
        // Save the person entity to the database
        
        return Ok(person);
    }

    [HttpGet("{id}")]
    public IActionResult GetPerson(int id)
    {
        // Retrieve the person entity from the database
        var person = new Person { Id = id, FirstName = "Test", LastName = "Test1", Age = 30 };
        
        var personDto = _mapper.Map<PersonDTO>(person);
        return Ok(personDto);
    }
}

Mapping with Custom Logic

Sometimes, we need to apply custom logic during the mapping process.

example, we have a scenario where we need to combine the FirstName and LastName properties into a single FullName property in the destination object

Define Models and DTOs

public class User
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime DateOfBirth { get; set; }
}
public class UserDTO
{
    public string FullName { get; set; }
    public int Age { get; set; }
}

Create a Mapping Profile

  • Create a UserProfile class that inherits from Profile.
  • Use the CreateMap method to define the mapping between User and UserDTO.
  • Use ForMember to apply custom logic:
    • Combine FirstName and LastName into FullName.
    • Compute Age using the CalculateAge method.
using AutoMapper;

public class UserProfile : Profile
{
    public UserProfile()
    {
        CreateMap<User, UserDTO>()
            .ForMember(dest => dest.FullName, opt => opt.MapFrom(src => $"{src.FirstName} {src.LastName}"))
            .ForMember(dest => dest.Age, opt => opt.MapFrom(src => CalculateAge(src.DateOfBirth)));
    }

    private int CalculateAge(DateTime dateOfBirth)
    {
        var today = DateTime.Today;
        var age = today.Year - dateOfBirth.Year;
        if (dateOfBirth.Date > today.AddYears(-age)) 
        {
            age--;
        }
        return age;
    }
}

Use AutoMapper in a Controller

Use AutoMapper to map between User and UserDTO in a controller.

[HttpPost]
public IActionResult CreateUser([FromBody] User user)
{
    var userDto = _mapper.Map<UserDto>(user);
    return Ok(userDto);
}

Sample Response

CreateUser Request (Input: User)

{
    "FirstName": "John",
    "LastName": "Doe",
    "DateOfBirth": "1990-01-01"
}

CreateUser Response (Output: UserDto)

{
    "FullName": "John Doe",
    "Age": 34
}

Flattening Complex Objects

Flattening complex objects in AutoMapper involves transforming nested objects into a simpler, flat structure. This is useful when you want to combine properties from nested objects into a single object, such as when preparing data for a UI or for API responses.

Define Models and DTOs

Create the source models and the flattened destination DTO.

public class Order
{
    public int OrderId { get; set; }
    public DateTime OrderDate { get; set; }
    public Customer Customer { get; set; }
    public List<OrderDetail> OrderDetails { get; set; }
}

public class Customer
{
    public int CustomerId { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}

public class OrderDetail
{
    public int ProductId { get; set; }
    public string ProductName { get; set; }
    public int Quantity { get; set; }
    public decimal Price { get; set; }
}
public class OrderDto
{
    public int OrderId { get; set; }
    public DateTime OrderDate { get; set; }
    public string CustomerName { get; set; }
    public string CustomerEmail { get; set; }
    public List<OrderDetailDto> OrderDetails { get; set; }
}

public class OrderDetailDto
{
    public int ProductId { get; set; }
    public string ProductName { get; set; }
    public int Quantity { get; set; }
    public decimal Price { get; set; }
}

Create a Mapping Profile

Define the mapping configuration to flatten the nested objects.

using AutoMapper;

public class OrderProfile : Profile
{
    public OrderProfile()
    {
        CreateMap<Order, OrderDto>()
            .ForMember(dest => dest.CustomerName, opt => opt.MapFrom(src => src.Customer.Name))
            .ForMember(dest => dest.CustomerEmail, opt => opt.MapFrom(src => src.Customer.Email));

        CreateMap<OrderDetail, OrderDetailDto>();
    }
}

Use AutoMapper in a Controller

Use AutoMapper to map between Order and OrderDto in a controller.

[ApiController]
[Route("api/[controller]")]
public class OrderController : ControllerBase
{
    private readonly IMapper _mapper;

    public OrderController(IMapper mapper)
    {
        _mapper = mapper;
    }

    [HttpPost]
    public IActionResult CreateOrder([FromBody] Order order)
    {
        var orderDto = _mapper.Map<OrderDto>(order);
        return Ok(orderDto);
    }

    [HttpGet("{id}")]
    public IActionResult GetOrder(int id)
    {
        // Retrieve the order entity from the database
        var order = new Order
        {
            OrderId = id,
            OrderDate = DateTime.Now,
            Customer = new Customer { CustomerId = 1, Name = "Test", Email = "[email protected]" },
            OrderDetails = new List<OrderDetail>
            {
                new OrderDetail { ProductId = 1, ProductName = "Product A", Quantity = 2, Price = 50 },
                new OrderDetail { ProductId = 2, ProductName = "Product B", Quantity = 1, Price = 30 }
            }
        };
        
        var orderDto = _mapper.Map<OrderDto>(order);
        return Ok(orderDto);
    }
}

Sample Output

CreateOrder Request (Input: Order)

{
    "OrderId": 101,
    "OrderDate": "2024-12-20T12:34:56",
    "Customer": {
        "CustomerId": 1,
        "Name": "Test",
        "Email": "[email protected]"
    },
    "OrderDetails": [
        {
            "ProductId": 1,
            "ProductName": "Product A",
            "Quantity": 2,
            "Price": 50.00
        },
        {
            "ProductId": 2,
            "ProductName": "Product B",
            "Quantity": 1,
            "Price": 30.00
        }
    ]
}

CreateOrder Response (Output: OrderDto)

{
    "OrderId": 101,
    "OrderDate": "2024-12-20T12:34:56",
    "CustomerName": "Test",
    "CustomerEmail": "[email protected]",
    "OrderDetails": [
        {
            "ProductId": 1,
            "ProductName": "Product A",
            "Quantity": 2,
            "Price": 50.00
        },
        {
            "ProductId": 2,
            "ProductName": "Product B",
            "Quantity": 1,
            "Price": 30.00
        }
    ]
}

GetOrder Request

GET /api/order/101

{
    "OrderId": 101,
    "OrderDate": "2024-12-20T12:34:56",
    "CustomerName": "Test",
    "CustomerEmail": "[email protected]",
    "OrderDetails": [
        {
            "ProductId": 1,
            "ProductName": "Product A",
            "Quantity": 2,
            "Price": 50.00
        },
        {
            "ProductId": 2,
            "ProductName": "Product B",
            "Quantity": 1,
            "Price": 30.00
        }
    ]
}

Reverse Mapping

AutoMapper supports reverse mapping, which can be handy when you need to map back from a DTO to the original entity. Let's consider a scenario where we have an Employee entity and an EmployeeDto. We'll configure AutoMapper to map from Employee to EmployeeDto and also from EmployeeDto back to Employee.

Define Models and DTOs

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

public class EmployeeDto
{
    public int Id { get; set; }
    public string FullName { get; set; }
}

Create a Mapping Profile

  • Create an EmployeeProfile class that inherits from Profile.
  • Use CreateMap to define the mapping between Employee and EmployeeDto.
  • Use ForMember to apply custom logic:
    • Combine FirstName and LastName into FullName for EmployeeDto.
    • Use ReverseMap to define the reverse mapping, splitting FullName back into FirstName and LastName.
public class EmployeeProfile : Profile
{
    public EmployeeProfile()
    {
        CreateMap<Employee, EmployeeDto>()
            .ForMember(dest => dest.FullName, opt => opt.MapFrom(src => $"{src.FirstName} {src.LastName}"))
            .ReverseMap()
            .ForMember(dest => dest.FirstName, opt => opt.MapFrom(src => src.FullName.Split(' ')[0]))
            .ForMember(dest => dest.LastName, opt => opt.MapFrom(src => src.FullName.Split(' ')[1]));
    }
}

Use AutoMapper in a Controller

Use AutoMapper to map between Employee and EmployeeDto in a controller. The EmployeeController uses AutoMapper to map between Employee and EmployeeDto objects in the CreateEmployee and GetEmployee action methods.

  • The CreateEmployee method maps EmployeeDto to Employee to save to the database.
  • The GetEmployee method maps Employee to EmployeeDto for returning as a response.
[ApiController]
[Route("api/[controller]")]
public class EmployeeController : ControllerBase
{
    private readonly IMapper _mapper;

    public EmployeeController(IMapper mapper)
    {
        _mapper = mapper;
    }

    [HttpPost]
    public IActionResult CreateEmployee([FromBody] EmployeeDto employeeDto)
    {
        var employee = _mapper.Map<Employee>(employeeDto);
        // Save the employee entity to the database 
        
        return Ok(employee);
    }

    [HttpGet("{id}")]
    public IActionResult GetEmployee(int id)
    {
        // Retrieve the employee entity from the database
        var employee = new Employee { Id = id, FirstName = "Test", LastName = "Test1" };
        
        var employeeDto = _mapper.Map<EmployeeDto>(employee);
        return Ok(employeeDto);
    }
}

Sample Output

CreateEmployee Request (Input: EmployeeDto)

{
    "Id": 1,
    "FullName": "John Doe"
}

CreateEmployee Response (Output: Employee)

{
    "Id": 1,
    "FirstName": "John",
    "LastName": "Doe"
}

GetEmployee Request

GET /api/employee/1

{
    "Id": 1,
    "FullName": "John Doe"
}

Summary

In this article, you have learned the following topics.

  • What is AutoMapper?
  • How to use AutoMapper in the .NET Web API Application with examples
  • Different use cases for AutoMapper