Previously, I use this way to map an entity to a data transfer object(DTO):
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public string Introduction { get; set; }
public ICollection<Employee> Employees { get; set; }
}
public class CompanyDto
{
public int Id { get; set; }
public string Name { get; set; }
}
[ApiController]
[Route("company")]
public class CompanyController : Controller
{
private readonly ICompanyRepository companyRepository;
private readonly IMapper mapper;
public CompanyController(ICompanyRepository companyRepository,IMapper mapper)
{
this.companyRepository = companyRepository;
this.mapper = mapper;
}
[HttpGet]
[Route("")]
public async Task<ActionResult<IEnumerable<CompanyDto>>> GetCompanies()
{
var companies = await companyRepository.GetCompaniesAsync();
var companyDtos = mapper.Map<IEnumerable<CompanyDto>>(companies);
var model = new List<CompanyDto>();
foreach (var company in companies)
{
CompanyDto companyDto = new CompanyDto
{
Id=company.Id,
Name=company.Name
};
model.Add(companyDto);
}
return Ok(model);
}
}
However, if we use AutoMapper, the situation becomes much easier. There are 4 steps.
Install package :
Install AutoMapper.Extensions.Microsoft.DependencyInjection by Nuget package manager.
Create profile
Create a class to build profile:
using AutoMapper;
public class CompanyProfile:Profile
{
public CompanyProfile()
{
CreateMap<Company, CompanyDto>();
}
}
Set up startup.cs
There are two way of append AutoMapper :
public void ConfigureServices(IServiceCollection services)
{
// add AutoMapper
services.AddAutoMapper();
}
or
public void ConfigureServices(IServiceCollection services)
{
// add AutoMapper
var mappingConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new CompanyProfile());
});
IMapper mapper = mappingConfig.CreateMapper();
services.AddSingleton(mapper);
}
These two ways get almost same results.
Using IMapper in a controller
By using AutoMapper, the codes are as simple as follow:
[ApiController]
[Route("company")]
public class CompanyController : Controller
{
private readonly ICompanyRepository companyRepository;
private readonly IMapper mapper;
public CompanyController(ICompanyRepository companyRepository,
IMapper mapper)
{
this.companyRepository = companyRepository;
this.mapper = mapper;
}
[HttpGet]
public async Task<ActionResult<IEnumerable<CompanyDto>>> GetCompanies()
{
var companies = await companyRepository.GetCompaniesAsync();
// use automapper to map companies to companyDtos
var companyDtos = mapper.Map<IEnumerable<CompanyDto>>(companies);
return Ok(companyDtos);
}
[HttpPut("companyId")]
public async Task<ActionResult> UpdateCompanies(
int companyId,CompanyDto companyDto )
{
var companyEntity = await companyRepository.GetCompaniesAsync(companyId);
// map companyEntity to companyDto
// update values in companyDto
// map back companyDto to Company object
// this three things simple done by following sentence
mapper.Map(companyDto, companyEntity);
await companyRepository.UpdateCompany(companyEntity)
return NoContent();
}
}
Notice:
- Entities can either contain more properties or less properties than target DTO
- If the name of the Properties are different between two classes, or people need to combine two properties, it also works fine
- People can combine two entities into one DTO
1.Contain more or less properties
As is seen in the beginning, Company class contains a "Introduction" property, using AutoMapper can automatic ignore this property without a null exception. If Company contains less property, for instance, CompanyDto contains a "Location" property, after mapping, the "Location" field in the object will be null.
2. Difference or Combination
When property's name is different between the classes, there are methods in AutoMapper can be used. For example, we change the name property to CompayName.
public class Company
{
public int Id { get; set; }
public string CompanyName { get; set; }
public string Introduction { get; set; }
public ICollection<Employee> Employees { get; set; }
}
public class CompanyDto
{
public int Id { get; set; }
public string Name { get; set; }
}
Create mapper can be:
CreateMap<Company, CompanyDto>()
.ForMember(dest=>dest.CompanyName,
opt=>opt.MapFrom(src=>src.Name));
Or if you want to combine two property together in Employee:
public class Employee
{
public int Id { get; set; }
public int CompanyId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Company Company { get; set; }
}
public class EmployeeDto
{
public int Id { get; set; }
public int CompanyId { get; set; }
public string Name { get; set; }
public string CompanyName { get; set; }
}
Create mapper can be:
CreateMap<Employee, EmployeeDto>()
.ForMember(dest => dest.Name,
opt => opt.MapFrom(src => $"{src.FirstName} {src.LastName}"));
3. Combine two entities together
When People want to combine Employee and Company to EmployeeDto, it can be:
[HttpGet]
[Route("")]
public async Task<ActionResult<IEnumerable<EmpDto>>> GetCompanies()
{
var companies = await companyRepository.GetCompaniesAsync();
var emps = await companyRepository.GetEmpAsync();
var empDtos = mapper.Map< IEnumerable<Company>,
IEnumerable<CompDto>>(companies);
mapper.Map<IEnumerable<Employee>,IEnumerable <CompDto>>(emps, empDtos);
return Ok(empDtos);
}