Component refactoring
.NET Core C# ORM Patterns

How to Use AutoMapper in a .NET Core Application

Welcome to today’s post.

In today’s post I will be showing how to use an ORM mapping tool like AutoMapper to tidy up your mapping code within your application and give guidelines on whether to use a mapping tool.

Why should I use an ORM mapping tool within my application?

The reasons might be:

  1. To tidy up your mapping code. Manually crafted mapping repeated for the same view models and DTOs can look untidy.
  2. Reduce hard to find errors down the track caused by redundant fields.
  3. Localized mapping logic so that debugging is easier.
  4. Limiting your UI views and dependent APIs and applications to necessary data fields.

In addition, AutoMapper is an ORM library that allows us to use the well-known Adapter design pattern without having to implement classes to map one DTO / model structure to another DTO / model structure.

To install AutoMapper within a .NET Core project you search for and install the following NuGet packages:

You will need to configure AutoMapper to be used within your .NET Core service collection. Within ConfigureServices() in your Startup.cs file:

using AutoMapper;
…
public void ConfigureServices(IServiceCollection services)
{
    …
    services.AddAutoMapper(typeof(Startup));
    …
}

The next configuration you will need to so is by creating a mapping profile. A mapping profile defines which objects you would like to map, and the member field of each source object you wish to map to a corresponding destination objects member field.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AutoMapper;
using BookLoan.Models;
	
namespace BookLoan.Loan.API.Mapping
{
    public class BookLoanMappingProfile : Profile
    {
        public BookLoanMappingProfile()
        {
            CreateMap<BookStatusViewModel, LoanReportDto>()
	            .ForMember(dest => dest.Status, 
                    opt => opt.MapFrom(src => src.Status))
	            .ForMember(dest => dest.Title, 
                    opt => opt.MapFrom(src => src.Title))
	            .ForMember(dest => dest.Borrower, 
                    opt => opt.MapFrom(src => src.Borrower))
	            .ForMember(dest => dest.DateLoaned, 
                    opt => opt.MapFrom(src => src.DateLoaned))
                .ForMember(dest => dest.DateReturn, 
                    opt => opt.MapFrom(src => src.DateReturn))
	            .ForMember(dest => dest.DateDue, 
                    opt => opt.MapFrom(src => src.DateDue));
        }
    }
}

As you can see, I have mapped several fields from the model class that contains the higher number of member fields to a model DTO class that contains a subset of the fields.

I have used the AutoMapper extension method ForMember() which allows us to chain together a number of mappings in the same mapping configuration.

Using the mapper within our classes requires us to inject the instance into the constructor. This is done using the IMapper interface.

public class ReportService : IReportService
{
    ...
    private readonly IMapper _mapper;

    public ReportService(
	    ...
        IMapper mapper)
    {
        ...
        _mapper = mapper;
    }
    ...
}

In the mapping profile we have a pair of classes.

The model definition for the source object is shown below:

public class BookStatusViewModel: BookViewModel
{
    public string Status { get; set; }
    public DateTime DateLoaned { get; set; }
    public DateTime DateDue { get; set; }
    public DateTime DateReturn { get; set; }
    public string Borrower { get; set; }
    public bool OnShelf { get; set; }
}

The model definition for the destination object is shown below:

public class LoanReportDto
{
    public string Status { get; set; }
    public string Title { get; set; }
    public DateTime DateLoaned { get; set; }
    public DateTime DateDue { get; set; }
    public DateTime DateReturn { get; set; }
    public string Borrower { get; set; }
    public int DaysOverdue { get; set; }
}

Below is a method that contains manual mapping of the above classes:

private LoanReportDto BookToBookLoanStatus(BookViewModel input)
{
    BookLoan.Models.BookStatusViewModel bookStatus = 
        _loanService.GetBookLoanStatus(input.ID).GetAwaiter().GetResult();
         
    return new BookStatusViewModel()
    {
        ID = input.ID,
        Author = input.Author,
        Title = input.Title,
        Genre = input.Genre,
        ISBN = input.ISBN,
        Edition = input.Edition,
        Location = input.Location,
        YearPublished = input.YearPublished,
        OnShelf = bookStatus.OnShelf,
        DateLoaned = bookStatus.DateLoaned,
        DateReturn = bookStatus.DateReturn,
        DateDue = bookStatus.DateDue,
        Status = bookStatus.Status,
        Borrower = bookStatus.Borrower
    };
}

After reviewing the dependencies of the above method, we decide to use AutoMapper to remap the above to a subset of the above fields in the LoanReportDto class. To execute a mapping we use the Map() method which is defined as follows:

  var dto =_mapper.Map<DestinationDto>(SourceDto);

Where:

SourceDto is the source mapping object.

DestinationDto is the destination mapping object.

dto is the resulting mapping object.

After applying AutoMapper our method looks as shown:

private LoanReportDto BookToBookLoanStatus(BookViewModel input)
{
    BookLoan.Models.BookStatusViewModel bookStatus = 
        _loanService.GetBookLoanStatus(input.ID).GetAwaiter().GetResult();
    bookStatus.Title = input.Title;

    var loanReport =_mapper.Map<LoanReportDto>(bookStatus);
            
    return loanReport;
}

After running our application and debugging we notice that the source mapping for the book loan status is shown with the three source date mapping fields highlighted:

Following the mapping, the same source date fields have been mapping into the destination object as shown:

After the mapping, our code is cleaner and easier to understand.

There are many more configurations we can apply to our mappings including conditional mappings and mappings of lists, custom resolvers and so on.

More details are on the AutoMapper documentation.

That’s all for today’s post.

I hope you found this post useful and informative.

Social media & sharing icons powered by UltimatelySocial