Automapper Conditional Mapping in ASP.NET Core Web API

Automapper Conditional Mapping in ASP.NET Core Web API

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

AutoMapper Conditional Mapping

AutoMapper is a popular object-to-object mapping library that simplifies the process of transforming data from one object type to another. It is widely used in .NET applications to automate and streamline the mapping of properties between different object models, such as data transfer objects (DTOs) and domain models.

Conditional Mapping in AutoMapper is a powerful feature that allows us to specify conditions, i.e., custom logic that controls how and when properties are mapped from the source to the destination object. This can be useful when we want to apply certain mappings only if specific conditions are met at runtime.

To use Conditional Mapping in AutoMapper, we generally use the Condition method within our mapping configuration. This method allows us to specify a condition that must be true for the mapping to occur. If the condition is false, the mapping for that particular property will be skipped.

Example to Understand AutoMapper Conditional Mapping in ASP.NET Core Web API

Let us understand AutoMapper Conditional Mapping in ASP.NET Core Web API with Examples. We are going to use the following two classes to Understand AutoMapper Conditional Mapping.

Product Model:

Create a class file named Product.cs and copy and paste the following code. This is a very simple class having 5 primitive-type properties. This is going to be our Source Object.

namespace AutomapperDemo.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string OptionalName { get; set; }
        public int Quantity { get; set; }
        public int Amount { get; set; }
    }
}
ProductDTO Model:

Next, create another class file named ProductDTO.cs and copy and paste the following code. This is also a very simple class having 4 primitive-type properties. This is going to be our Destination Object.

namespace AutomapperDemo.Models
{
    public class ProductDTO
    {
        public int Id { get; set; }
        public string ItemName { get; set; }
        public int ItemQuantity { get; set; }
        public int Amount { get; set; }
    }
}
Business Requirement:
  • We need to Map the Name property of the Source Object Product to the ItemName property of the destination object ProductDTO only if the Name value starts with the letter A; else, Map the OptionalName property value of the Source Object Product with the ItemName property of the destination object ProductDTO.
  • If the Quantity value of the Source Object Product is greater than 0, then only map it to the ItemQuantity property of the destination object ProductDTO.
  • Similarly, if the Amount value of the Source Object Product class is greater than 100, then only map it to the Amount property of the destination object ProductDTO.
Mapping Profile Configuration

To achieve this, we need to use AutoMapper Conditional Mapping. So, create a class file with the names MyMappingProfile.cs and copy and paste the following code. Here, you can see that for the ItemName destination property, we are using the ternary operator and setting the value. If the Name starts with A, then we are assigning the Name property value. Otherwise, we are assigning the OptionalName property value to the Destination Name property. Then, using the AutoMapper Condition method, we check the value of the Property of the Source Object, and if it satisfies the condition, then we map the value to the destination property. If the condition fails, then it will store the default value based on the data type.

using AutoMapper;
namespace AutomapperDemo.Models
{
    public class MyMappingProfile : Profile
    {
        public MyMappingProfile()
        {
            //Configure the Mappings Between Source Product and Destination ProductDTO
            CreateMap<Product, ProductDTO>()

                //Map Quantity and ItemQuantity as names are different
                .ForMember(dest => dest.ItemQuantity, act => act.MapFrom(src => src.Quantity))

                //If the Name Starts with A, then Map the Name Value, else Map the OptionalName value
                .ForMember(dest => dest.ItemName, act => act.MapFrom(src =>
                        (src.Name.StartsWith("A") ? src.Name : src.OptionalName)))
                    
                //Map the Quantity value if it is greater than 0
                .ForMember(dest => dest.ItemQuantity, opt => opt.Condition(src => src.Quantity > 0))
                    
                //Map the Amount value if it is greater than 100
                .ForMember(dest => dest.Amount, act => act.Condition(src => (src.Amount > 100)));
        }
    }
}
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 Conditional Mapping in a Controller

Now, implement a controller that uses AutoMapper Conditional Mapping. So, create an Empty API Controller named Product and copy and paste the following code.

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

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

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

        private List<Product> listProducts = new List<Product>()
        {
            new Product { Id = 1001, Name="Laptop", OptionalName="Gaming Laptop", Amount = 1000, Quantity = 10},
            new Product { Id = 1002, Name="Apple Laptop", OptionalName="Programming Apple Laptop", Amount = 2000, Quantity = 0}
        };

        [HttpGet("{Id}")]
        public ActionResult<ProductDTO> GetProdcutById(int Id)
        {
            Product? product = listProducts.FirstOrDefault(x => x.Id == Id);
            if(product != null) 
            {
                //var productDTO = _mapper.Map<Product, ProductDTO>(product);
                var productDTO = _mapper.Map<ProductDTO>(product);
                return Ok(productDTO);
            }
            return BadRequest("Product Not Found");
        }
    }
}
Testing the API:

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

API: Return Employee by ID

URL: api/Product/1001

