And the object mapper:
/// <summary>
/// This Mapper can transfer the values bwtween two existing objects, the source and the destination.
///
/// Property names are matched after being normalized:
/// 1. Underscores are removed (foo_bar_id becomes foobarid).
/// 2. Converted to uppercase (foobarid becomes FOOBARID)
/// </summary>
/// <typeparam name="S"></typeparam>
/// <typeparam name="T"></typeparam>
public class Mapper<S, T>
{
List<MemberInfo> targetMembers = new List<MemberInfo>();
private List<string> ignoreList = new List<string>();
public List<string> IgnoreList
{
get { return ignoreList; }
set { ignoreList = value; }
}
public Mapper()
{
this.targetMembers.AddRange(typeof(T).GetProperties());
this.targetMembers.AddRange(typeof(T).GetFields());
}
/// <summary>
/// Transfer the values bwtween two existing objects, the source and the destination.
/// </summary>
/// <param name="source">The object from which property values will be obtained.</param>
/// <param name="target">The object who's properties recieve the value of their matching property in the <paramref name="source"/></param>
/// <param name="failIfNotMatched">When a property in the <paramref name="source"/> does not match to a property in the <paramref name="target"/>
/// and <paramref name="failIfNotMatched"/> is TRUE, a <c>TargetNotMatchedException</c> will be thrown. Otherwise the unmatched property is ignored.< </param>
/// <param name="mapInheritedMembers">When <paramref name="mapInheritedMembers"/> is TRUE the set of source properties will include properties which
/// are inherited. Otherwise only the properties of the most derived type are mapped.</param>
public void MapProperties(S source, T target, bool failIfNotMatched = true, bool mapInheritedMembers = false)
{
BindingFlags bindingFlags = mapInheritedMembers
? BindingFlags.Public | BindingFlags.Instance
: BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance;
foreach (PropertyInfo property in source.GetType()
.GetProperties(bindingFlags)
.Where(c => !IgnoreList.Contains(c.Name)))
{
try
{
var sourceField = Factory.Get<MapperItem>(property, source);
var targetField = Factory.Get<MapperItem>(MatchToTarget(property), target);
targetField.Assign(sourceField);
}
catch (TargetNotMatchedException noMatch)
{
if (failIfNotMatched)
{
throw noMatch;
}
}
}
}
private MemberInfo MatchToTarget(MemberInfo member)
{
var exactMatch = this.targetMembers.Where(c => c.Name == member.Name);
if (exactMatch.FirstOrDefault() != null)
{
return exactMatch.First();
}
var sameAlphaChars = this.targetMembers.Where(c => Normalize(c.Name) == Normalize(member.Name));
if (sameAlphaChars.FirstOrDefault() != null)
{
return sameAlphaChars.First();
}
throw new TargetNotMatchedException(member, typeof(T));
}
private static string Normalize(string input)
{
string normalized = input.Replace("_", "").ToUpper();
return normalized;
}
}
MapperItem
/// <summary>
/// Encapsulates an item to be mapped and supports conversion from the souce type to the destination type.
/// </summary>
public class MapperItem
{
private MemberInfo memberInfo;
private object target;
private Type type;
private static Dictionary<Tuple<Type, Type>, Func<object, object>> Conversions = new Dictionary<Tuple<Type, Type>, Func<object, object>>();
/// <summary>
/// Constructor. TODO: improve comment
/// </summary>
/// <param name="member"></param>
/// <param name="target"></param>
public MapperItem(MemberInfo member, object target)
{
this.memberInfo = member;
this.target = target;
this.type = this.memberInfo.UnderlyingType();
}
/// <summary>
/// Transfers the value from one mapper item to the other while applying type conversion.
/// </summary>
/// <param name="source"></param>
public void Assign(MapperItem source)
{
this.memberInfo.Assign(this.target, source.Convert(this.type));
}
/// <summary>
/// Allows arbitrary conversions.
/// </summary>
/// <typeparam name="S"></typeparam>
/// <typeparam name="T"></typeparam>
/// <param name="converter"></param>
public static void AddConversion<S, T>(Func<object, object> converter)
{
Conversions.Add(Tuple.Create(typeof(S), typeof(T)), converter);
}
private object Value
{
get
{
return this.memberInfo.Value(this.target);
}
}
private object Convert(Type convertToType)
{
object converted = null;
if (this.Value == null)
{
return converted;
}
else if (convertToType.IsAssignableFrom(this.type))
{
converted = this.Value;
}
else
{
var conversionKey = Tuple.Create(this.type, convertToType);
if (Conversions.ContainsKey(conversionKey))
{
converted = Conversions[conversionKey](this.Value);
}
else
{
throw new Exception(convertToType.Name + " is not assignable from " + this.type.Name);
}
}
return converted;
}
}
Reflection extensions:
public static class ReflectionExtensions
{
public static Type UnderlyingType(this MemberInfo member)
{
Type type;
switch (member.MemberType)
{
case MemberTypes.Field:
type = ((FieldInfo)member).FieldType;
break;
case MemberTypes.Property:
type = ((PropertyInfo)member).PropertyType;
break;
case MemberTypes.Event:
type = ((EventInfo)member).EventHandlerType;
break;
default:
throw new ArgumentException("member must be if type FieldInfo, PropertyInfo or EventInfo", "member");
}
return Nullable.GetUnderlyingType(type) ?? type;
}
public static object Value(this MemberInfo member, object target)
{
if (member is PropertyInfo)
{
return (member as PropertyInfo).GetValue(target, null);
}
else if (member is FieldInfo)
{
return (member as FieldInfo).GetValue(target);
}
else
{
throw new Exception("member must be either PropertyInfo or FieldInfo");
}
}
public static void Assign(this MemberInfo member, object target, object value)
{
if (member is PropertyInfo)
{
(member as PropertyInfo).SetValue(target, value, null);
}
else if (member is FieldInfo)
{
(member as FieldInfo).SetValue(target, value);
}
else
{
throw new Exception("destinationMember must be either PropertyInfo or FieldInfo");
}
}
public static T MemberValue<T>(this object source, string memberName)
{
return (T)source.GetType().GetMember(memberName)[0].Value(source);
}
}