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;
}
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; }
}
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; }
}
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
-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.
Create a new folder called Models
mkdir Models
Then create the User.cs in the Models folder.
touch Models/User.cs
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;
}
Create a new folder called Dtos
mkdir Dtos
Then create the UserResponse.cs in the Dtos folder.
touch Dtos/UserResponse.cs
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;
}
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; }
}
Create a new folder called Data
mkdir Data
Then create the DataContext.cs in the Data folder.
touch Data/DataContext.cs
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; }
}
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
Check the .csproj
file to ensure the packages were installed
Next we can import the missing references
Create a new folder called Repositories
mkdir Repositories
Then create the IAuthRepository.cs in the Repositories folder.
touch Repositories/IAuthRepository.cs
IAuthRepository.cs
public interface IAuthRepository
{
Task<UserResponse> Register(CreateUserDto user);
}
Create a new folder called Repositories
mkdir Repositories
Then create the AuthRepository.cs in the Repositories folder.
touch Repositories/AuthRepository.cs
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
};
}
}
Let's the AuthController.cs
touch Controllers/AuthController.cs
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);
}
}
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;"
}
}
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();
Run migrations
dotnet ef migrations add InitialCreate
dotnet ef database update
Run the project
dotnet run
open the swagger ui
http://localhost:5108/swagger/index.html
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
We get a 200
Ok Response and Only exposed the Data coming from our UserResponse.cs
class
Mapping with AutoMapper
Let's create a helper class called MappingProfile.cs in the Helpers folder.
touch Helpers/MappingProfile.cs
MappingProfile.cs
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<User, UserResponse>();
CreateMap<CreateUserDto, User>();
}
}
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);
}
}
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));
//..
Run the project
dotnet run
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
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;
}
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;
}
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>();
}
}
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
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.
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)
Nice step by step explanation with short and clear examples !!
Thanks you
@yogini16 Glad you found it useful