Method: GET

Using Postman:

How to Implement AutoMapper Conditional Mapping in ASP.NET Core Web API Application with Examples

Using Custom Method with Automapper Conditional Mapping in ASP.NET Core Web API

Creating a custom method and using it within the Condition method in AutoMapper is a powerful feature that allows you to apply complex logic to determine if a particular mapping operation should occur. Let us understand this with an example. We will rewrite the previous example to use the Custom method with Automapper Conditional mapping.

So, create a class file named CustomConditionalMapping.cs and then copy and paste the following code. Here, we are defining three methods, and all the methods return type is Boolean, which is important. This is because the method used within the Condition method must return a Boolean value, and that value will decide whether to map the destination property value or not.

namespace AutomapperDemo.Models
{
    public class CustomConditionalMapping
    {
        public static bool ShouldMapName(string name)
        {
            // Return True is Name Starts with A
            return name.StartsWith("A");
        }

        public static bool ShouldMapQuantity(int Quantity)
        {
            // Only Map the Quantity value if it is greater than 0
            return Quantity > 0;
        }

        public static bool ShouldMapAmount(int Amount)
        {
            // Only Map the Amount value if it is greater than 100
            return Amount > 100;
        }
    }
}
Modifying the Mapper Profile Configuration:

Next, we need to use the custom methods within the Condition method. So, modify the MyMappingProfile.cs class file as follows:

using AutoMapper;
namespace AutomapperDemo.Models
{
    public class MyMappingProfile : Profile
    {
        public MyMappingProfile()
        {
            //Configure the Mappings Between Source Product and Destination ProductDTO
            CreateMap<Product, ProductDTO>()

                //Map Quantity and ItemQuantity as names are different
                .ForMember(dest => dest.ItemQuantity, act => act.MapFrom(src => src.Quantity))

                //Call the ShouldMapName Method
                .ForMember(dest => dest.ItemName, act => act.MapFrom(src =>
                        (CustomConditionalMapping.ShouldMapName(src.Name) ? src.Name : src.OptionalName)))

                //Call the ShouldMapQuantity Method
                .ForMember(dest => dest.ItemQuantity,
                  opt => opt.Condition(src => CustomConditionalMapping.ShouldMapQuantity(src.Quantity)))

                //Call the ShouldMapAmount Method
                .ForMember(dest => dest.Amount,
                  opt => opt.Condition(src => CustomConditionalMapping.ShouldMapAmount(src.Amount)));
        }
    }
}

Now, run the application and access the above endpoint, and you should get the output as expected in the previous example.

Data Integrity and Validation using Automapper Conditional Mapping in ASP.NET Core Web API

You can use conditional mappings to ensure that only valid data is transferred from one object to another. By setting conditions on mappings, you can prevent invalid data from corrupting your target object, thus maintaining data integrity across your application.

Assume we have a source object PersonViewModel that contains user input data and a destination object Person that represents the validated data model. We only want to map the Age from PersonViewModel to Person if the Age is greater than 18 and less than 60 (a simple validation rule).

Define Source and Destination Models
namespace AutomapperDemo.Models
{
    public class PersonViewModel
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

    public class Person
    {
        public string Name { get; set; }
        public int? Age { get; set; }
    }
}
Configure AutoMapper:

Next, modify the MyMappingProfile.cs class file as follows:

using AutoMapper;
namespace AutomapperDemo.Models
{
    public class MyMappingProfile : Profile
    {
        public MyMappingProfile()
        {
            //Configure the Mappings Between PersonViewModel Product and Destination Person
            CreateMap<PersonViewModel, Person>()
                //Map the Age value only if it is greater than 18 and less than 60
                .ForMember(dest => dest.Age, opt => opt.Condition(src => src.Age > 18 && src.Age < 60));
        }
    }
}
Using Automapper in a Controller:

Create an API Controller named User 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 UserController : ControllerBase
    {
        private readonly IMapper _mapper;

        public UserController(IMapper mapper)
        {
            _mapper = mapper;
        }

        [HttpPost]
        public ActionResult<Person> AddUser(PersonViewModel model)
        {
            Person person = _mapper.Map<Person>(model);
            //Run the Application and Checked the Response and see whether Age Value is returned
            return Ok(person);
        }
    }
}
Testing the API:

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

API: Add a New User

URL: api/User

Method: POST

Request Body:

{
  "name": "Pranaya",
  "age": 15
}
Using Postman:

Data Integrity and Validation using Automapper Conditional Mapping in ASP.NET Core Web API

Business Logic using AutoMapper Conditional Mapping:

By using conditions that are based on business rules, AutoMapper can integrate closely with your application’s business logic layer. This ensures that the mappings are always consistent with the business rules of your application.

Let’s explore an example to demonstrate how you can use AutoMapper’s conditional mapping feature to apply business logic during the mapping process. Suppose we have a User Domain Model and a UserDTO that we want to map to. We might only want to include certain properties in the UserDTO based on specific conditions. First, define the domain model and DTO:

