Friday, March 16, 2012

Extending the Value Injector beyond one level properties mapping between entities

For many enterprises application, we may have entities in different layer and we would be needing an easy way to map the properties between them. There are many options available but I used Value Injector.

But this has two problems :

  1. It will not work for nested properties. It just converts the one level properties which are basic types
  2. It will not work for IEnumerable types.

The good thing that l like about Value Injector is it is extendable. So this how I solved both the above problems.

Extend ConventionInjection to write logic for multiple inner level mappings. This code uses the power of reflection and recursion to go into multiple inner levels.

public class CloneInjection : ConventionInjection
{
protected override bool Match(ConventionInfo c)
{
return c.SourceProp.Name == c.TargetProp.Name && c.SourceProp.Value != null;
}

protected override object SetValue(ConventionInfo c)
{
//for value types and string just return the value as is
if (c.SourceProp.Type.IsValueType || c.SourceProp.Type == typeof(string))
{
return c.SourceProp.Value;
}

//handle arrays
if (c.SourceProp.Type.IsArray)
{
var arr = c.SourceProp.Value as Array;
var clone = arr.Clone() as Array;

for (int index = 0; index < arr.Length; index++)
{
var a = arr.GetValue(index);
if (a.GetType().IsValueType || a.GetType() == typeof(string)) continue;
clone.SetValue(Activator.CreateInstance(a.GetType()).InjectFrom<CloneInjection>(a), index);
}
return clone;
}


if (c.SourceProp.Type.IsGenericType)
{
//handle IEnumerable<> also ICollection<> IList<> List<>
if (c.SourceProp.Type.GetGenericTypeDefinition().GetInterfaces().Contains(typeof(IEnumerable)))
{
var t = c.SourceProp.Type.GetGenericArguments()[0];

if (t.IsValueType || t == typeof(string))
{
return c.SourceProp.Value;
}

t = c.TargetProp.Type.GetGenericArguments()[0];

var tlist = typeof(List<>).MakeGenericType(t);
var list = Activator.CreateInstance(tlist);

var addMethod = tlist.GetMethod("Add");
foreach (var o in c.SourceProp.Value as IEnumerable)
{
var e = Activator.CreateInstance(t).InjectFrom<CloneInjection>(o);
addMethod.Invoke(list, new[] { e }); // in 4.0 you can use dynamic and just do list.Add(e);
}
return list;
}

//unhandled generic type, you could also return null or throw
return c.SourceProp.Value;
}

//for simple object types create a new instance and apply the clone injection on it
return Activator.CreateInstance(c.TargetProp.Type)
.InjectFrom<CloneInjection>(c.SourceProp.Value);
}

Use this custom Injection class while mapping between two entities so that irrespective of multiple nested properties, you can easily clone to different type


Ex:


 

target.InjectFrom<CloneInjection>(source);

Write your own Extension method to consider mapping between two enumerable or any type entities. Again here also we check of generic types for two entities and if they are IEnumerable, construct the list items of target type using reflection and add it to target object

public static object InjectCompleteFrom(this object target, object source)
{
if (target.GetType().IsGenericType &&
target.GetType().GetGenericTypeDefinition() != null &&
target.GetType().GetGenericTypeDefinition().GetInterfaces() != null &&
target.GetType().GetGenericTypeDefinition().GetInterfaces().Contains(typeof(IEnumerable)) &&
source.GetType().IsGenericType &&
source.GetType().GetGenericTypeDefinition() != null &&
source.GetType().GetGenericTypeDefinition().GetInterfaces() != null &&
source.GetType().GetGenericTypeDefinition().GetInterfaces().Contains(typeof(IEnumerable)))
{
var t = target.GetType().GetGenericArguments()[0];

var tlist = typeof(List<>).MakeGenericType(t);
var addMethod = tlist.GetMethod("Add");

foreach (var sourceItem in source as IEnumerable)
{
var e = Activator.CreateInstance(t).InjectFrom<CloneInjection>(sourceItem);
addMethod.Invoke(target, new[] { e });
}

return target;
}
else
{
return target.InjectFrom<CloneInjection>(source);
}
}

This is how you will need to use it:

IEnumerable<Category> categoryList = categoryService.GetAll();
IList<CategoryViewModel> viewModelList = new List<CategoryViewModel>();
viewModelList.InjectCompleteFrom(categoryList);

I have answered question related to this at http://stackoverflow.com/questions/7872405/how-to-map-lists-with-valueinjector/9735614#9735614


I will plan to add this in my MVC tool kit project at http://mvctoolkit.codeplex.com/


To know more about this project, refer http://sureshballa.blogspot.in/2012/03/my-first-community-contribution-mvc.html