Automapper Post-Condition Mapping in ASP.NET Core Web API

AutoMapper Post-Condition Mapping in ASP.NET Core Web API

In this article, I will discuss How to Implement AutoMapper Post-Condition Mapping in ASP.NET Core Web API Application with Examples. Please read our previous article discussing AutoMapper Pre-Condition Mapping in ASP.NET Core Web API with Examples.

Post-Condition Mapping using AutoMapper

Post-Condition Mapping in AutoMapper is a feature that allows us to specify a condition that must be met after the mapping has occurred. This feature is useful when we want to enforce certain rules or conditions on the destination object based on the result of the mapping process. Unlike pre-condition checks, which determine whether a property should be mapped at all, post-conditions evaluate the state of the mapped object to decide if additional actions should be taken or if the mapping result is valid.

Here’s a basic structure on how to use Post-Condition Mapping:

  • Define the Mapping: First, you need to define your mapping configuration between the source and destination types using AutoMapper’s CreateMap method.
  • Specify the Post-Condition: Then specify a post-condition using the AfterMap method. This method allows you to execute custom logic after the source object has been mapped to the destination object. Inside the AfterMap method, you can access both the source and destination objects, enabling you to inspect the result of the mapping and apply any necessary conditions or adjustments.

Here is the syntax of how to use Post-Condition Mapping in Automapper:

CreateMap<Source, Destination>()
        .AfterMap((src, dest) =>
        {
            // Post-condition logic here
            if (dest.SomeProperty == "SomeValue")
            {
                // Adjust the destination object as necessary
                dest.AnotherProperty = "AdjustedValue";
            }
        });

In this example, after the source object is mapped to the destination object, the AfterMap method checks if SomeProperty of the destination object has a specific value. If the condition is met, it modifies the AnotherProperty of the destination object accordingly.

Example to Understand AutoMapper Post-Condition Mapping in ASP.NET Core Web API:

Let us understand AutoMapper Post-Condition Mapping in ASP.NET Core Web API with an example. For this example, assume we have the following Order entity and a corresponding OrderDTO entity.

Order Entity

Create a class file named Order.cs, and then copy and paste the following code:

namespace AutomapperDemo.Models
{
    public class Order
    {
        public int Id { get; set; }
        public string CustomerName { get; set; }
        public decimal TotalAmount { get; set; }
    }
}
OrderDTO Entity

Create a class file named OrderDTO.cs and then copy and paste the following code:

namespace AutomapperDemo.Models
{
    public class OrderDTO
    {
        public int Id { get; set; }
        public string CustomerName { get; set; }
        public decimal TotalAmount { get; set; }
        public string Status { get; set; }
    }
}
Configure AutoMapper Profile

Create an AutoMapper profile to define the mapping configurations. Here, you can specify a post-condition to set the Status property of OrderDTO based on the Total Amount. So, create a class file named MyMappingProfile.cs and then copy and paste the following code. Here, if the Order Amount is greater than 1000, then it’s a Premium order. Otherwise, it is a Standard order, and based on this order status, you might apply some discount, you might reduce the delivery charges, etc., as per your requirement.

using AutoMapper;
namespace AutomapperDemo.Models
{
    public class MyMappingProfile : Profile
    {
        public MyMappingProfile()
        {
            //Configure the Mappings Between Order and OrderDTO
            CreateMap<Order, OrderDTO>()
            .AfterMap((src, dest) =>
            {
                dest.Status = src.TotalAmount > 1000 ? "Premium" : "Standard";
            });
        }
    }
}
Here,
  • CreateMap<TSource, TDestination>() is a generic method where TSource is the source type (in this case, Order) and TDestination is the destination type (in this case, OrderDTO). This method call starts the definition of a mapping configuration between two types.
  • .AfterMap() is a method that defines a custom action to be performed after the mapping process from the source to the destination object has been completed. It takes a lambda expression with two parameters: src (source object) and dest (destination object).
  • In this lambda expression, we specify the custom logic to be applied after the initial mapping. Here, it sets the Status property of the OrderDto based on the TotalAmount of the Order. If the TotalAmount is greater than 1000, Status is set to “Premium”; otherwise, it’s set to “Standard”.
Register AutoMapper in Program.cs

Register AutoMapper and profiles with the dependency injection container in the Program.cs class file. So, please add the following statement within the Program class. This configuration tells AutoMapper to scan the assembly (or assemblies) for classes that inherit from Profile and automatically register them.

