DEV Community

Cover image for "Mastering AutoMapper: Simplifying Object Mapping in .NET"
Abayomi Ogunnusi
Abayomi Ogunnusi

Posted on

"Mastering AutoMapper: Simplifying Object Mapping in .NET"

Hello folks today we will be doing a walk-through on how to use AutoMapper in a dotnet web application, and its benefit over traditional mapping.

Ingredients
Dotnet Core SDK

Prerequisite
🏝️ Basic Knowledge of C#
🏝️ How to use the terminal

Agenda
🪢 Introduction: what is Automapper
🪢 Domain models and our view models
🪢 Create a new C# project in Rider.
🪢 Manual Mapping in Action
🪢 Automapper in Action

What is an AutoMapper

AutoMapper is a simple little library built to solve a deceptively complex problem - getting rid of code that mapped one object to another. reference

Essentially, an AutoMapper helps to map our domain models to our view models. We can use AutoMapper in our controller or in a separate helper class.

Domain models and our view models

  • Domain models are the models that represent the data in our database.
  • View models are the models that represent the data that we want to show to the user. e.g the data transfer objects (DTOs) that we use in our API.
Example

The User.cs class below is the internal representation of the data in our database.

public class User
{
    public int Id { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }
    public DateTime DateOfBirth { get; set; }
    public int Age { get; set; }
    public string KnownAs { get; set; }
    public DateTime Created { get; set; } = DateTime.Now;
    public DateTime LastActive { get; set; } = DateTime.Now;

}
Enter fullscreen mode Exit fullscreen mode
View models example

Next we have the UserForListDto.cs class below. This is the model that we want to send to the client. It contains only the properties that we want to send to the client.

public class UserForListDto
{
    public int Id { get; set; }
    public string Username { get; set; }
    public int Age { get; set; }
    public string KnownAs { get; set; }
}
Enter fullscreen mode Exit fullscreen mode
UserCreateDto.cs

This is the model that we want to receive from the client for creating a new user.

