ASP.NET Core: Using AutoMapper

AutoMapper is a simple C# library that helps to transform one object into another object. This is a less configuration-based conventional object-to-object mapper library. This object-to-object mapping works by transforming the input object of one type to an output object of a different type. 

Why the AutoMapper is needed?

The main reason behind using AutoMapper is to make sure that we can hide the important properties of the core object (aka the domain object) from showing them directly to the client. 

Let's take an example here, in any Employee Information System the actual Employee Domain object has various properties like Name, DeptName, Designation, Salary, Experience, TasksToPerform, etc. When this object is needed by the Accounting System, it just needs Name, DeptName, and Salary, so instead of passing the Employee object to the Accounting System with unnecessary data, it's better to create a separate ViewModel DTO with necessary properties for Accounting System and map with the Employee object. But in this case, mapping the Employee object with ViewModel DTO explicitly increases code complexity with an additional coding overhead. This is where an AutoMapper library is useful.

Using the AutoMapper library makes the code simple and more maintainable and further, it helps to implement a Clean Architecture.

Technically, the AutoMapper uses a concept of Reflection to retrieve the type metadata of objects. 


To use the AutoMapper, we need to install the following package

AutoMapper.Extensions.Microsoft.DependencyInjection

The most important class of the package is Profile class. This class provides the named configuration a map between the actual domain object and the ViewModel DTO object. This class provides the CreateMap() method as follows


 CreateMap<TSource, TDestination>()

This is a generic method. In this method, the TSource is the actual Core Domain object which will be mapped with the TDestination object. This helps to prevent the direct access of the Core Domain object by the different systems or other outer layers of the application architecture.  


The Implementation of the AutoMapper with ASP.NET Core

Step 1: Open Visual Studio 2022 and create a new ASP.NET Core API project targeted to .NET 7. Name this project as Cire_AutoMapper.    

Step 2: In this project, add the AutoMapper.Extensions.Microsoft.DependencyInjection package so that we can use the AutoMapper in this project.

Step 3: In this project, add a new folder named Models. In this folder, add a new class file named Address.cs. In this class file, we will add an Address class as shown in Listing 1


namespace Core_AutoMapper.Models
{
    public class Address
    {
        public string? BuildingOrSocietyName { get; set; }
        public string? BuindingNoFlatNo { get; set; }
        public string? Area { get; set; }
        public string? City { get; set; }
        public string? State { get; set; }
        public override  string ToString()
        {
            return $"{BuildingOrSocietyName}, {BuindingNoFlatNo}, {Area}, {City}, {State}";
        }
    }
}

Listing 1: The Address class

The Address class contains the ToString() method that returns a string by concatenating all properties of the Address class.   

In the same folder, add a new class file named Employee.cs. In this class file, we will add Employee class as shown in Listing 2


namespace Core_AutoMapper.Models
{
    public class Employee
    {
        public int EmpNo { get; set; }
        public string? EmpName { get; set; }
        public string? DeptName { get; set; }
        public string? Designation { get; set; }
        public Address? Address { get; set; }
        public int Salary { get; set; }
    }
}

Listing 2: The Employee class  

The Employee class contains an Address property of the type Address class.      

Step 4: In the project, add a new folder named Database. In this folder, we will add EmployeesDb.cs. In this class file, we will add EmpllyeesDb class that contains the hard-coded Employee data as shown in Listing 3


using Core_AutoMapper.Models;

namespace Core_AutoMapper.Database
{
    public class EmployeesDb : List<Employee>
    {
        public EmployeesDb()
        {
            Add(new Employee() {
                EmpNo=101,
                EmpName="Mahesh",
                DeptName="IT",
                Designation= "Director",
                Address =new Address() 
                {
                   BuildingOrSocietyName = "Society-1",
                   BuindingNoFlatNo = "B192",
                   Area= "A.M.Road",
                   City = "Pune",
                   State = "MH"
                },
             Salary =1200000
            });

            Add(new Employee()
            {
                EmpNo = 102,
                EmpName = "Manoj",
                DeptName = "HR",
                Designation = "Director",
                Address = new Address()
                {
                    BuildingOrSocietyName = "Society-2",
                    BuindingNoFlatNo = "C192",
                    Area = "B.M.Road",
                    City = "Pune",
                    State = "MH"
                },
                Salary = 3200000
            });

            Add(new Employee()
            {
                EmpNo = 103,
                EmpName = "Ajay",
                DeptName = "IT",
                Designation = "Manager",
                Address = new Address()
                {
                    BuildingOrSocietyName = "Society-3",
                    BuindingNoFlatNo = "D192",
                    Area = "C.M.Road",
                    City = "Bangalore",
                    State = "KR"
                },
                Salary = 900000
            });

            Add(new Employee()
            {
                EmpNo = 104,
                EmpName = "Amit",
                DeptName = "HR",
                Designation = "Manager",
                Address = new Address()
                {
                    BuildingOrSocietyName = "Society-4",
                    BuindingNoFlatNo = "E192",
                    Area = "K.M.Road",
                    City = "Bangalore",
                    State = "KR"
                },
                Salary = 800000
            });


        }
    }
}