builder.Services.AddAutoMapper(typeof(Program).Assembly);

Using AutoMapper in Controllers

Now, in your controller, you can use AutoMapper to map Order instances to OrderDTO, and the post-condition will automatically apply. So, create an API Empty Controller named Order Controller and then copy and paste the following code:

using AutoMapper;
using AutomapperDemo.Models;
using Microsoft.AspNetCore.Mvc;

namespace AutomapperDemo.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class OrderController : ControllerBase
    {
        //Create a variable to holder mapper instance
        private readonly IMapper _mapper;

        //Framework will inject the instance using Constructor
        public OrderController(IMapper mapper)
        {
            //Initialize the variable with the injected mapper instance
            _mapper = mapper;
        }

        private List<Order> listOrders = new List<Order>()
        {
            new Order { Id = 1001, CustomerName="Pranaya", TotalAmount = 1000},
            new Order { Id = 1002, CustomerName="Priyanka", TotalAmount = 2000}
        };

        [HttpGet("{id}")]
        public ActionResult<OrderDTO> GetOrder(int id)
        {
            var order = listOrders.FirstOrDefault(x => x.Id == id);
            if(order == null)
            {
                return NotFound();
            }

            //Map the Order with OrderDTO
            var orderDto = _mapper.Map<OrderDTO>(order);

            return Ok(orderDto);
        }
    }
}

In this example, after an Order is mapped to an OrderDTO, the post-condition in the mapping configuration checks the TotalAmount of the order. If it is greater than 1000, the Status of the OrderDTO is set to “Premium”; otherwise, it is set to “Standard”. This approach allows for flexible and dynamic post-mapping behavior, making your API more adaptable to complex business logic.

Testing the API:

Let us test the above endpoint using Postman and see the results.

API: Return an Order Based on the Order ID

URL: api/Order/1001

Method: GET

Using Postman:

How to Implement AutoMapper Post-Condition Mapping in ASP.NET Core Web API Application with Examples

Creating a Custom Method and using it within the AfterMap Method using AutoMapper

To create and use a custom method within the PostCondition method, i.e., AfterMap method using AutoMapper, we would typically be looking to perform a custom operation or transformation on our destination object after the standard mapping has taken place. This is useful when we need to apply some additional logic that doesn’t fit neatly into a direct field-to-field mapping scenario. Let us understand this with an example:

Define Your Source and Destination Classes:

First, you need to define the classes between which you want to map properties. First, define the Order and OrderDTO classes. Assume that an Order contains multiple OrderItems, and you want to calculate the total price in the OrderDTO. Also, we need to format the order data into a specific format.

OrderItem Model:
namespace AutomapperDemo.Models
{
    public class OrderItem
    {
        public string ProductName { get; set; }
        public decimal Price { get; set; }
        public int Quantity { get; set; }
    }
}
Order Model:
namespace AutomapperDemo.Models
{
    public class Order
    {
        public int OrderId { get; set; }
        public DateTime OrderDate { get; set; }
        public string CustomerName { get; set; }
        public List<OrderItem> Items { get; set; } = new List<OrderItem>();
    }
}
OrderItemDTO Model:
namespace AutomapperDemo.Models
{
    public class OrderItemDTO
    {
        public string ProductName { get; set; }
        public decimal Price { get; set; }
        public int Quantity { get; set; }
    }
}
OrderDTO Model:
namespace AutomapperDemo.Models
{
    public class OrderDTO
    {
        public int OrderId { get; set; }
        public string OrderDate { get; set; } // Example of a custom format need
        public string CustomerName { get; set; }
        public decimal TotalPrice { get; set; }
        public List<OrderItemDTO> Items { get; set; } = new List<OrderItemDTO>();
    }
}
Create Custom Methods

Next, create a method that will perform the custom operations you need on your OrderDTO instances after AutoMapper completes the mapping. This could involve formatting data, setting additional properties that are not directly mapped, or other custom logic. So, create a class file named CustomizeOrderDTO.cs and then copy and paste the following code:

namespace AutomapperDemo.Models
{
    public class CustomizeOrderDTO
    {
        //This method will return the Customize Order date
        public static string CustomizeOrderDate(DateTime orderDate)
        {
            //Custom Date Format
            return orderDate.ToString("dd-MM-yyyy");
        }

