How to implement a custom object mapper in C#
By FoxLearn 12/31/2024 9:45:51 AM 64
However, it has limitations, particularly when handling complex data structures or incompatible types. In such cases, implementing a custom object mapper becomes necessary.
The Role of Object Mappers
Object mapping essentially means transferring data from one object (source) to another (destination).
What is a custom object mapper?
A custom object mapper allows developers to define specific mapping rules tailored to their application's needs.
Why do we use a custom object mapper?
Using custom mappers also provides greater control over performance, enabling optimizations that are difficult to achieve with third-party libraries.
Some common use cases for custom mappers include:
- Handling incompatible data structures: When source and destination objects have mismatched properties or types, a custom mapper can bridge the gap.
- Integration with external components: If external APIs or databases use different data models than the internal system, a custom mapper ensures smooth integration.
- Versioning: As software evolves, changes in data models may necessitate remapping. A custom mapper can easily accommodate these shifts.
- Performance optimization: Fine-tuning how data is mapped allows for reducing overhead and improving application speed.
- Domain-specific rules: Custom mappers can implement specific transformations or business rules that go beyond simple property copying.
Custom Object Mapper in C#
Let’s consider a scenario where we need to map data between two objects, CustomerModel and CustomerDTO. The CustomerModel may be used internally within an application, while the CustomerDTO might be used to transfer data over a network or API.
CustomerModel (Internal Model)
public class CustomerModel { public int Id { get; set; } public string FullName { get; set; } public string Email { get; set; } public string Address { get; set; } public DateTime DateOfBirth { get; set; } }
CustomerDTO (Data Transfer Object)
public class CustomerDTO { public int Id { get; set; } public string Name { get; set; } public string Email { get; set; } public string Address { get; set; } public string Age { get; set; } // Derived from DateOfBirth in CustomerModel }
Notice that CustomerModel includes a DateOfBirth
field, while CustomerDTO has an Age
field, which needs to be calculated during mapping. This is an ideal scenario where a custom object mapper would be useful.
Implementing a Custom Object Mapper
Here’s how we can implement a custom object mapper that handles the mapping, including custom logic for calculating the Age from the DateOfBirth field.
public class CustomObjectMapper { public TDestination Map<TSource, TDestination>(TSource sourceObject) { var destinationObject = Activator.CreateInstance<TDestination>(); if (sourceObject != null) { foreach (var sourceProperty in typeof(TSource).GetProperties()) { var destinationProperty = typeof(TDestination).GetProperty(sourceProperty.Name); if (destinationProperty != null) { // Handle special logic for Age field if (destinationProperty.Name == "Age" && sourceProperty.Name == "DateOfBirth") { DateTime dateOfBirth = (DateTime)sourceProperty.GetValue(sourceObject); int age = DateTime.Now.Year - dateOfBirth.Year; if (DateTime.Now.DayOfYear < dateOfBirth.DayOfYear) age--; destinationProperty.SetValue(destinationObject, age.ToString()); } else { destinationProperty.SetValue(destinationObject, sourceProperty.GetValue(sourceObject)); } } } } return destinationObject; } }
In this CustomObjectMapper
, we add special handling for the Age field in CustomerDTO, which is derived from DateOfBirth in CustomerModel.
Using the Custom Mapper in Action
Now, let’s use our custom object mapper to map a CustomerModel object to a CustomerDTO object.
public class CustomerController { private readonly CustomObjectMapper _mapper; public CustomerController() { _mapper = new CustomObjectMapper(); } public CustomerDTO GetCustomer(int id) { // Assuming GetCustomerInstance(id) fetches a CustomerModel object from the database var sourceCustomer = GetCustomerInstance(id); var customerDTO = _mapper.Map<CustomerModel, CustomerDTO>(sourceCustomer); return customerDTO; } }
Example data
public CustomerModel GetCustomerInstance(int id) { return new CustomerModel { Id = id, FullName = "John Doe", Email = "[email protected]", Address = "123 Elm Street", DateOfBirth = new DateTime(1990, 5, 15) // Age will be calculated from this }; }
Custom Mapper in Action
Here’s what happens when we call GetCustomer
in our controller:
var customerDTO = GetCustomer(1); Console.WriteLine($"Id: {customerDTO.Id}, Name: {customerDTO.Name}, Age: {customerDTO.Age}, Email: {customerDTO.Email}, Address: {customerDTO.Address}");
Output:
Id: 1, Name: John Doe, Age: 34, Email: [email protected], Address: 123 Elm Street
Benefits of Using a Custom Mapper
- Custom Logic: You can easily introduce special logic for fields like Age, which requires calculations based on other properties like DateOfBirth.
- Separation of Concerns: The controller only interacts with the DTO, keeping business logic and data mapping in the repository or service layer.
- Reusability: The custom mapper can be reused across the application wherever this mapping logic is required.
When to Use a Custom Mapper
While AutoMapper is great for straightforward property mappings, custom mappers are ideal when:
- You need to apply additional logic (like calculating values or transforming data).
- The source and destination structures differ significantly (e.g., different property names, types, or complex nested objects).
- You need to optimize performance by controlling which properties are mapped.
- Content Negotiation in Web API
- How to fix 'InvalidOperationException: Scheme already exists: Bearer'
- How to fix System.InvalidOperationException: Scheme already exists: Identity.Application
- Add Thread ID to the Log File using Serilog
- Handling Exceptions in .NET Core API with Middleware
- InProcess Hosting in ASP.NET Core
- Limits on ThreadPool.SetMinThreads and SetMaxThreads
- Controlling DateTime Format in JSON Output with JsonSerializerOptions