DEV Community

Cover image for .NET 6 - AutoMapper & Data Transfer Objects (DTOs) 🗺
Mohamad Lawand
Mohamad Lawand

Posted on

.NET 6 - AutoMapper & Data Transfer Objects (DTOs) 🗺

Intro

In this article we will be exploring AutoMapper and Data Transfer Objects (DTOs) in .Net 6 Web Api. You can watch the full video on YouTube

We can start today by explaining what is AutoMapper and why do we need it

AutoMapper is a library that helps us to transform one object type to another in a very easy accurate way.

We start by creating our web API

dotnet new webapi -n SampleMapper
Enter fullscreen mode Exit fullscreen mode

Once we create our application we need to install the AutoMapper nuget package into our application

dotnet add package AutoMapper --version 12.0.0
dotnet add package AutoMapper.Extensions.Microsoft.DependencyInjection --version 12.0.0
Enter fullscreen mode Exit fullscreen mode

Now that our nuget packages are installed we can start implementing and utilising automapper.

In this example we will be building a simple api which will take DTOs which the client sends and we will be utilising automapper to transform the objs into database obj and vise versa when we get any obj from the data we will do some data transformation to the client so we are not returning the full object

The first thing we need to do is to create a new folder called models which will represent our database tables. Inside the root app directory we add this folder and inside the Models folder we create a new class called Driver and we add the following to it

namespace SampleMapper.Models;