        //This method will set the TotalPrice directly in the destination object
        public static void CalculateTotalPrice(Order source, OrderDTO destination)
        {
            //Setting the Total Price
            destination.TotalPrice = source.Items.Sum(item => item.Price * item.Quantity);
        }
    }
}
Use the Custom Method in Automapper Post Condition Mapping

Next, we need to use the custom static methods in our AutoMapper mapping configuration, specifically within the AfterMap method. Use the AfterMap method to specify that your custom method should be called post-mapping. So, modify your MyMappingProfile.cs class file as follows to include the custom method in the Post Condition:

using AutoMapper;
namespace AutomapperDemo.Models
{
    public class MyMappingProfile : Profile
    {
        public MyMappingProfile()
        {
            //Configure the Mappings Between Order and OrderDTO
            CreateMap<Order, OrderDTO>()
            .AfterMap((src, dest) => CustomizeOrderDTO.CalculateTotalPrice(src, dest))
            //.AfterMap(CustomizeOrderDTO.CalculateTotalPrice)
            .AfterMap((src, dest) =>
            {
                dest.OrderDate = CustomizeOrderDTO.CustomizeOrderDate(src.OrderDate);
            });

            //Configure the Mappings Between OrderItem and OrderItemDTO
            CreateMap<OrderItem, OrderItemDTO>();
        }
    }
}
Modifying the Order Controller:

Next, modify the Order Controller as follows to test all the scenarios:

using AutoMapper;
using AutomapperDemo.Models;
using Microsoft.AspNetCore.Mvc;

namespace AutomapperDemo.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class OrderController : ControllerBase
    {
        //Create a variable to holder mapper instance
        private readonly IMapper _mapper;

        //Framework will inject the instance using Constructor
        public OrderController(IMapper mapper)
        {
            //Initialize the variable with the injected mapper instance
            _mapper = mapper;
        }

        [HttpGet("{id}")]
        public ActionResult<OrderDTO> GetOrder(int id)
        {
            var order = new Order
            {
                OrderId = id,
                OrderDate = DateTime.Now,
                CustomerName = "Pranaya",
                Items = new List<OrderItem>
                {
                    new OrderItem { ProductName = "Product 1", Price = 100m, Quantity = 2 },
                    new OrderItem { ProductName = "Product 2", Price = 50m, Quantity = 1 },
                    // Add more items as needed
                }
            };

            //Map the Order with OrderDTO
            var orderDto = _mapper.Map<OrderDTO>(order);

            return Ok(orderDto);
        }
    }
}
Testing the API:

Let us test the above endpoint using Postman and see the results.

API: Return an Order Based on the Order ID

URL: api/Order/1001

Method: GET

Using Postman:

Creating a Custom Method and using it within the AfterMap Method using AutoMapper

When Should We Use AutoMapper Post-Condition Mapping in ASP.NET Core Web API?

Post-Condition Mapping with AutoMapper is a specific feature that allows for the execution of additional logic after the main mapping operation has been completed. This feature can be particularly useful in several scenarios:

  • Conditional Property Mapping: Post-condition mapping is useful when you need to map properties based on the values of other properties conditionally. For example, you might only want to map certain fields if they have not been explicitly set to a specific value during the initial mapping process.
  • Validating Mapped Values: After AutoMapper has performed the initial mapping, you might want to validate the mapped values to ensure they meet specific criteria. Post-condition mapping provides a hook for running such validation checks and potentially adjusting the mapped values based on the validation results.
  • Applying Business Rules: There may be instances where the mapping of properties from source to destination is subject to complex business rules that cannot be easily expressed within the confines of AutoMapper’s standard mapping configurations. Post-condition mappings allow for the execution of these rules after the main mapping process, enabling the application of modifications or additional logic based on the initially mapped object.
  • Default Value Assignment: In cases where certain destination properties should only be assigned default values if no suitable source value exists, post-condition mapping can be used to check the results of the initial mapping and apply defaults accordingly. This is particularly useful for avoiding the overwriting of destination values with nulls or undesired defaults.

In the next article, I will discuss Implementing Conditional Mapping using AutoMapper in ASP.NET Core Web API with Examples. In this article, I explain How to Implement AutoMapper Post-Condition Mapping in ASP.NET Core Web API with Examples. I hope you enjoy this article, “AutoMapper Pre-Condition and Post-Condition Mapping in ASP.NET Core Web API.”

Leave a Reply

Your email address will not be published. Required fields are marked *