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.
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:
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.