Customize your Mapping in AutoMapper

Customize your Mapping in AutoMapper

In this article, we will be talking about advanced topics in AutoMapper Custom mapping in AutoMapper.

What is AutoMapper?

Automapper is a library that helps you to do object-to-object mapping. it has many options and allows many configurations.

What's object-to-object mapping?

Is the process of taking an object of type A and transforming it into another object of type B, for most cases, it's when you are using DTOs or ViewModel(for Asp.net MVC) object.

What is the Advantage of using AutoMapper?

The advantage of using this library is that you can gain time and reduce code lines and avoid repeated tasks, like doing object mapping, which process can take more than 10 rows for a big object.

To explain this topic I will create 3 classes, Book, Author, and BookCategory. we will use the relationship from the Entity framework model first.

public class Book
{
    [Key]
    public long Id { get; set; }
    public string? Title { get; set; }
    public DateTime PublicationYear { get; set; }
    public long? AuthorId { get; set; }
    public long? CategoryId { get; set; }

    public Author? Author { get; set; }
    public Category? Category { get; set; }
}
public class Author
{
    public Author()
    {
        Books = new HashSet<Book>();
    }

    [Key]
    public long Id { get; set; }
    public string? Firstname { get; set; }
    public string? Lastname { get; set; }
    public DateTime BirthDay { get; set; }

    public ICollection<Book>? Books { get; set; }
}

As you can see there is a dependency of one-to-many between book and author entity

public class BookCategory
{
    public long Id { get; set; }
    public string? Name { get; set; }
}

First I will create a DTO, which is supposed to display to the user the entire details of a selected book to a user, I will call it BookDTO

 public class BookDTO
 {
        public long Id { get; set; }
        public string? Title { get; set; }
        public string? PublicationYear { get; set; }
        public string? AuthorFirstname { get; set; }
        public string? AuthorLastname { get; set; }
        public DateTime AuthorBirthDay { get; set; }
        public string? CategoryName { get; set; }
 }

so to customize the mapping in the profile class, we will proceed like this

public class MappingProfile : Profile
{
   public MappingProfile()
   {

           CreateMap<Book, BookDTO>()
                .ForMember(dest => dest.AuthorFirstname , opt => opt.MapFrom(src =>   
                 src.Author.Firstname))
                .ForMember(dest => dest.AuthorLastname , opt => opt.MapFrom(src => 
                src.Author.Lastname))
                .ForMember(dest => dest.AuthorBirthDay , opt => opt.MapFrom(src => 
                src.Author.BirthDay))
                .ForMember(dest => dest.PublicationYear, opt => opt.MapFrom(src => 
                src.PublicationYear.ToShortDateString()))//(1)
                .ForMember(dest => dest.CategoryName , opt => opt.MapFrom(src => 
                src.Category.Name));
     }
}

So what going on in the MappingProfile Class? well as usually when using Automapper we always have a configuration class where we specify the mapping policy for our classes(DTOs/ViewModels). For customs objects like BookDTO we needed to specify the mapping policies in the AutoMapper profiles class in other words we need to configure the mapping behaviors. In our snippet, we are configuring the mapping behavior in a way that AutoMapper will for the property Firstname of the BookDTO object, map it from the Book Author's Firstname property, and the same for "AuthorLastname", "AuthorBirthDay", "PublicationYear", "CategoryName ". And to use those configurations in the Business logic, it's quite simple. see below.

public async Task<IEnumerable<BookDTO>> GetBooksAsync()
 {
      var books = await _context
          .Books
          .Include(x => x.Author)
          .Include(x => x.Category)
          .ToListAsync();

      return _mapper.Map<IEnumerable<BookDTO>>(books);//list mapping
}

public async Task<BookDTO> GetBookAsync()
{
    var book = await _context
        .Books
        .Include(x => x.Author)
        .Include(x => x.Category)
        .FirstOrDefaultAsync();

    return _mapper.Map<BookDTO>(book); //Single object mapping
}

You can even decide to do serialization and deserialize on a property or replace null with a specific value. For example, if in our BookDTO object we had a property of type Author and when inserting in the table Book we serialized the author into the column Author we could just do as shown below

CreateMap<Book, BookDTO>()
           .ForMember(dest => dest.Author, opt => opt.NullSubstitute(new Author()))
            .ForMember(dest => dest.Author, opt => opt.MapFrom(src =>     
            JsonConvert.DeserializeObject<object>(src.Author)));

If you want to configure serialization in AutoMapper's profile class to serialize an object and map it to a string property into another object you can proceed as shown below.

CreateMap<AddBookDTO, Book>()
          .ForMember(dest => dest.Author, opt => opt.MapFrom(src =>  
          JsonConvert.SerializeObject(src.Author)));

Conclusion

AutoMapper can simplify your life as a developer(.Net developer) by avoiding repeated tasks. with the eg and explanation shown in the above lines. i hope it helps you