When building full-stack applications with ASP.NET Core and Angular (or any frontend), you often deal with DTOs (Data Transfer Objects), ViewModels, and Entities. Mapping data between these objects manually can be repetitive, error-prone, and messy.

AutoMapper is a library that simplifies object-to-object mapping in ASP.NET Core. It automatically maps properties between objects with minimal configuration, making your backend code cleaner and maintainable.

This article explains how to use AutoMapper step by step in a full-stack application scenario.

What You Will Learn

Step 1. Why Use AutoMapper

Suppose you have a User entity in your database:

Code
public class User
{
    public int Id { get; set; }
    public string FullName { get; set; }
    public string Email { get; set; }
    public string PasswordHash { get; set; }
}

You don’t want to send the PasswordHash to the frontend. You may create a UserDto:

Code
public class UserDto
{
    public string FullName { get; set; }
    public string Email { get; set; }
}

Without AutoMapper, you need to manually map:

Code
var userDto = new UserDto
{
    FullName = user.FullName,
    Email = user.Email
};

For large objects or multiple endpoints, this becomes tedious. AutoMapper handles this automatically.

Part 1. Install Automapper

Run this command in your ASP.NET Core project:

Code
dotnet add package AutoMapper.Extensions.Microsoft.DependencyInjection

Part 2. Create DTOs (Data Transfer Objects)

DTOs are lightweight objects that contain only the data you want to expose to the frontend.

Example

Code
public class UserDto
{
    public string FullName { get; set; }
    public string Email { get; set; }
}

public class CreateUserDto
{
    public string FullName { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }
}

Part 3. Configure Automapper

Create a Mapping Profile:

MappingProfile.cs

Code
using AutoMapper;

public class MappingProfile : Profile
{
    public MappingProfile()
    {
        // Map User -> UserDto
        CreateMap<User, UserDto>();

        // Map CreateUserDto -> User
        CreateMap<CreateUserDto, User>()
            .ForMember(dest => dest.PasswordHash, opt => opt.MapFrom(src => BCrypt.Net.BCrypt.HashPassword(src.Password)));
    }
}

Step 2: Register AutoMapper in Startup

Program.cs (ASP.NET Core 6/7+)

Code
using AutoMapper;

var builder = WebApplication.CreateBuilder(args);

// Add AutoMapper
builder.Services.AddAutoMapper(typeof(Program));

// Add other services
builder.Services.AddControllers();
builder.Services.AddDbContext<AppDbContext>(...);

var app = builder.Build();
app.MapControllers();
app.Run();

Part 4. Using Automapper in Controllers

Inject IMapper into your controller:

Code
using AutoMapper;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    private readonly AppDbContext _context;
    private readonly IMapper _mapper;

    public UsersController(AppDbContext context, IMapper mapper)
    {
        _context = context;
        _mapper = mapper;
    }

    // Get all users
    [HttpGet]
    public IActionResult GetUsers()
    {
        var users = _context.Users.ToList();
        var usersDto = _mapper.Map<List<UserDto>>(users);
        return Ok(usersDto);
    }

    // Create new user
    [HttpPost]
    public IActionResult CreateUser([FromBody] CreateUserDto createUserDto)
    {
        var user = _mapper.Map<User>(createUserDto);
        _context.Users.Add(user);
        _context.SaveChanges();

        var userDto = _mapper.Map<UserDto>(user);
        return Ok(userDto);
    }
}

Part 5. Benefits in Full-Stack Developement

  1. Cleaner backend code
    AutoMapper removes repetitive manual mapping, making your controllers concise.

  2. Consistent data shaping
    You can ensure all endpoints return consistent DTOs.

  3. Separation of concerns
    Entities remain database-focused; DTOs remain API-focused.

  4. Security
    You can exclude sensitive fields like PasswordHash without manually removing them.

  5. Easy maintenance
    Changing property names in the database requires only updating the mapping profile.

Part 6. Integrating with Angular

Suppose you have an Angular frontend:

Service to get users

Code
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class UserService {
  constructor(private http: HttpClient) {}

  getUsers() {
    return this.http.get<User[]>('https://localhost:5001/api/users');
  }

  createUser(user: any) {
    return this.http.post<User>('https://localhost:5001/api/users', user);
  }
}

Component to display users

Code
export class UsersComponent implements OnInit {
  users: any[] = [];

  constructor(private userService: UserService) {}

  ngOnInit() {
    this.userService.getUsers().subscribe((res: any) => this.users = res);
  }
}

Because the backend uses AutoMapper to exclude sensitive fields, the frontend receives only the data it needs.

Part 7. Advanced mapping Techniques

1. Flattening Nested Objects

Code
CreateMap<User, UserDto>()
    .ForMember(dest => dest.RoleName, opt => opt.MapFrom(src => src.Role.Name));

2. Conditional Mapping

Code
CreateMap<User, UserDto>()
    .ForMember(dest => dest.Email, opt => opt.Condition(src => !string.IsNullOrEmpty(src.Email)));

3. Ignore Certain Properties

Code
CreateMap<CreateUserDto, User>()
    .ForMember(dest => dest.Id, opt => opt.Ignore());

Part 8. Best Practices

  1. Use DTOs for all API responses

  2. Keep MappingProfiles organized by domain

  3. Avoid mapping large lists inside controller; map in services

  4. Use Flattening and Conditional mapping for complex objects

  5. Test your mappings with Unit Tests

Conclusion

Using AutoMapper in ASP.NET Core helps you:

For full-stack developers using Angular or React, AutoMapper ensures that your API consistently sends clean, secure, and structured data.