public class UserCreateDto
{
    public string Username { get; set; }
    public string Password { get; set; }
    public DateTime DateOfBirth { get; set; }
    public string KnownAs { get; set; }
    public int Age { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Let's create a new Dotnet Core API project to demonstrate how to map our domain models to our view models manually.

Create a new Dotnet Core API project

Image description

-o is the output directory that we want to create the project in.

Open the project in your favorite code editor. I am using JetBrains Rider.

Image description

Create a new folder called Models
mkdir Models
Enter fullscreen mode Exit fullscreen mode

Then create the User.cs in the Models folder.

touch Models/User.cs
Enter fullscreen mode Exit fullscreen mode
User.cs
public class User
{
    public int Id { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }
    public DateTime DateOfBirth { get; set; }
    public int Age { get; set; }
    public string KnownAs { get; set; }
    public DateTime Created { get; set; } = DateTime.Now;

}
Enter fullscreen mode Exit fullscreen mode
Create a new folder called Dtos
mkdir Dtos
Enter fullscreen mode Exit fullscreen mode

Then create the UserResponse.cs in the Dtos folder.

touch Dtos/UserResponse.cs
Enter fullscreen mode Exit fullscreen mode
UserResponse.cs

For Read operations, we want to send the UserResponse model to the client. This model contains only the properties that we want to send to the client.

public class UserResponse
{
   public int Id { get; set; }
    public string Username { get; set; } = String.Empty;
    public int Age { get; set; }
    public string KnownAs { get; set; } = String.Empty;
}
Enter fullscreen mode Exit fullscreen mode
Dtos/CreateUserDto.cs

For Create operations, we want to receive the CreateUserDto model from the client. This model contains only the properties that we want to receive from the client.

public class CreateUserDto
{
    public string Username { get; set; } = String.Empty;
    public string Password { get; set; } = String.Empty;
    public DateTime DateOfBirth { get; set; }
    public string KnownAs { get; set; } = String.Empty;
    public int Age { get; set; }
}
Enter fullscreen mode Exit fullscreen mode
Create a new folder called Data
mkdir Data
Enter fullscreen mode Exit fullscreen mode

Then create the DataContext.cs in the Data folder.

touch Data/DataContext.cs
Enter fullscreen mode Exit fullscreen mode
DataContext.cs

DataContext.cs bridges the gap between our application and the database.

public class DataContext : DbContext
{
    public DataContext(DbContextOptions<DataContext> options) : base(options)
    {
    }

    public DbSet<User> Users { get; set; }
}
Enter fullscreen mode Exit fullscreen mode
Let's install the packages that we need
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package AutoMapper.Extensions.Microsoft.DependencyInjection
Enter fullscreen mode Exit fullscreen mode

Check the .csproj file to ensure the packages were installed

Image description

Next we can import the missing references

Image description

Create a new folder called Repositories
mkdir Repositories
Enter fullscreen mode Exit fullscreen mode

Then create the IAuthRepository.cs in the Repositories folder.

touch Repositories/IAuthRepository.cs
Enter fullscreen mode Exit fullscreen mode
IAuthRepository.cs
public interface IAuthRepository
{
    Task<UserResponse> Register(CreateUserDto user);
}
Enter fullscreen mode Exit fullscreen mode
Create a new folder called Repositories
mkdir Repositories
Enter fullscreen mode Exit fullscreen mode

Then create the AuthRepository.cs in the Repositories folder.

touch Repositories/AuthRepository.cs
Enter fullscreen mode Exit fullscreen mode
Mapping without AutoMapper

public class AuthRepository: IAuthRepository
{
    private readonly DataContext _context;

    public AuthRepository(DataContext context)
    {
        _context = context;
    }

    public async Task<UserResponse> Register(CreateUserDto user)
    {
        // insert the user into the database from the CreateUserDto
        var userToCreate = new User
        {
            Username = user.Username,
            DateOfBirth = user.DateOfBirth,
            KnownAs = user.KnownAs,
            Age = user.Age
        };

        // save the user to the database
        var createdUser = await _context.Users.AddAsync(userToCreate);
        // save the changes to the database
        await _context.SaveChangesAsync();

        // return the user to the client in the UserResponse format
        return new UserResponse
        {
            Id = createdUser.Entity.Id,
            Username = createdUser.Entity.Username,
            Age = createdUser.Entity.Age,
            KnownAs = createdUser.Entity.KnownAs
        };
    }
}
Enter fullscreen mode Exit fullscreen mode
Let's the AuthController.cs
touch Controllers/AuthController.cs
Enter fullscreen mode Exit fullscreen mode
AuthController.cs
[Route("api/[controller]")]
[ApiController]
public class AuthController : ControllerBase
{
    private readonly IAuthRepository _repo;

    public AuthController(IAuthRepository repo)
    {
        _repo = repo;
    }

    [HttpPost("register")]
    public async Task<IActionResult> Register(CreateUserDto user)
    {
        var createdUser = await _repo.Register(user);

        return Ok(createdUser);
    }
}
Enter fullscreen mode Exit fullscreen mode
appsettings.json

Let's add our connection string to the appsettings.json file.

appsettings.json

Let's add our connection string to the appsettings.json file.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "DefaultConnection": "Server=localhost,1433;Database=bookstoredb;User Id=SA;Password=YourPassword;Encrypt=false;TrustServerCertificate=True;"
  }
}

Enter fullscreen mode Exit fullscreen mode
Program.cs

Next, let's add our connection string to the Program.cs file and add the DataContext to the DI container.

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");

// DI for DataContext
builder.Services.AddDbContext<DataContext>(options =>
    options.UseSqlServer(connectionString));

// DI for Interfaces and Implementations
builder.Services.AddScoped<IAuthRepository, AuthRepository>();

builder.Services.AddControllers();
// 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();
Enter fullscreen mode Exit fullscreen mode
Run migrations
dotnet ef migrations add InitialCreate
Enter fullscreen mode Exit fullscreen mode

Image description

dotnet ef database update
Enter fullscreen mode Exit fullscreen mode

Image description

Run the project
dotnet run
Enter fullscreen mode Exit fullscreen mode

Image description

open the swagger ui

http://localhost:5108/swagger/index.html
Image description

We only exposed the data we want to the client to see coming from the view model called CreateUserDto we created earlier.

Let fill our payload

Image description

We get a 200 Ok Response and Only exposed the Data coming from our UserResponse.cs class
Image description

Mapping with AutoMapper

Let's create a helper class called MappingProfile.cs in the Helpers folder.

