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 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
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
}
}
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 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
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
}
}
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 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; }
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; }
}
}
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)
protected override void OnModelCreating(ModelBuilder modelBuilder)
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; }
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; }
}
}
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:
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
"EmployeeDBConnection": "Server=LAPTOP-6P5NK25R\\SQLSERVER2022DEV;Database=EmployeeDB;Trusted_Connection=True;TrustServerCertificate=True;"
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"EmployeeDBConnection": "Server=LAPTOP-6P5NK25R\\SQLSERVER2022DEV;Database=EmployeeDB;Trusted_Connection=True;TrustServerCertificate=True;"
}
}
{
"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;
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.UseHttpsRedirection();
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();
}
}
}
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 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
// 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))
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))
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;
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;
}
}
}
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 AutomapperDemo.Data;
using AutomapperDemo.DTOs;
using AutomapperDemo.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace AutomapperDemo.Controllers
[Route("api/[controller]")]
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)
// 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
// Map the list of Employee entities to a list of EmployeeDTOs
var employeeDTOs = _mapper.Map<List<EmployeeDTO>>(employees);
// 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
.FirstOrDefaultAsync(e => e.Id == id);
return NotFound($"Employee with ID {id} not found.");
// Map the found Employee entity to EmployeeDTO
var employeeDTO = _mapper.Map<EmployeeDTO>(employee);
// POST: api/Employees/AddEmployee
// Creates a new employee record from the provided EmployeeDTO
[HttpPost("AddEmployee")]
public async Task<ActionResult<EmployeeDTO>> AddEmployee(EmployeeDTO employeeDTO)
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
.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);
// Save the updated record in the database
await _context.SaveChangesAsync();
catch (DbUpdateConcurrencyException ex)
return NotFound($"Employee with ID {id} no longer exists.");
// 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);
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);
}
}
}
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)
"Email": "john.doe@example.com",
{
"FullName": "John Doe",
"Email": "john.doe@example.com",
"Gender": "Male",
"City": "New York",
"State": "NY",
"Country": "USA"
}
{
"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)
"FullName": "John A. Doe",
"Email": "john.a.doe@example.com",
{
"EmployeeId": 1,
"FullName": "John A. Doe",
"Email": "john.a.doe@example.com",
"Gender": "Male",
"City": "Los Angeles",
"State": "CA",
"Country": "USA"
}
{
"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.”
About the Author: Pranaya Rout
Pranaya Rout has published more than 3,000 articles in his 11-year career. Pranaya Rout has very good experience with Microsoft Technologies, Including C#, VB, ASP.NET MVC, ASP.NET Web API, EF, EF Core, ADO.NET, LINQ, SQL Server, MYSQL, Oracle, ASP.NET Core, Cloud Computing, Microservices, Design Patterns and still learning new technologies.