AutoMapper is a mapping library that helps us to convert one object type to another. While it offers incredible flexibility and convenience, dealing with null values within source objects can sometimes lead to unexpected issues. In this article, we will explore how AutoMapper handles null value mapping and what are the consequences of incorrect null value mapping. Besides that, we will find out the right way to handle null values gracefully by configuring AutoMapper to ignore them for all source members.

To download the source code for this article, you can visit our GitHub repository.

So let’s dive into the details.

Setup AutoMapper in ASP.NET Core

First, let’s set up a simple ASP.NET Core Console application. We’ll need to install the AutoMapper NuGet package:

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

PM> Install-Package AutoMapper

Prior to delving into the specifics of AutoMapper mapping, it’s essential to set up our project to utilize AutoMapper: 

var config = new MapperConfiguration(cfg =>
{
    cfg.AddProfile<DefaultMappingProfile>();
});

IMapper mapper = config.CreateMapper();

Here, as a first step, we define an AutoMapper configuration instance by utilizing the MapperConfiguration class. It takes an action delegate in which we instruct AutoMapper to apply mapping rules defined in the DefaultMappingProfile class. We will talk about it shortly.

After completing the configuration, we can proceed with the creation process by calling the CreateMapper() method. It generates a mapper instance IMapper which we use for mapping purposes.

For a more in-depth look at using AutoMapper, be sure to check out our article Getting Started with AutoMapper in ASP.NET Core.

Create a Mapping Profile

AutoMapper, by convention, matches source and destination properties by their names and types. But in some situations, it requires special attention to handle mapping. AutoMapper supports custom configuration through mapping profiles. 

Let’s create a Mapping Profile class:

public class DefaultMappingProfile : Profile
{
    public DefaultMappingProfile()
    {
        CreateMap<StudentItemDto, StudentEntity>()
            .ForMember(dest => dest.Age, opt => opt.MapFrom(src => src.AgeInfo));
        CreateMap<StudentEntity, StudentItemDto>()
            .ForMember(dest => dest.AgeInfo, opt => opt.MapFrom(src => src.Age));
    }
}

Here, we generate the DefaultMappingProfile class to customize the mapping rules, inheriting from the AutoMapper Profile class.

In the constructor, we define our custom mapping rules. The CreateMap<StudentItemDto, StudentEntity>() method instructs AutoMapper to map the StudentItemDto source type to the StudentEntity destination type. This method returns a mapping object of the type IMappingExpression. Utilizing the features of IMappingExpression enables us to tailor our mapping rules according to our specific requirements. For example, we call the ForMember() method to create a rule that maps the AgeInfo property from the source object to the Age property of the destination object.

In the same way, we establish a secondary mapping for our StudentEntity to correspond with the destination type StudentItemDto, forming a reverse mapping rule.

Null Values Mapping

We as software developers want to write robust and error-free software applications. Handling null values during data mapping is crucial. If we don’t manage null values right, the user of the app may face null reference exceptions, data loss or corruption, or incorrect working logic in the application.

Let’s see how AutoMapper maps null values with the default configuration:

private static StudentEntity MapToStudentEntity<TProfile>(StudentItemDto source) 
    where TProfile : Profile, new()
{
    var config = new MapperConfiguration(cfg =>
    {
        cfg.AddProfile<TProfile>();
    });

    IMapper mapper = config.CreateMapper();
    var destination = GetSampleEntity();
    destination = mapper.Map(source, destination);

    Console.WriteLine("Destination : {0}",
        JsonConvert.SerializeObject(destination, Formatting.Indented));

    if (destination.Department == null)
        throw new ArgumentNullException("Department", "Department is null");

    return destination;
}

Here, we create a generic MapToStudentEntity() function that takes a source parameter of type StudentItemDto and maps it to StudentEntity. To handle the mapping process, we generate a mapper object based on the generic TProfile

After that, we initialize a destination variable by calling the GetSampleEntity() method. It simply generates and returns a mock StudentEntity for testing purposes:

public static StudentEntity GetSampleEntity()
{
    var entity = new StudentEntity()
    {
        Id = 101,
        Name = "Sample",
        Age = 29,
        Department = "Computer Engineering",
        Grades = new List<decimal>() { 2.36M, 2.72M, 3.06M },
        University = new University() { Name = "Bosphorus University" }
    };

    return entity;
}

Then, we call the Map() method of the mapper object and it populates the destination object members with the source object members. 

Now, let’s see how to call this generic mapping method:

public static StudentEntity UpdateStudent(StudentItemDto source)
{
    return MapToStudentEntity<DefaultMappingProfile>(source);
}

Here, we create the UpdateStudent() method. For the sake of simplicity, it calls the MapToStudentEntity() method with the DefaultMappingProfile.

Let’s examine the mapping result displayed in the console output:

Source : {
  "Id": 0,
  "Name": "Test",
  "AgeInfo": 29,
  "Grades": null,
  "Department": null,
  "University": null,
  "IsGraduated": false
}

Using DefaultMappingProfile:
Destination : {
  "Id": 0,
  "Name": "Test",
  "Age": 29,
  "Grades": [],
  "Department": null,
  "University": null
}
Mapped with exception : Department is null (Parameter 'Department')

Here, we see that AutoMapper maps all null values from the source object to the destination object. Only, the Grades property that has the type List mapped to an empty list instead of a null value. Additionally, our application throws an exception because AutoMapper attempts to populate the Department member with a null value. At this point, if our application requires no null or empty values for certain properties, then it may end up crashing.