touch Helpers/MappingProfile.cs
Enter fullscreen mode Exit fullscreen mode
MappingProfile.cs
public class MappingProfile : Profile
{
    public MappingProfile()
    {
        CreateMap<User, UserResponse>();
        CreateMap<CreateUserDto, User>();
    }
}
Enter fullscreen mode Exit fullscreen mode
Let's explain the code above.
  • The CreateMap<User, UserResponse>(); maps the User model (the source) to the UserResponse model (the destination). i.e we want to convert the User model to the UserResponse model before we send it to the client.
  • The CreateMap<CreateUserDto, User>(); maps the CreateUserDto model (the source) to the User model (the destination). i.e we want to convert the CreateUserDto model to the User model before we save it to the database.
Let's update the AuthRepository.cs
public class AuthRepository : IAuthRepository
{
    private readonly DataContext _context;
    private readonly IMapper _mapper;

    public AuthRepository(DataContext context, IMapper mapper)
    {
        _context = context;
        _mapper = mapper;
    }

    public async Task<UserResponse> Register(CreateUserDto user)
    {
        // insert the user into the database from the CreateUserDto
        var userToCreate = _mapper.Map<User>(user); // map the CreateUserDto(user) to the User model in the <User> format

        // save the user to the database
        var createdUser = await _context.Users.AddAsync(userToCreate);
        // save the changes to the database
        await _context.SaveChangesAsync();

        // return the user to the client in the UserResponse format
        return _mapper.Map<UserResponse>(createdUser.Entity);
    }
}
Enter fullscreen mode Exit fullscreen mode

NOTE: We don't need to update the AuthController.cs because we are using the DI container to inject the IMapper into the AuthRepository.cs

Let's configure AutoMapper in the DI container
var builder = WebApplication.CreateBuilder(args);
//..

// DI for AutoMapper
builder.Services.AddAutoMapper(typeof(MappingProfile));

//..
Enter fullscreen mode Exit fullscreen mode
Run the project
dotnet run
Enter fullscreen mode Exit fullscreen mode
open the swagger ui

http://localhost:5108/swagger/index.html

We got the same result, but this time with fewer lines and more organised code

Image description

Egde cases
  • What if we want to map a property to a different property name?
  • What if we want to map a property to a different property type?

Let's assume the UserResponse model has a property called ProfilePictureUrl and the User model has a property called PhotoUrl. We want to map the PhotoUrl property to the ProfilePictureUrl property.

UserResponse.cs
public class UserResponse
{
    public int Id { get; set; }
    public string Username { get; set; } = String.Empty;
    public int Age { get; set; }
    public string KnownAs { get; set; } = String.Empty;
    public string ProfilePictureUrl { get; set; } = String.Empty;
}
Enter fullscreen mode Exit fullscreen mode
User.cs
public class User
{
    public int Id { get; set; }
    public string Username { get; set; } = String.Empty;
    public string Password { get; set; } = String.Empty;
    public DateTime DateOfBirth { get; set; }
    public int Age { get; set; }
    public string KnownAs { get; set; } = String.Empty;
    public DateTime Created { get; set; } = DateTime.Now;
    public string PhotoUrl { get; set; } = String.Empty;
}
Enter fullscreen mode Exit fullscreen mode

Note: Ensure you run the migrations and update the database before you continue.

MappingProfile.cs
public class MappingProfile : Profile
{
    public MappingProfile()
    {
        CreateMap<User, UserResponse>()
            .ForMember(dest => dest.ProfilePictureUrl, opt =>
                opt.MapFrom(src => src.PhotoUrl));
        CreateMap<CreateUserDto, User>();
    }
}
Enter fullscreen mode Exit fullscreen mode

Explanation: We are mapping the PhotoUrl property to the ProfilePictureUrl property using the ForMember method.
Basically, we are saying map the PhotoUrl property from the source(User) to the ProfilePictureUrl property in the destination(UserResponse

Run the project
dotnet run
Enter fullscreen mode Exit fullscreen mode

Image description
Notice we have the PhotoUrl property in the request body. But we want to map the PhotoUrl property to the ProfilePictureUrl property in the UserResponse model.

Image description

Conclusion

In this article, we have seen how to map our domain models to our view models using AutoMapper. We have also seen how to map a property to a different property name and how to map a property to a different property type. I hope you have learned something new. Thanks for reading.

Top comments (2)

Collapse
 
yogini16 profile image
yogini16

Nice step by step explanation with short and clear examples !!
Thanks you

Collapse
 
drsimplegraffiti profile image
Abayomi Ogunnusi

@yogini16 Glad you found it useful