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
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
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; }
}
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);
}
}
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; }
}
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};
}
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)
);
}
}
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};
}
We also need to update our program.cs to inject AutoMapper into our DI container
builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
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; }
}
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)
);
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);
}
Thank you for reading.
Top comments (1)
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!