Custom Type Converters

Sometimes, you need to take complete control over the conversion of one type to another. This is typically when one type looks nothing like the other, a conversion function already exists, and you would like to go from a "looser" type to a stronger type, such as a source type of string to a destination type of Int32.

For example, suppose we have a source type of:

public class Source
{
	public string Value1 { get; set; }
	public string Value2 { get; set; }
	public string Value3 { get; set; }
}

But you would like to map it to:

public class Destination
{
	public int Value1 { get; set; }
	public DateTime Value2 { get; set; }
	public Type Value3 { get; set; }
}

If we were to try and map these two types as-is, AutoMapper would throw an exception (at map time and configuration-checking time), as AutoMapper does not know about any mapping from string to int, DateTime or Type. To create maps for these types, we must supply a custom type converter, and we have three ways of doing so:

void ConvertUsing(Func<TSource, TDestination> mappingFunction);
void ConvertUsing(ITypeConverter<TSource, TDestination> converter);
void ConvertUsing<TTypeConverter>() where TTypeConverter : ITypeConverter<TSource, TDestination>;

The first option is simply any function that takes a source and returns a destination. This works for simple cases, but becomes unwieldy for larger ones. In more difficult cases, we can create a custom ITypeConverter<TSource, TDestination>:

public interface ITypeConverter<TSource, TDestination>
{
	TDestination Convert(TSource source);
}

And supply AutoMapper with either an instance of a custom type converter, or simply the type, which AutoMapper will instantiate at run time. The mapping configuration for our above source/destination types then becomes:

[Test]
public void Example()
{
    Mapper.CreateMap<string, int>().ConvertUsing(Convert.ToInt32);
    Mapper.CreateMap<string, DateTime>().ConvertUsing(new DateTimeTypeConverter());
    Mapper.CreateMap<string, Type>().ConvertUsing<TypeTypeConverter>();
    Mapper.CreateMap<Source, Destination>();
    Mapper.AssertConfigurationIsValid();

    var source = new Source
    {
        Value1 = "5",
        Value2 = "01/01/2000",
        Value3 = "AutoMapperSamples.GlobalTypeConverters.GlobalTypeConverters+Destination"
    };

    Destination result = Mapper.Map<Source, Destination>(source);
    result.Value3.ShouldEqual(typeof (Destination));
}

public class DateTimeTypeConverter : ITypeConverter<string, DateTime>
{
    public DateTime Convert(string source)
    {
        return System.Convert.ToDateTime(source);
    }
}

public class TypeTypeConverter : ITypeConverter<string, Type>
{
    public Type Convert(string source)
    {
        Type type = Assembly.GetExecutingAssembly().GetType(source);
        return type;
    }
}

In the first mapping, from string to Int32, we simply use the built-in Convert.ToInt32 function (supplied as a method group). The next two use custom ITypeConverter implementations.

The real power of custom type converters is that they are used any time AutoMapper finds the source/destination pairs on any mapped types. We can build a set of custom type converters, on top of which other mapping configurations use, without needing any extra configuration. In the above example, we never have to specify the string/int conversion again. Where as Custom Value Resolvers have to be configured at a type member level, custom type converters are global in scope.

System Type Converters

The .NET Framework also supports the concepts of type converters, through the TypeConverter class. AutoMapper supports these types of type converters, in configuration checking and mapping, without the need for any manual configuration. AutoMapper uses the TypeDescriptor.GetConverter method for determining if the source/destination type pair can be mapped.

Last edited May 6, 2009 at 1:49 AM by jbogard, version 1

Comments

alhambraeidos Nov 17, 2011 at 1:27 PM 
any sample for changed interface ITypeConverter

I get this error now
Error 7725 'BusinessFacade.Mappers.DecimalToNullableInt' does not implement interface member 'AutoMapper.ITypeConverter<decimal,int?>.Convert(AutoMapper.ResolutionContext)'

t316 May 25, 2011 at 2:10 PM 
I am a newbie and am looking for some advice on a design pattern for something very specific:
I am using Asp.Net MVC3 and EF 4.1 where I have an Email entity:
int Id;
string EmailAddress;

I also have a Referral entity:
int Id;
Email ReferredEmail;

I also have a ReferralVM:
string EmailAddress;

I use the ReferralVM for Create and Edit actions - this way I can use regular expression validators for the email address.

When using AutoMapper in the create and edit controller action, I want to map the ReferralVM to a Referral object, which in turn means that the string EmailAddress needs to be looked up in the db and either and existing or new Email entity needs to be returned to set the Email property of the Referral class. During the "auto mapping" how can I get an instance of my EF database context? If I new up one then the Referral and its Email property will be from different contexts, right? I need to pass through in to the guts of the auto mapping functionality a reference to an EF data context.

What's a good approach for solving this problem?

Thanks

adamtolley Apr 20, 2011 at 7:04 PM 
@danthman : If the design requires different conversions in different places you can get specific with your .ForMember() options when mapping specific types. It seems supplying a type converter is useful for defining a global conversion practice (which can be overridden by specific mappings).

Check out the "Projection" feature.

Lance May 27, 2010 at 6:06 PM 
This page is out of date.

The ITypeConverter interface has been changed to have a "TDestination Convert(ResolutionContext context)" instead of "TDestination Convert(TSource source)" for the Convert method.

Instead, this should likely show the use of the TypeConverter base class and its abstract "ConvertCore" method with the signature similar to above of:

protected abstract TDestination ConvertCore(TSource source);

danthman Jan 31, 2010 at 1:52 AM 
How does this work if there is more than one possible conversion between, say, string and DateTime?