Implementing AutoMapper for DTO Mapping with Audit Details
AutoMapper is a powerful object-object mapper that simplifies the transformation between data models. In Clean Architecture, it plays a vital role in maintaining a clear separation of concerns between layers. By automating mapping and handling fields like CreatedBy, CreatedOn, UpdatedBy, and UpdatedOn, AutoMapper reduces boilerplate code and ensures consistency.
This article delves into how AutoMapper is utilized in the Contact Management Application to efficiently handle:
- Mapping DTOs (Data Transfer Objects) to domain entities and vice versa.
- Automating audit field population.
- Supporting clean and maintainable service implementations.
Key Takeaways
- Learn how AutoMapper integrates into Clean Architecture.
- Understand mapping DTOs to domain entities with audit handling.
- Explore reusable generic services for seamless CRUD operations.
- Discover the benefits of AutoMapper in building scalable applications.
1. What is AutoMapper?
AutoMapper automates data transformations between objects. It is especially useful in Clean Architecture, where decoupling between layers is a key principle. In the Contact Management Application, AutoMapper:
- Maps data between DTOs and domain entities seamlessly.
- Keeps domain logic independent of API-specific requirements.
- Automates population of audit fields like CreatedBy and UpdatedOn during create and update operations.
Part of the Series
This article is part of a series covering the Contact Management Application, a sample project demonstrating Clean Architecture principles. Other articles include:
- Clean Architecture: Introduction to the Project Structure
- Clean Architecture: Implementing AutoMapper for DTO Mapping with Audit Details (You are here)
- Clean Architecture: Validating Inputs with FluentValidation
- Clean Architecture: Dependency Injection Setup Across Layers
- Clean Architecture: Handling Authorization and Role-Based Access Control (RBAC)
- Clean Architecture: Implementing Activity Logging with Custom Attributes
- Clean Architecture: Unit of Work Pattern and Its Role in Managing Transactions
- Clean Architecture: Using Dapper for Data Access and Repository Pattern
- Clean Architecture: Best Practices for Creating and Using DTOs in the API
- Clean Architecture: Error Handling and Exception Management in the API
- Clean Architecture: Dockerizing the .NET Core API and MS SQL Server
- Clean Architecture: Seeding Initial Data Using Docker Compose and SQL Scripts
2. Setting Up AutoMapper in the Application
2.1 Install AutoMapper
To begin, install AutoMapper in your project via NuGet:
Install-Package AutoMapper.Extensions.Microsoft.DependencyInjection
2.2 Configure AutoMapper Profiles
Mapping profiles define transformation rules. In the application, a BaseMappingProfile is implemented to manage shared functionality, including the assignment of audit fields:
public abstract class BaseMappingProfile : Profile
{
private IHttpContextAccessor _httpContextAccessor;
protected BaseMappingProfile(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected void SetAuditFields<TSource, TEntity>(TSource dto, TEntity entity, bool isCreate = true)
where TEntity : BaseEntity
{
Guid userId = GetCurrentUserId();
var currentTime = DateTimeOffset.UtcNow;
if (isCreate)
{
entity.CreatedOn = currentTime;
entity.CreatedBy = userId;
}
else
{
entity.UpdatedOn = currentTime;
entity.UpdatedBy = userId;
}
}
private Guid GetCurrentUserId()
{
var userIdClaim = _httpContextAccessor?.HttpContext?.User?.FindFirst("Id");
return userIdClaim != null ? Guid.Parse(userIdClaim.Value) : Guid.Empty;
}
}
ContactPersonMappingProfile demonstrates mappings specific to the ContactPerson entity:
public class ContactPersonPofile : BaseMappingProfile
{
public ContactPersonPofile(IHttpContextAccessor httpContextAccessor)
: base(httpContextAccessor)
{
CreateMap<ContactPerson, ContactPersonResponse>();
CreateMap<CreateContactPerson, ContactPerson>()
.AfterMap((src, dest) => SetAuditFields(src, dest)); // Audit fields on creation
CreateMap<UpdateContactPerson, ContactPerson>()
.AfterMap((src, dest) => SetAuditFields(src, dest, false)); // Audit fields on update
}
}
Key Highlights:
• The profile supports flexible mapping for create, update, and response operations.
• Audit fields are seamlessly and automatically populated for every request type.
2.3 Registering AutoMapper in Startup.cs
To register AutoMapper in the API Layer, add the following line to the Startup.cs
file in the ConfigureServices
method:
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
This guarantees that AutoMapper profiles are effortlessly discovered and consistently applied throughout the application.
3. Implementing a Reusable Service with AutoMapper
The GenericService provides reusable CRUD functionality, leveraging AutoMapper for object transformations:
public class GenericService<TEntity, TResponse, TCreate, TUpdate>
: IGenericService<TEntity, TResponse, TCreate, TUpdate>
where TEntity : BaseEntity
where TResponse : class
where TCreate : class
where TUpdate : class
{
private readonly IGenericRepository<TEntity> _repository;
private readonly IMapper _mapper;
private readonly IUnitOfWork _unitOfWork;
public GenericService(IGenericRepository<TEntity> repository, IMapper mapper, IUnitOfWork unitOfWork)
{
_repository = repository;
_mapper = mapper;
_unitOfWork = unitOfWork;
}
public async Task<TResponse> Add(TCreate createDto)
{
using var transaction = _unitOfWork.BeginTransaction();
try
{
var entity = _mapper.Map<TEntity>(createDto);
var createdEntity = await _repository.Add(entity, transaction);
await _unitOfWork.CommitAsync();
return _mapper.Map<TResponse>(createdEntity);
}
catch
{
await _unitOfWork.RollbackAsync();
throw;
}
}
public async Task<TResponse> Update(TUpdate updateDto)
{
var entity = _mapper.Map<TEntity>(updateDto);
var updatedEntity = await _repository.Update(entity);
return _mapper.Map<TResponse>(updatedEntity);
}
public async Task<bool> Delete(Guid id)
{
return await _repository.Delete(id);
}
public async Task<TResponse> FindByID(Guid id)
{
var entity = await _repository.FindByID(id);
return _mapper.Map<TResponse>(entity);
}
public async Task<IEnumerable<TResponse>> FindAll()
{
var entities = await _repository.FindAll();
return _mapper.Map<IEnumerable<TResponse>>(entities);
}
}
The ContactPersonService extends GenericService for domain-specific operations:
public class ContactPersonService : GenericService<ContactPerson, ContactPersonResponse, CreateContactPerson, UpdateContactPerson>, IContactPersonService
{
public ContactPersonService(IGenericRepository<ContactPerson> repository, IMapper mapper, IUnitOfWork unitOfWork)
: base(repository, mapper, unitOfWork)
{
}
// Additional methods specific to ContactPerson can go here
}
In Clean Architecture, DTOs are utilized in the API Layer to exchange data without revealing the underlying domain models. Below is an example demonstrating how AutoMapper facilitates the mapping of data from a CreateContactPerson DTO to the ContactPerson domain entity.
3.1 Example of CreateContactPersonDto
The DTO used when creating a new contact person:
public class CreateContactPerson
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public long Mobile { get; set; }
}
3.2 Example of ContactPerson Entity
The domain entity that represents a contact:
public class ContactPerson : BaseEntity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public long Mobile { get; set; }
}
The ContactPerson
entity inherits from the BaseEntity
, which contains common properties like CreatedBy
, CreatedOn
, UpdatedBy
, and UpdatedOn
.
4. Managing Audit Fields in AutoMapper
Audit fields like CreatedBy, CreatedOn, UpdatedBy, and UpdatedOn are critical for tracking changes in your data. The BaseMappingProfile simplifies handling these fields by encapsulating the logic in a reusable profile.
public abstract class BaseMappingProfile : Profile
{
private IHttpContextAccessor _httpContextAccessor;
protected BaseMappingProfile(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected void SetAuditFields<TSource, TEntity>(TSource dto, TEntity entity, bool isCreate = true)
where TEntity : BaseEntity
{
Guid userId = GetCurrentUserId();
var currentTime = DateTimeOffset.UtcNow;
if (isCreate)
{
entity.CreatedOn = currentTime;
entity.CreatedBy = userId;
}
else
{
entity.UpdatedOn = currentTime;
entity.UpdatedBy = userId;
}
}
private Guid GetCurrentUserId()
{
var userIdClaim = _httpContextAccessor?.HttpContext?.User?.FindFirst("Id");
return userIdClaim != null ? Guid.Parse(userIdClaim.Value) : Guid.Empty;
}
}
Benefits of AutoMapper for Audit Fields
- Consistency: Audit fields are seamlessly populated throughout the application, ensuring uniformity and accuracy.
- Separation of concerns: The service layer focuses on business logic while AutoMapper handles mapping and setting audit data.
- Reduced boilerplate code: You don’t need to manually set audit fields in every service or repository method.
5. Benefits of AutoMapper in Clean Architecture
- Decoupling: Ensures clear separation between API and Domain layers by working with DTOs and domain entities independently.
- Consistency: Automatically handles repetitive tasks like populating audit fields, reducing errors.
- Maintainability: Any changes to the structure of DTOs or domain entities can be easily handled by updating the mapping configurations, without impacting other layers of the application.
- Efficiency: Reduces boilerplate code and streamlines development processes.
Conclusion
In this article, we explored how to set up and use AutoMapper for mapping DTOs to domain entities and vice versa in the Contact Management Application. We also saw how AutoMapper helps manage audit fields like CreatedBy
and UpdatedBy
, ensuring that they are automatically populated during create and update operations.
The use of AutoMapper greatly simplifies the process of mapping objects and ensures clean separation of concerns between layers. In the next article, we will look at how to implement validation in the Application Layer using FluentValidation.
For a deeper look at the project, check out the full source code on GitHub:
GitHub Repository: Contact Management Application
Discover more from Nitin Singh
Subscribe to get the latest posts sent to your email.
8 Responses
[…] Clean Architecture: Implementing AutoMapper for DTO Mapping and Audit Logging […]
[…] Clean Architecture: Implementing AutoMapper for DTO Mapping and Audit Logging […]
[…] Clean Architecture: Implementing AutoMapper for DTO Mapping and Audit Logging […]
[…] Clean Architecture: Implementing AutoMapper for DTO Mapping and Audit Logging […]
[…] Clean Architecture: Implementing AutoMapper for DTO Mapping and Audit Logging […]
[…] Clean Architecture: Implementing AutoMapper for DTO Mapping and Audit Logging […]
[…] Clean Architecture: Implementing AutoMapper for DTO Mapping and Audit Logging […]
[…] Clean Architecture: Implementing AutoMapper for DTO Mapping and Audit Logging […]