User Model:
namespace AutomapperDemo.Models
{
    public class User
    {
        public int Id { get; set; }
        public string Username { get; set; }
        public string Email { get; set; }
        public bool IsActive { get; set; }
    }
}
UserDTO:
namespace AutomapperDemo.Models
{
    public class UserDTO
    {
        public int Id { get; set; }
        public string Username { get; set; }
        // Assume we only want to map Email if the user is active
        public string Email { get; set; }
    }
}

Now, let’s configure AutoMapper to map User to UserDTO, applying a condition to the Email property mapping. We only want to map the Email if the user is active (IsActive is true). modify the MyMappingProfile.cs class file as follows:

using AutoMapper;
namespace AutomapperDemo.Models
{
    public class MyMappingProfile : Profile
    {
        public MyMappingProfile()
        {
            //Configure the Mappings Between User and UserDTO
            CreateMap<User, UserDTO>()
                .ForMember(dest => dest.Email, opt => opt.Condition(src => src.IsActive))
                .ForMember(dest => dest.Username, opt => opt.MapFrom(src => src.Username));
        }
    }
}

In the mapping configuration, ForMember is used to configure individual property mappings. The opt.Condition(src => src.IsActive) specifies that the Email property should only be mapped if IsActive is true for the source object. If IsActive is false, Email in UserDTO will not be mapped (i.e., it will be null or the default value for its type).

Using Automapper in User Controller:

Next, modify the User Controller as follows:

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

namespace AutomapperDemo.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class UserController : ControllerBase
    {
        private readonly IMapper _mapper;

        public UserController(IMapper mapper)
        {
            _mapper = mapper;
        }

        [HttpGet]
        public ActionResult<UserDTO> GetUser()
        {
            User user = new User
            {
                Id = 1,
                Username = "Pranaya",
                Email = "Pranaya@Example.com",
                IsActive = false, // Change this to see how it affects the mapping
            };

            //Mapping User to UserDTO
            UserDTO userDTO = _mapper.Map<UserDTO>(user);
            return Ok(userDTO);
        }
    }
}
Testing the API:

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

URL: api/User

Method: GET

Using Postman:

Business Logic using AutoMapper Conditional Mapping

In this example, AutoMapper will map Email from User to UserDTO only if IsActive is true. This allows you to integrate conditional logic directly into your mapping configurations, making your code cleaner and more maintainable, especially when dealing with complex scenarios where the business logic dictates the mapping behavior.

Basic Conditional Property Mapping using Automapper

Suppose you have a source object, User, and a destination object, UserDTO, and you only want to map the Email field if it’s not null or empty. We are going to use the same User and UserDTO models that we used in our previous example. So, modify the MyMappingProfile.cs class file as follows:

using AutoMapper;
namespace AutomapperDemo.Models
{
    public class MyMappingProfile : Profile
    {
        public MyMappingProfile()
        {
            //Configure the Mappings Between User and UserDTO
            CreateMap<User, UserDTO>()
                .ForMember(dest => dest.Email, opt => opt.Condition(src => !string.IsNullOrEmpty(src.Email))); 
        }
    }
}
Modifying User Controller:

Next, modify the User Controller as follows:

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

namespace AutomapperDemo.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class UserController : ControllerBase
    {
        private readonly IMapper _mapper;

        public UserController(IMapper mapper)
        {
            _mapper = mapper;
        }

        [HttpGet]
        public ActionResult<UserDTO> GetUser()
        {
            User user = new User
            {
                Id = 1,
                Username = "Pranaya",
                Email = "Pranaya@Example.com", // Make this field NULL and see the effects
                IsActive = true, 
            };

            //Mapping User to UserDTO
            UserDTO userDTO = _mapper.Map<UserDTO>(user);
            return Ok(userDTO);
        }
    }
}

Now, access the above endpoint, and you should get the output as expected.

Complex Condition Based on Multiple Fields

Now, what if you want to check both conditions for Email mapping, i.e., the user must be active, and the email ID value should not be null or empty? In that case, you need to provide both conditions while doing the mapping, as shown below:

using AutoMapper;
namespace AutomapperDemo.Models
{
    public class MyMappingProfile : Profile
    {
        public MyMappingProfile()
        {
            //Configure the Mappings Between User and UserDTO
            CreateMap<User, UserDTO>()
                .ForMember(dest => dest.Email, 
                opt => opt.Condition(src => !string.IsNullOrEmpty(src.Email) 
                                    && src.IsActive));
        }
    }
}
Advantages of Conditional Mapping
  • Flexibility: Allows for more complex mappings that can adapt based on the data being mapped based on the conditions.
  • Control: Provides control over the mapping process, enabling you to avoid overwriting values in certain scenarios or to only map values when specific conditions are met.
  • Efficiency: Can improve performance by avoiding unnecessary mappings.

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

Leave a Reply

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