09 January 2011 - 11:45 PM / by Dominic Pettifer. 0 Comments for AutoMapper – A Custom Type Converter That Exposes a Destination Value.
Cool C# Snippets - Writing a custom ITypeConverter for AutoMapper is easy, just write a class that extends the TypeConverter<TSource, TDestination> abstract type. But it’s possible to supply a pre-instantiated destination object during conversion which the TypeConverter type doesn’t expose.
I love AutoMapper. It means I don’t have to write code that looks like this:
TwitterUser user = TwitterUser.Show("Sironfoot").ResponseObject;
Tweeter tweeter = new Tweeter();
tweeter.TweetId = user.Id;
tweeter.Username = user.ScreenName;
tweeter.FullName = user.Name;
tweeter.ImageUrl = user.ProfileImageLocation;
// Dozens more properties (snip)...
return tweeter;Here we’re trying to convert a TwitterUser object provided from the excellent Twitterizer .NET Twitter API, to my own Tweeter domain object so it can be saved to the database. It gets tedious filling in each property manually, and if it’s happening in more than one place it’s violating the DRY principle. But with AutoMapper we can write code like this:
TwitterUser user = TwitterUser.Show("Sironfoot").ResponseObject;
return Mapper.Map<TwitterUser, Tweeter>(user);We’ve condensed our mapping logic down to a single line of code, and externalised the conversion code itself into its own class, that we register with AutoMapper:
public class TwitterUserToTweeterTypeConverter :
TypeConverter<TwitterUser, Tweeter>
{
protected override Tweeter ConvertCore(TwitterUser source)
{
// code to convert a TwitterUser to a Tweeter here (snip)
return tweeter;
}
}
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
Mapper.CreateMap<TwitterUser, Tweeter>()
.ConvertUsing<TwitterUserToTweeterTypeConverter>();
}
}With AutoMapper we can provide an already instantiated destination object rather than create a new one, like this:
TwitterUser user = TwitterUser.Show("Sironfoot").ResponseObject;
Tweeter tweeter = TweeterRepository.FromUsername("Sironfoot");
return Mapper.Map<TwitterUser, Tweeter>(user, tweeter);We’re using the overloaded version of the Map method that excepts a pre-instantiated destination object. This should return our existing Tweeter object but with the properties ‘re-filled’ in with the object pulled from Twitter, thus we are updating our database entry with live data from Twitter, but we want to keep certain properties intact (such as the database primary key ID).
However, this won’t work because the TypeConverter base type doesn’t expose this destination object, it will just return a fresh new Tweeter object each time. We could implement the ITypeConverter interface directly, or we could write our own TypeConverter base type like so:
using System;
using AutoMapper;
namespace MvcLibrary.AutoMapper
{
/// <summary>
/// An improvement on AutoMapper's TypeConverter type
/// to include access to an existing Destination type
/// </summary>
/// <typeparam name="TSource">The Source type being converted from</typeparam>
/// <typeparam name="TDestination">The Destination type being converted to</typeparam>
public abstract class AdvancedTypeConverter<TSource, TDestination> :
ITypeConverter<TSource, TDestination>
{
private ResolutionContext Context = null;
public TDestination Convert(ResolutionContext context)
{
Context = context;
if (Context.SourceValue != null &&
!(Context.SourceValue is TSource))
{
string message = "Value supplied is of type {0} but expected {1}.\n" +
"Change the type converter source type, or redirect " +
"the source value supplied to the value resolver using FromMember.";
throw new AutoMapperMappingException(Context, string.Format(
message, typeof(TSource), Context.SourceValue.GetType()));
}
return ConvertCore((TSource)Context.SourceValue);
}
protected TDestination ExistingDestination
{
get
{
if (Context == null)
{
string message = "ResolutionContext is not yet set. " +
"Only call this property inside the 'ConvertCore' method.";
throw new InvalidOperationException(message);
}
if (Context.DestinationValue != null &&
!(Context.DestinationValue is TDestination))
{
string message = "Destination Value is of type {0} but expected {1}.";
throw new AutoMapperMappingException(Context, string.Format(
message, typeof(TDestination), Context.DestinationValue.GetType()));
}
return (TDestination)Context.DestinationValue;
}
}
protected abstract TDestination ConvertCore(TSource source);
}
}If you strip away the exception handling code, it’s a pretty simple class. We’re exposing a ‘ExistingDestination’ property which we can check for, null means no existing destination object was provided so we create a new one. We extend the AdvancedTypeConverter<TSource, TDestination> type like so:
public class TwitterUserToTweeterTypeConverter :
AdvancedTypeConverter<TwitterUser, Tweeter>
{
protected override Tweeter ConvertCore(TwitterUser source)
{
Tweeter tweeter = ExistingDestination ?? new Tweeter();
tweeter.TwitterId = (long)source.Id;
tweeter.Username = source.ScreenName;
tweeter.FullName = source.Name;
tweeter.ImageUrl = source.ProfileImageLocation;
// other properties (snip)
return tweeter;
}
}This TypeConverter allows us to use either overloaded Mapper.Map method, with or without a pre-existing destination object.
These dominoes are getting bigger and bigger every year. (from the blog And So It Begins )
“Success is not the key to happiness. Happiness is the key to success.” - http://t.co/8k1uOWyR
about 15 hours ago from webDisappointed that Google's doodle is just a .gif. They could do that in HTML5 surely?
about 18 hours ago from webHave a look at http://t.co/R8tu6VBV - 90% of the website is an advert, ridiculous. Need to start using IMDB / Metacritic a lot more.
3:41 PM February 21st from web"If you see a proprietary memory card, they blew it." - http://t.co/qifZsKPs
1:28 PM February 21st from web