Listing 3: The EmployeesDb class

Step 5: In the project, add a new folder named Services. In this folder, add a new class file named EmployeeService.cs. In this file, add the EmployeeService class as shown in Listing 4


using Core_AutoMapper.Database;
using Core_AutoMapper.Models;

namespace Core_AutoMapper.Services
{
    public class EmployeeService
    {
        EmployeesDb employeesDb;

        public EmployeeService()
        {
            employeesDb = new EmployeesDb();
        }

        public List<Employee> GetEmoployees()
        {
            return employeesDb;
        }
    }
}

Listing 4: The EmployeeService  

   

Step 6: In the project, add a new folder named ViewModels. In this folder, add a new class file named EmployeeViewModel.cs. In this class file, we will add code for the EmployeeViewModel class. This class will be used as a destination class that will be mapped with the Employee class. The code for the EmployeeViewModel is shown in Listing 5


using System.ComponentModel.DataAnnotations;

namespace Core_AutoMapper.ViewModels
{
    public class EmployeeViewModel
    {
        [Display(Name ="Employee Name")]
        public string? EmpName { get; set; }
        [Display(Name = "Department Name")]
        public string? DeptName { get; set; }
        [Display(Name = "Designation")]
        public string? Designation { get; set; }
        [Display(Name = "Address")]
        public string? Address { get; set; }
    }
}

Listing 5: The EmployeeViewModel class

The EmployeeViewModel class contains the string properties. Most of the properties of the EmployeeViewModel class match with properties of the Employee class. But as shown in Listing 2, the Employee class contains the Address property of the type Address class, but in the EmployeeViewModel the Address property is of the type string. This is where we need to use the AutoMapper carefully to make sure that the properties of the Employee class match with the EmployeeViewModel.  To configure the AutoMapper, we need to create a Profile class as shown in Listing 6. 


using AutoMapper;
using Core_AutoMapper.Models;
using Core_AutoMapper.ViewModels;

namespace Core_AutoMapper.Mappers
{
    public class EmployeeProfile : Profile
    {
        public EmployeeProfile()
        {
            // CreateMap<Employee,EmployeeViewModel>();
            CreateMap<Employee, EmployeeViewModel>()
                .ForMember(dest=>dest.EmpName,opt=>opt.MapFrom(src=>src.EmpName))
                .ForMember(dest => dest.DeptName, opt => opt.MapFrom(src => src.DeptName))
                .ForMember(dest => dest.Designation, opt => opt.MapFrom(src => src.Designation))
                .ForMember(dest => dest.Address, opt => opt.MapFrom(src=>src.Address.ToString()));
        }
    }
}

Listing 6: The Profile class for Mapper

As shown in Listing 6 the properties of the Employee class are mapped with the EmployeeViewModel class. If the property names of both source and destination classes are the same with types then it is not necessary to write the code for mapping using ForMember() method. In our case, the Address property has different types in both the source and the destination classes that is why we have used the ForMember() method to define the mapping configuration for properties. 

Step 7: Modify the code in the Program.cs to register the AutoMapper and the EmployeeService in the Dependency container as shown in Listing 7


builder.Services.AddAutoMapper(typeof(Program));
builder.Services.AddScoped<EmployeeService>();

Listing 7: The AutoMappper registration    

Step 8: In the Controllers folder, add a new empty API Controller and name it as EmployeeController. In this controller, we will inject the IMapper interface using constructor injection to use the mapped object. This interface has the Map() method. This method is used to execute the mapping from the source object to the destination object. The code for the controller is shown in Listing 8


using AutoMapper;
using Core_AutoMapper.Services;
using Core_AutoMapper.ViewModels;
using Microsoft.AspNetCore.Mvc;

namespace Core_AutoMapper.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class EmployeeController : ControllerBase
    {
        IMapper mapper;
        EmployeeService service;
        List<EmployeeViewModel> records;

        public EmployeeController(IMapper mapper, EmployeeService service)
        {
            this.mapper = mapper;
            this.service = service;
            records = new List<EmployeeViewModel>();
        }

        [HttpGet]
        public IActionResult Get() 
        {
            var employees = service.GetEmoployees();
            records = mapper.Map<List<EmployeeViewModel>>(employees);
            return Ok(records);
        }
    }
}

Listing 8: The EmployeeController class

As shown in Listing 8, the EmployeeController class contains the Get() method, this method contains code to read Employee data using the GetEmployees() method from the EmployeeService class. Once the Employee data is available it is mapped with the EmployeeViewModel List class and returned into the response. This approach prevents writing the sensitive data of the Employee e.g. Salary into the response. One more point is the Address is written in the string format instead of the Address type object.

Run the application and make the HTTP GET request the Employee data will be returned as shown in Figure 1




Figure 1: The Employee Response using the Mapped object


The code for this article can be read from this link.

Conclusion: The AutoMapper is the best feature to prevent important information from the domain object from being sent to the client app.

     


Popular posts from this blog

Uploading Excel File to ASP.NET Core 6 application to save data from Excel to SQL Server Database

ASP.NET Core 6: Downloading Files from the Server

ASP.NET Core 6: Using Entity Framework Core with Oracle Database with Code-First Approach