We can solve this problem by checking null properties manually and initializing them with a default value, but this is not practical and it shadows the power of AutoMapper. AutoMapper provides a graceful solution for such situations.

So, let’s handle null values with AutoMapper.

Ignore Null Values With AutoMapper

When AutoMapper encounters null values within source members, it can react in various ways, from throwing exceptions to populating destination objects with unexpected values. To ensure a seamless mapping process, it’s often necessary to instruct AutoMapper to gracefully ignore null values.

As we mentioned earlier, we configure mapping rules via mapping profile classes. Thus, it is time to create a new mapping profile that contains mapping rules to ignore null values for all source members:

public class IgnoreNullMappingProfile : Profile
{
    public IgnoreNullMappingProfile()
    {
        CreateMap<StudentItemDto, StudentEntity>()
            .ForMember(dest => dest.Age, opt => opt.MapFrom(src => src.AgeInfo))
            .ForAllMembers(opts =>
            {
                opts.Condition((src, dest, srcMember) => srcMember != null);
            });

        CreateMap<StudentEntity, StudentItemDto>()
            .ForMember(dest => dest.AgeInfo, opt => opt.MapFrom(src => src.Age))
            .ForAllMembers(opts =>
            {
                opts.Condition((src, dest, srcMember) => srcMember != null);
            });
    }
}

Here, we create the IgnoreNullMappingProfile mapping class. Its purpose is to ignore null values from the source object. To accomplish this, we invoke the ForAllMembers() method, which takes an action delegate as an argument. Inside the delegate method, we configure AutoMapper to ignore null values for all source members by calling opts.Condition((src, dest, srcMember) => srcMember != null).

Let’s see IgnoreNullMappingProfile in action:

public static StudentEntity UpdateStudentIgnoreNullValues(StudentItemDto source)
{
    return MapToStudentEntity<IgnoreNullMappingProfile>(source);
}

Here, we create the UpdateStudentIgnoreNullValues() method. It calls the MapToStudentEntity() method with the IgnoreNullMappingProfile.

Now, we can re-check the mapping result again:

Source : {
  "Id": 0,
  "Name": "Test",
  "AgeInfo": 29,
  "Grades": null,
  "Department": null,
  "University": null,
  "IsGraduated": false
}

Using IgnoreNullMappingProfile:
Destination : {
  "Id": 0,
  "Name": "Test",
  "Age": 29,
  "Grades": [],
  "Department": "Computer Engineering",
  "University": {
    "Name": "Bosphorus University",
    "Location": null
  }
}
Mapped without exception

With the new configuration, AutoMapper ignores null values and does not map them to the destination object. Besides that, our application does not throw an exception. But still, we have a problem. The Grades property is null in the source object and mapped as an empty list to the destination object. If our application requires us to ignore null values for all source members, then we should find a way to handle null value mapping for lists or collections. 

So, let’s empower the mapping configuration to ignore the null values for list or collection members.

Ignore Null Values for Lists and Collections

AutoMapper provides many functionalities to customize mapping rules. When it comes to the List or Collection type members, we have seen that with default mapping rules, AutoMapper maps null values to an empty list or collection.

AutoMapper has a configuration method named AllowNull(). With this configuration, AutoMapper allows null collections from the source object and tries to map to the destination object. By using both the AllowNull() and the Condition((...) => srcMember != null) rules, we configure AutoMapper to ignore null values for list and collection types.

With this in mind, let’s see how we use the AllowNull() method in mapping configuration:

public class IgnoreNullMappingProfile : Profile
{
    public IgnoreNullMappingProfile()
    {
        CreateMap<StudentItemDto, StudentEntity>()
            .ForMember(dest => dest.Age, opt => opt.MapFrom(src => src.AgeInfo))
            .ForAllMembers(opts =>
            {
                opts.AllowNull();
                opts.Condition((src, dest, srcMember) => srcMember != null);
            });

        CreateMap<StudentEntity, StudentItemDto>()
            .ForMember(dest => dest.AgeInfo, opt => opt.MapFrom(src => src.Age))
            .ForAllMembers(opts =>
            {
                opts.AllowNull();
                opts.Condition((src, dest, srcMember) => srcMember != null);
            });
    }
}

With the latest configuration modifications in place, let’s examine the mapping result once more:

Source : {
  "Id": 0,
  "Name": "Test",
  "AgeInfo": 29,
  "Grades": null,
  "Department": null,
  "University": null,
  "IsGraduated": false
}

Using IgnoreNullMappingProfile:
Destination : {
  "Id": 0,
  "Name": "Test",
  "Age": 29,
  "Grades": [
    2.36,
    2.72,
    3.06
  ],
  "Department": "Computer Engineering",
  "University": {
    "Name": "Bosphorus University",
    "Location": null
  }
}
Mapped without exception

Here, AutoMapper ignores the Grades property in the source object and keeps the destination object’s original value.

Conclusion

In this article, we have explored AutoMapper mapping profiles. We configured AutoMapper via customized mapping rules through mapping profiles. More importantly, we delved into null value mapping problems and what consequences may occur when we don’t care about null value mapping. Subsequently, we discussed how AutoMapper provides a graceful solution to ignore null values from all source members. Finally, we examined how we address null value mapping for list or collection types by utilizing AutoMapper’s AllowNull feature.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!