Back to: ASP.NET Core Web API Tutorials
AutoMapper Reverse Mapping in ASP.NET Core Web API
In this article, I will discuss How to Implement AutoMapper Reverse Mapping in ASP.NET Core Web API Application with Examples. Please read our previous article discussing Mapping Complex Type to Primitive Type using AutoMapper in ASP.NET Core Web API with Examples.
AutoMapper Reverse Mapping in ASP.NET Core Web API
AutoMapper is a widely used object-to-object mapping library in .NET that helps streamline the process of mapping properties between different objects, such as entity models and Data Transfer Objects (DTOs). It significantly reduces the amount of repetitive code needed for manual property assignments. Reverse mapping is a feature in AutoMapper that enables two-way mapping between objects without requiring separate configurations for each direction. This is extremely useful in ASP.NET Core Web API applications where we often need to:
- Map our EF Core entities to DTOs when sending data to the client.
- When creating or updating data, map incoming DTOs back to our EF Core entities.
When we create a mapping between two classes (e.g., an Entity and a DTO), we typically define the mapping in one direction: CreateMap<Employee, EmployeeDTO>(); If we want to map back (i.e., from EmployeeDTO to Employee), we have two options:
- Option1: Create another mapping configuration explicitly: CreateMap<EmployeeDTO, Employee>();
- Option2: Use AutoMapper’s Reverse Mapping feature by appending .ReverseMap() to your mapping configuration: CreateMap<Employee, EmployeeDTO>().ReverseMap();
The ReverseMap() method automatically creates the reverse mapping for us. This simplifies our configuration, reduces code duplication, and ensures that both mappings remain consistent.
Example to Understand AutoMapper Reverse Mapping in ASP.NET Core Web API:
Let’s understand the AutoMapper Reverse Mapping in ASP.NET Core Web API with one real-time example. We will create a simple employee management system with the following classes:
- Address Model: Represents the address details of an employee.
- Employee Model: Represents the employee and includes personal information.
- EmployeeDTO: Combining properties from the Employee and Address models for client-server data transfer.
Setting Up the Project and Installing Entity Framework Core and Automapper
First, create a new ASP.NET Core Web API Application named AutomapperDemo. Once you create the project, please install the Entity Framework Core and Automapper Packages by executing the following command in the Package Manager Console.
- Install-Package Microsoft.EntityFrameworkCore.SqlServer
- Install-Package Microsoft.EntityFrameworkCore.Tools
- Install-Package AutoMapper.Extensions.Microsoft.DependencyInjection
Defining Entities and DTOs:
First, create two folders named Models and DTOs in the project root directory, where we will create all our Entities and DTOs.
Address Model
Create a class file named Address.cs within the Models folder and add the following code. This class represents the address details of an employee.
namespace AutomapperDemo.Models { public class Address { public int Id { get; set; } // Unique identifier for the address public string City { get; set; } // City of the address public string State { get; set; } // State of the address public string Country { get; set; } // Country of the address public int EmployeeId { get; set; } // Foreign key to Employee public Employee Employee { get; set; } // Navigation property for Employee } }
Employee Model:
Create a class file named Employee.cs within the Models folder and add the following code. This class represents the employee entity with personal and address details.
namespace AutomapperDemo.Models { public class Employee { public int Id { get; set; } // Primary key for the employee public string FirstName { get; set; } // Employee's first name public string LastName { get; set; } // Employee's last name public string Email { get; set; } // Employee's email address public string Gender { get; set; } // Employee's gender public Address Address { get; set; } // Navigation property to the Address entity } }
EmployeeDTO Model
Create a class file named EmployeeDTO.cs within the DTOs folder, then add the following code. This Data Transfer Object combines properties from both Employee and Address for client-server communication.
namespace AutomapperDemo.DTOs { public class EmployeeDTO { public int EmployeeId { get; set; } // Mapped from Employee.Id public string FullName { get; set; } // Combined first and last names public string Email { get; set; } // Employee's email address public string Gender { get; set; } // Employee's gender // Address properties extracted from the Address model public string City { get; set; } public string State { get; set; } public string Country { get; set; } } }
Create a DbContext Class
First, create a folder called Data, and then inside the Data folder, create a class file named EmployeeDBContext.cs, then copy and paste the following code. Here, we have also added some initial seed data for testing purposes.
using AutomapperDemo.Models; using Microsoft.EntityFrameworkCore; namespace AutomapperDemo.Data { public class EmployeeDBContext : DbContext { public EmployeeDBContext(DbContextOptions<EmployeeDBContext> options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { // Seed Employee data modelBuilder.Entity<Employee>().HasData( new Employee { Id = 1, FirstName = "Anurag", LastName = "Mohanty", Email = "anurag.mohanty@example.com", Gender = "Male" }, new Employee { Id = 2, FirstName = "Pranaya", LastName = "Rout", Email = "pranaya.rout@example.com", Gender = "Male" } ); // Seed Address data linked to employees modelBuilder.Entity<Address>().HasData( new Address { Id = 1, City = "Bhubaneswar", State = "Odisha", Country = "India", EmployeeId = 1 }, new Address { Id = 2, City = "Mumbai", State = "Maharashtra", Country = "India", EmployeeId = 2 } ); } // Define DbSets for Employee and Address public DbSet<Employee> Employees { get; set; } public DbSet<Address> Addresses { get; set; } } }
Configure the Database Connection
Next, please update the appsettings.json with the database connection string as follows:
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "ConnectionStrings": { "EmployeeDBConnection": "Server=LAPTOP-6P5NK25R\\SQLSERVER2022DEV;Database=EmployeeDB;Trusted_Connection=True;TrustServerCertificate=True;" } }
Set Up AutoMapper, DbContext, and Middleware:
Please modify the Program class as follows.
using AutomapperDemo.Data; using Microsoft.EntityFrameworkCore; namespace AutomapperDemo { public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); // Register AutoMapper and the mapping profile builder.Services.AddAutoMapper(typeof(Program).Assembly); // Add services to the container. builder.Services.AddControllers() // Optionally, configure JSON options or other formatter settings .AddJsonOptions(options => { // Configure JSON serializer settings to keep the Original names in serialization and deserialization options.JsonSerializerOptions.PropertyNamingPolicy = null; }); // Register the EmployeeDBContext with dependency injection builder.Services.AddDbContext<EmployeeDBContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("EmployeeDBConnection"))); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run(); } } }
Creating and Applying Database Migration:
Open the Package Manager Console and Execute the Add-Migration and Update-Database commands as follows to generate the Migration file and then apply the Migration file to create the EmployeeDB database and required Employees and Addresses tables:
Once you execute the above commands and verify the database, you should see the EmployeeDB database with the required Employees and Addresses tables, as shown in the image below.
Configure AutoMapper with Reverse Mapping in ASP.NET Core Web API
Create a mapping profile that defines the transformation rules between Employee and EmployeeDTO and vice versa. This allows us to map an Employee object to EmployeeDTO and vice versa. First, create a folder named MappingProfiles, and inside this folder, create a class file named EmployeeMappingProfile.cs and add the following code:
using AutoMapper; using AutomapperDemo.DTOs; using AutomapperDemo.Models; namespace AutomapperDemo.MappingProfiles { public class EmployeeMappingProfile : Profile { public EmployeeMappingProfile() { // Define two-way mapping between Employee and EmployeeDTO. // This maps Employee.Id to EmployeeDTO.EmployeeId, combines FirstName and LastName into FullName, // and maps the Address properties to their corresponding DTO fields. CreateMap<Employee, EmployeeDTO>() .ForMember(dest => dest.EmployeeId, opt => opt.MapFrom(src => src.Id)) .ForMember(dest => dest.FullName, opt => opt.MapFrom(src => $"{src.FirstName} {src.LastName}")) .ForMember(dest => dest.City, opt => opt.MapFrom(src => src.Address.City)) .ForMember(dest => dest.State, opt => opt.MapFrom(src => src.Address.State)) .ForMember(dest => dest.Country, opt => opt.MapFrom(src => src.Address.Country)) // Enable reverse mapping: this automatically creates the mapping from EmployeeDTO to Employee .ReverseMap() // When mapping back, split the FullName into FirstName and LastName. .ForMember(dest => dest.FirstName, opt => opt.MapFrom(src => GetFirstName(src.FullName))) .ForMember(dest => dest.LastName, opt => opt.MapFrom(src => GetLastName(src.FullName))); } // Extracts the first name from FullName. private static string GetFirstName(string fullName) { if (string.IsNullOrWhiteSpace(fullName)) return string.Empty; var names = fullName.Split(' '); return names.FirstOrDefault() ?? string.Empty; } // Extracts the last name from FullName. private static string GetLastName(string fullName) { if (string.IsNullOrWhiteSpace(fullName)) return string.Empty; var names = fullName.Split(' '); // If there is more than one word, combine the rest as the last name return names.Length > 1 ? string.Join(" ", names.Skip(1)) : string.Empty; } } }
Code Explanations:
- Forward Mapping: Converts Employee to EmployeeDTO, including combining first and last names.
- Reverse Mapping: Converts EmployeeDTO back to Employee and splits the full name into first and last names using helper methods. Using ReverseMap() avoids defining a second, separate mapping.
Using Automapper Reverse Mapping in a Controller
Implement a controller that uses AutoMapper’s reverse mapping capabilities. This controller will handle CRUD operations for employees, demonstrating both forward and reverse mappings. So, create a controller named EmployeesController within the Controllers folder and add the following code:
using AutoMapper; using AutomapperDemo.Data; using AutomapperDemo.DTOs; using AutomapperDemo.Models; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; namespace AutomapperDemo.Controllers { [Route("api/[controller]")] [ApiController] public class EmployeesController : ControllerBase { private readonly IMapper _mapper; // AutoMapper instance for mapping objects private readonly EmployeeDBContext _context; // EF Core database context public EmployeesController(IMapper mapper, EmployeeDBContext context) { _mapper = mapper; _context = context; } // GET: api/Employees/GetEmployees // Retrieves all employees with their address details [HttpGet("GetEmployees")] public async Task<ActionResult<IEnumerable<EmployeeDTO>>> GetEmployees() { // Retrieve employees including their related Address data var employees = await _context.Employees .AsNoTracking() .Include(e => e.Address) .ToListAsync(); // Map the list of Employee entities to a list of EmployeeDTOs var employeeDTOs = _mapper.Map<List<EmployeeDTO>>(employees); return Ok(employeeDTOs); } // GET: api/Employees/GetEmployee/1 // Retrieves a single employee by ID with address details [HttpGet("GetEmployee/{id}")] public async Task<ActionResult<EmployeeDTO>> GetEmployee(int id) { // Find employee by ID and include Address data var employee = await _context.Employees .AsNoTracking() .Include(e => e.Address) .FirstOrDefaultAsync(e => e.Id == id); if (employee == null) { return NotFound($"Employee with ID {id} not found."); } // Map the found Employee entity to EmployeeDTO var employeeDTO = _mapper.Map<EmployeeDTO>(employee); return Ok(employeeDTO); } // POST: api/Employees/AddEmployee // Creates a new employee record from the provided EmployeeDTO [HttpPost("AddEmployee")] public async Task<ActionResult<EmployeeDTO>> AddEmployee(EmployeeDTO employeeDTO) { if (employeeDTO == null) { return BadRequest("Employee data is null."); } // Convert the incoming EmployeeDTO to an Employee entity var employee = _mapper.Map<Employee>(employeeDTO); // Add the new employee to the database _context.Employees.Add(employee); await _context.SaveChangesAsync(); // Map the newly created Employee entity back to EmployeeDTO (includes generated ID) var createdEmployeeDTO = _mapper.Map<EmployeeDTO>(employee); return Ok(createdEmployeeDTO); } // PUT: api/Employees/UpdateEmployee/1 // Updates an existing employee's details based on the provided EmployeeDTO [HttpPut("UpdateEmployee/{id}")] public async Task<ActionResult<EmployeeDTO>> UpdateEmployee(int id, EmployeeDTO employeeDTO) { if (id != employeeDTO.EmployeeId) { return BadRequest("Employee ID mismatch."); } // Retrieve the existing employee record including its Address var existingEmployee = await _context.Employees .Include(e => e.Address) .FirstOrDefaultAsync(e => e.Id == id); if (existingEmployee == null) { return NotFound($"Employee with ID {id} not found."); } // Map updated values from the DTO to the existing Employee entity _mapper.Map(employeeDTO, existingEmployee); try { // Save the updated record in the database await _context.SaveChangesAsync(); } catch (DbUpdateConcurrencyException ex) { if (!EmployeeExists(id)) { return NotFound($"Employee with ID {id} no longer exists."); } else { // Return a 500 status with an error message on exception return StatusCode(500, $"An error occurred: {ex.Message}"); } } // Map the updated Employee entity back to EmployeeDTO and return it var updatedEmployeeDTO = _mapper.Map<EmployeeDTO>(existingEmployee); return Ok(updatedEmployeeDTO); } // Helper method to check if an employee exists in the database private bool EmployeeExists(int id) { return _context.Employees.Any(e => e.Id == id); } } }
Testing the API Endpoints Using Postman
Now, run the application and test the functionality, and it should work as expected. First, run the application, and then you can test the endpoints as follows:
GET All Employees:
Method: GET
URL: https://localhost:<PORT>/api/Employees/GetEmployees
Expected Response: You will receive a JSON array of employees with their address details.
GET a Single Employee:
Method: GET
URL: https://localhost:<PORT>/api/Employees/GetEmployee/1
Expected Response: If the employee does not exist, you will receive a 404 response. If the employee exists, you will get 200 Ok Responses.
POST a New Employee:
Method: POST
Headers: Content-Type: application/json
URL: https://localhost:<PORT>/api/Employees/AddEmployee
Body: (Raw JSON)
{ "FullName": "John Doe", "Email": "john.doe@example.com", "Gender": "Male", "City": "New York", "State": "NY", "Country": "USA" }
Expected Response: You should receive a response with status 200 (Ok) and the created employee data.
PUT (Update) an Existing Employee:
Method: PUT
Headers: Content-Type: application/json
URL: https://localhost:<PORT>/api/Employees/UpdateEmployee/1
Body:(Raw JSON)
{ "EmployeeId": 1, "FullName": "John A. Doe", "Email": "john.a.doe@example.com", "Gender": "Male", "City": "Los Angeles", "State": "CA", "Country": "USA" }
Expected Response: The response should return the updated employee details with a 200 (OK) status.
When Should We Use AutoMapper Reverse Mapping in ASP.NET Core Web API?
We need to use AutoMapper Reverse Mapping for a two-way transformation between two models. Some common scenarios include:
- Two-Way Data Flow: You want to expose data via a GET endpoint (mapping Entity → DTO) and Accept updates or new entity data via POST/PUT endpoints (mapping DTO → Entity).
- Minimize Mapping Definitions: If your entity and DTO properties are almost the same (with some minor differences in naming or structure), ReverseMap() keeps the configuration in a single place instead of writing separate CreateMap<>() calls for both directions.
- Consistency and Maintainability: When you add or remove properties from your entity, using ReverseMap() helps you quickly notice where changes might affect both directions of the map.
AutoMapper ReverseMap() allows you to maintain a single mapping configuration to handle Entity → DTO and DTO → Entity transformations. This approach simplifies your code, reduces duplication, and ensures that both directions stay in sync. It is particularly useful in scenarios where objects need to be transformed both ways (e.g., API input and output). Using ReverseMap() eliminates redundant mapping configurations and maintains consistency across transformations.
In the next article, I will discuss How to Implement AutoMapper Conditional Mapping in ASP.NET Core Web API with Examples. In this article, I explain AutoMapper Reverse Mapping in ASP.NET Core Web API with Examples. I hope you enjoy this article, “AutoMapper Reverse Mapping in ASP.NET Core Web API.”