Most of us, at some time, have faced a situation where we need to convert the data contract objects to corresponding data model object. I agree, it is very easy to do so. We just need to write some static
methods to convert one type of object to another. However this task is very boring. Also, you need to be careful while translating each source property to correct destination property, especially when copying things in this case. You need to be careful enough to handle null
values.
So what is the solution then? Yes, our friend has already written a library for you to do this repetitive and boring task. Along with this, it provides you variety of ways to do this. You can configure things as per your requirement. Now without waiting any more, let's know more and in detail about what is AutoMapper and how to use it.
What is AutoMapper?
- Object-Object Mapper – Translated object of one class to object of its counterpart class.
- Nuget Package – You can get AutoMapper using Nuget Package manager since it is shared through Nuget.
What are the Things to Know About AutoMapper?
- It ignores
null
reference exceptions when mapping your source to your target. - It does the dirty work for you of figuring out how to map type A to type B.
- As long as type B follows
AutoMapper
’s established convention, almost zero configuration is needed to map two types. - It provides simple configuration of types, as well as simple testing of mappings.
- AutoMapper also supports Flattening.
What are the Simple Steps to Use AutoMapper?
- Open Visual Studio.
- Create a new project (Windows/Web). I created console application.
- Install AutoMapper into your project using Nuget Package Manager UI or Nuget Package Manager Console. I used the below command to install it through console. It will install the latest version of AutoMapper.
Install-Package AutoMapper
- Create two classes “Source.cs” and “Dest.cs” as below:
namespace Demo.AppCode
{
public class Source
{
public int Id { get; set; }
public string Value { get; set; }
}
}
namespace Demo.AppCode
{
public class Dest
{
public int Id { get; set; }
public string Value { get; set; }
}
}
- Use
Mapper.Map()
to convert source object to destination object as below:
using System;
using AutoMapper;
using Demo.AppCode;
namespace Demo
{
class Program
{
static void Main(string[] args)
{
var aSourceObject = new Source()
{
Id = 1000,
Value = "Test Value"
};
Mapper.CreateMap<Source, Dest>();
var aDestObject = Mapper.Map<Dest>(aSourceObject);
Console.WriteLine("Source.Id = {0}
Dest.Id={1}", aSourceObject.Id, aDestObject.Id);
Console.WriteLine("Source.Value = {0}
Dest.Value={1}", aSourceObject.Value, aDestObject.Value);
Console.ReadKey();
}
}
}
The above example is the simplest example of using AutoMapper in your application. You can have some more properties in your classes. Apart from this, you can do some custom configuration. We will see such examples soon.
Where Should I Configure AutoMapper?
If you’re using the static Mapper
method, configuration should only happen once per AppDomain
/Application. That means the best place to put the configuration code is in application startup, such as the Global.asax file > Application_Start()
method for ASP.NET applications. Typically, the configuration bootstrapper class is in its own class, and this bootstrapper class is called from the startup method.
What Do You Mean by bootstrapper Class in the Above Statement?
Bootstrapper class is a general class in our application where we create a method, say “Initialize()
” and we put all the initialization related code blocks. These code blocks are supposed to run only once. In this way, it is easy to maintain this code block. It is good practice that we create a startup.cs file in our application at root and use this class wherever needed. In this case, we can call Startup.CreateMappings()
in Global.asax > Application_Start()
method.
How Can I Test My Mappings?
To test your mappings, you need to create a test that calls your bootstrapper class say “Startup.CreateMappings()
” to create all the mappings and call Mapper.AssertConfigurationIsValid()
method which should verify if there is any issue with your mappings. Below is the example:
using AutoMapper;
using Demo.AppCode;
namespace Demo
{
public static class Startup
{
public static void CreateMappings()
{
Mapper.CreateMap<Source, Dest>();
Mapper.AssertConfigurationIsValid();
}
}
}
What is Flattening? Can You Give An Example Using AutoMapper?
There are times when you have a complex object having child objects with their own child object and so on. In this case, you may need to translate this complex object to some other simple object which contains only few properties out of the complex object.
So converting the complex object (having nested child objects) to simple object (having single level of properties) is called Flattening.
AutoMapper does support Flattening. Below is an example which gives you an idea about how to do it:
using System;
using System.Collections.Generic;
using AutoMapper;
using Demo.AppCode;
namespace Demo
{
class Program
{
static void Main(string[] args)
{
Startup.CreateMappings();
var aComplexObject = new Complex()
{
Other = new Other(){Name = "Other child"},
Childs = new List<Child>()
{
new Child(){ChildId = 101, ChildValue = "Value1"},
new Child(){ChildId = 102, ChildValue="Value2"}
}
};
var aSimpleObject = Mapper.Map<Simple>(aComplexObject);
Console.WriteLine("SimpleObject.OtherName = {0}", aSimpleObject.OtherName);
Console.WriteLine("SimpleObject.TotalChildCount = {0}", aSimpleObject.TotalChildCount);
Console.ReadKey();
}
}
}
using System.Collections.Generic;
namespace Demo.AppCode
{
public class Complex
{
public Other Other { get; set; }
public IList<Child> Childs { get; set; }
public int GetTotalChildCount()
{
return Childs == null ? 0 : Childs.Count;
}
}
public class Other
{
public string Name { get; set; }
}
public class Child
{
public int ChildId { get; set; }
public string ChildValue { get; set; }
}
}
namespace Demo.AppCode
{
public class Simple
{
public string OtherName { get; set; }
public int TotalChildCount { get; set; }
}
}
What are the Rules/Conventions of AutoMapper Which We Should Follow to Get Better Result?
Following are some important conventions you should remember while using AutoMapper:
- When you configure a source/destination type pair in AutoMapper, its configurator attempts to match properties and methods on the source type to properties on the destination type. If for any property on the destination type a property, method, or a method prefixed with “Get” exist on the source type, AutoMapper assigns its value to the corresponding property (without “
Get
”) in destination type. - When you configure a source/destination type pair in AutoMapper, its configurator attempts to match properties and methods on the source type to properties on the destination type. If for any property on the destination type a property, method, or a method prefixed with “Get” does not exist on the source type, AutoMapper splits the destination member name into individual words (by PascalCase conventions).
What is Custom Value Resolver in AutoMapper? When and How to Use It in My Application?
Although AutoMapper covers quite a few destination member mapping scenarios, there are the scenarios where AutoMapper needs a little help in resolving. For this, we need to create our own resolver.
Steps to Use Custom Value Resolver in AutoMapper
- Create two classes,
Source
and Destination
. - Create custom value resolver by inheriting AutoMapper class “
ValueResolver<S,T>
”. Here S
represents source class data type and T
represents the property datatype in destination class. - Tell AutoMapper to use this custom value resolver when resolving a specific destination member/property. We have several options in telling AutoMapper a custom value resolver to use, including:
ResolveUsing<TValueResolver>
ResolveUsing(typeof(CustomValueResolver))
ResolveUsing(aValueResolverInstance)
Below is an example which demonstrates the use of custom AutoMapper resolver to resolve Source
class object to Dest
class object.
using System;
using AutoMapper;
namespace Demo
{
public class Source
{
public int Id { get; set; }
public string Value { get; set; }
}
public class Dest
{
public string IdAndValue { get; set; }
}
public class SourceToDestResolver : ValueResolver<Source, string>
{
protected override string ResolveCore(Source theSource)
{
return string.Format("Id with Value: {0}-{1}", theSource.Id, theSource.Value);
}
}
class Program
{
static void Main(string[] args)
{
Mapper.CreateMap<Source, Dest>()
.ForMember(dest => dest.IdAndValue, opt => opt.ResolveUsing<SourceToDestResolver>());
var aSource = new Source() {Id = 1001, Value = "Any Value"};
var aDest = Mapper.Map<Source, Dest>(aSource);
Console.WriteLine("Source Id: {0} Source Value: {1}", aSource.Id, aSource.Value);
Console.WriteLine("Destination IdWithValue: {0}", aDest.IdAndValue);
Console.ReadKey();
}
}
}
Can We Instruct AutoMapper to Use Custom Constructor Methods to Create Object of Custom Resolver Instead of Reflection? If Yes, How?
Because we only supplied the type of the custom resolver to AutoMapper, the mapping engine will use reflection to create an instance of the value resolver.
If we don’t want AutoMapper to use reflection to create the instance, we can either supply the instance directly, i.e., ResolveUsing(aValueResolverInstance)
, or use the ConstructedBy
method to supply a custom constructor method:
Mapper.CreateMap<Source, Destination>()
.ForMember(dest => dest.IdWithValue,
opt => opt.ResolveUsing<SourceToDestResolver>().ConstructedBy(() =>
new SourceToDestResolver())
);
AutoMapper will execute callback function instead of using reflection during the mapping operation, helpful in scenarios where the resolver might have constructor arguments or need to be constructed by an IoC container.
How Can You Customize the Source Value Supplied to the Resolver?
By default, AutoMapper passes the source object to the resolver. This limits the reusability of resolvers, since the resolver is coupled to the source type. If, however, we supply a common resolver across multiple types, we configure AutoMapper to redirect the source value supplied to the resolver:
Mapper.CreateMap<Source, Dest>()
.ForMember(dest => dest.Total,
opt => opt.ResolveUsing<SourceToDestResolver>().FromMember(src => src.SubTotal));
Mapper.CreateMap<OtherSource, OtherDest>()
.ForMember(dest => dest.OtherTotal,
opt => opt.ResolveUsing<SourceToDestResolver>().FromMember(src => src.OtherSubTotal));
public class CustomResolver : ValueResolver<decimal, decimal> {
}
Can You Modify the Value of Source Object Before and After Map Actions? If Yes, How?
Occasionally, you might need to perform custom logic before or after a map occurs. In this case, you can create global before/after map actions. For example, you may want to validate the source values or trim string
s:
Mapper.CreateMap<Source, Dest>()
.BeforeMap((src, dest) => src.Value = src.Value + 10)
.AfterMap((src, dest) => dest.Name = src.Name.Trim());
You can also create before/after map callbacks during mapping:
Mapper.Map<Source, Dest>(src, opt => {
opt.BeforeMap((src, dest) => src.Value = src.Value + 10);
opt.AfterMap((src, dest) => dest.Name = HttpContext.Current.Identity.Name);
});
What is Conditional Mapping in AutoMapper? How To Do It?
AutoMapper allows you to add conditions to properties that must be met before that property will be mapped. In other words, you can validate the properties before translation.
Mapper.CreateMap<Source,Dest>()
.ForMember(dest => dest.Id, opt => opt.Condition(src => (src.Id > 0)));
“Null substitution” allows you to supply an alternate value for a destination member if the source value is null
anywhere along the member chain.
Mapper.CreateMap<Source, Dest>()
.ForMember(dest => dest.Country, opt => opt.NullSubstitute("India"));
Does AutoMapper Support Nested Mapping?
Yes, AutoMapper supports Nested Mapping. As the mapping engine executes the mapping, it can use one of a variety of methods to resolve a destination member value. One of these methods is to use another type map, where the source member type and destination member type are also configured in the mapping configuration. This allows us to not only flatten our source types, but create complex destination types as well.
In this case, you need to create mapping for source object type along with nested child object types:
Mapper.CreateMap<OuterSource, OuterDest>();
Mapper.CreateMap<InnerSource, InnerDest>();
Mapper.CreateMap<MostInnerSource, MostInnerDest>();
Do We Need to Create Mapping for List/Array/Collection?
No, we do not need to create mapping for List
/Array
/Collection
. AutoMapper only requires configuration of element types, not of any array or list type that might be used.
How AutoMapper Translated Source Types Which Inherits Some Other Classes?
In such scenario where we have a hierarchy of types in both our source and destination types. AutoMapper supports polymorphic arrays and collections, such that derived source/destination types are used if found.
AutoMapper still requires explicit configuration for child mappings, as AutoMapper cannot “guess” which specific child destination mapping to use. Here is an example of the above types:
Mapper.CreateMap<ParentSource, ParentDestination>()
.Include<ChildSource, ChildDestination>();
Mapper.CreateMap<ChildSource, ChildDestination>();
Hope it will help you understand about the concepts of AutoMapper and how to use it in your application. Let me know if you want some more details about it. I would be glad to help.
References
Please refer to AutoMapper for more details.