public class Driver
{
    public Guid Id { get; set; }
    public string FirstName { get; set; } = "";
    public string LastName { get; set; } = "";
    public int DriverNumber { get; set; }
    public DateTime DateAdded { get; set; }
    public DateTime DateUpdated { get; set; }
    public int Status { get; set; }
    public int WorldChampionships { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Next we need to create a controller which will responsible for handling all of the Driver requests. In order to make this example as simple as possible we will be using an in-memory database instead of a full database.

using Microsoft.AspNetCore.Mvc;
using SampleMapper.Models;

namespace SampleMapper.Controllers;

[ApiController]
[Route("[controller]")]
public class DriversController : ControllerBase
{
    private static List<Driver> drivers = new List<Driver>();

    private readonly ILogger<DriversController> _logger;

    public DriversController(ILogger<DriversController> logger)
    {
        _logger = logger;
    }

    [HttpGet]
    public IActionResult GetDrivers()
    {
        var items = drivers.Where(x => x.Status == 1).ToList();
        return Ok(items);
    }

    [HttpPost]
    public IActionResult CreateDriver(Driver data)
    {
        if(ModelState.IsValid)
        {
            drivers.Add(data);

            return CreatedAtAction("GetDriver", new {data.Id}, data);
        }

        return new JsonResult("Something went wrong") {StatusCode = 500};
    }

    [HttpGet("{id}")]
    public IActionResult GetDriver(Guid id)
    {
        var item = drivers.FirstOrDefault(x => x.Id == id);

        if(item == null)
            return NotFound();

        return Ok(item);
    }

    [HttpPut("{id}")]
    public IActionResult UpdateDriver(Guid id, Driver item)
    {
        if(id != item.Id)
            return BadRequest();

        var existItem = drivers.FirstOrDefault(x => x.Id == id);

        if(existItem == null)
            return NotFound();

        existItem.FirstName = item.FirstName;
        existItem.LastName = item.LastName;
        existItem.DriverNumber = item.DriverNumber;
        existItem.WorldChampionships = item.WorldChampionships;


        return NoContent();
    }

    [HttpDelete("{id}")]
    public IActionResult DeleteDriver(Guid id)
    {
        var existItem = drivers.FirstOrDefault(x => x.Id == id);

        if(existItem == null)
            return NotFound();

        existItem.Status = 0;

        return Ok(existItem);
    }
}
Enter fullscreen mode Exit fullscreen mode

Now let us see how we can transform this into a more optimised API and more user friendly.

Inside the Models folder we create a new folder called DTOs, inside the DTOs folder we create 2 new folders called

  • incoming: responsible for all incoming requests to our API
  • outgoing: responsible for all outgoing requests from our API

Will start by the incoming request. Inside the incoming folder we add the following class

namespace SampleMapper.Models.DTOs.Incoming;

public class DriverCreationDto
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int DriverNumber { get; set; }
    public int WorldChampionships { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Now let us update the controller to take advantage of this DTO

[HttpPost]
public IActionResult CreateDriver(DriverCreationDto data)
{
    var _driver = new Driver()
    {
        Id = Guid.NewGuid(),
        Status = 1,
        DateAdded = DateTime.Now,
        DateUpdated = DateTime.Now,
        DriverNumber = data.DriverNumber,
        FirstName = data.FirstName,
        LastName = data.LastName,
        WorldChampionships = data.WorldChampionships
    };
    if(ModelState.IsValid)
    {
        drivers.Add(_driver);

        return CreatedAtAction("GetDriver", new {_driver.Id}, data);
    }

    return new JsonResult("Something went wrong") {StatusCode = 500};
}
Enter fullscreen mode Exit fullscreen mode

We can see that there is an advantage by using the Dto but it still not what we need, we are still doing manual mapping between 2 objs.

Here we can see how AutoMapper will be able to help us

Inside the rood directory of the application we are going to create a new folder called Profiles, inside the folder we need to create a new class called DriverProfile and update it to the following

using AutoMapper;
using SampleMapper.Models;
using SampleMapper.Models.DTOs.Incoming;

namespace SampleMapper.Profiles;

public class DriverProfile : Profile
{
    public DriverProfile()
    {
        CreateMap<DriverCreationDto, Driver>()
            .ForMember(
                dest => dest.Id,
                opt => opt.MapFrom(src => Guid.NewGuid())
            )
            .ForMember(
                dest => dest.FirstName,
                opt => opt.MapFrom(src => $"{src.FirstName} ")
            )
            .ForMember(
                dest => dest.LastName,
                opt => opt.MapFrom(src => $"{src.LastName}")
            )
            .ForMember(
                dest => dest.WorldChampionships,
                opt => opt.MapFrom(src => src.WorldChampionships)
            )
            .ForMember(
                dest => dest.Status,
                opt => opt.MapFrom(src => 1)
            )
            .ForMember(
                dest => dest.DriverNumber,
                opt => opt.MapFrom(src => src.DriverNumber)
            );
    }
}
Enter fullscreen mode Exit fullscreen mode

Now let us update controller to utilise the automapper functionalities

private readonly IMapper _mapper;

    public DriversController(
        ILogger<DriversController> logger,
        IMapper mapper)
    {
        _logger = logger;
        _mapper = mapper;
    }

[HttpPost]
    public IActionResult CreateDriver(DriverCreationDto data)
    {
        var _driver = _mapper.Map<Driver>(data);

        if(ModelState.IsValid)
        {
            drivers.Add(_driver);

            return CreatedAtAction("GetDriver", new {_driver.Id}, data);
        }

        return new JsonResult("Something went wrong") {StatusCode = 500};
    }
Enter fullscreen mode Exit fullscreen mode

We also need to update our program.cs to inject AutoMapper into our DI container

builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
Enter fullscreen mode Exit fullscreen mode

Inside Outgoing folder we need add a new class called DriverDto and add the following

public class DriverDto
{
    public Guid Id { get; set; }
    public string FullName { get; set; }
    public int DriverNumber { get; set; }
    public int WorldChampionships { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Now let us utilise this DTO by updating our profile class to the following

CreateMap<Driver, DriverDto>()
            .ForMember(
                dest => dest.Id,
                opt => opt.MapFrom(src => Guid.NewGuid())
            )
            .ForMember(
                dest => dest.FullName,
                opt => opt.MapFrom(src => $"{src.FirstName} {src.LastName}")
            )
            .ForMember(
                dest => dest.DriverNumber,
                opt => opt.MapFrom(src => $"{src.DriverNumber}")
            )
            .ForMember(
                dest => dest.WorldChampionships,
                opt => opt.MapFrom(src => src.WorldChampionships)
            );
Enter fullscreen mode Exit fullscreen mode

And now let us update our controller to take advantage to the following

[HttpGet]
    public IActionResult GetDrivers()
    {
        var items = drivers.Where(x => x.Status == 1).ToList();

        var driverList = _mapper.Map<IEnumerable<DriverDto>>(items);
        return Ok(driverList);
    }
Enter fullscreen mode Exit fullscreen mode

Thank you for reading.

Top comments (1)

Collapse
 
rahul7795 profile image
Rahul R

Hi Mohamad. Great article explaining the significance of DTO! I used to just call automapper and get away with it. Now, I understood the heavy-lifting that's happening behind!