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

Saturday, March 3, 2012

My First Community Contribution - MVC Tool Kit Framework

 

I like the fact that Microsoft started supporting open source java script frameworks like JQuery, JQuery Validations etc. And now Microsoft is also going to start supporting Knockout JS which is great framework for data bindings with MVVM pattern. Also now web is slowly moving towards HTML 5 leaving away all proprietary frameworks like flash and Silverlight. Considering all these, I always had a wish that reusable features/controls that leverages the power of HTML5, Knockout JS(client JS library based on MVVM pattern) and data annotations would be great value add to developer which abstracts all underlying technologies that I mentioned and gives the super powers. Out of this is what came out is “MVC Tool Kit Framework” Yes, I have started creating some reusable features and wanted to make available to public. 1st release is out and you can get it from http://mvctoolkit.codeplex.com/. Thanks to my organization Neudesic for supporting this activity.

 

So what does this framework have?

At high level these are the features that it provides:

  1. Basic Input Control that supports HTML 5, Knockout JS and custom attributes in Data Annotations
  2. Validation Framework and some common validation attributes
  3. Checkbox list control that doesn’t exists in ASP.NET MVC

So what's so great about this framework?

Use this framework for normal ASP.NET MVC post back or with Knockout JS framework. Additionally get the benefits of  custom data annotation attributes and validation attributes that are present in this framework.

Input Controls

Each of input controls have these parameters:

  1. Expression<Func<TModel, TProperty>> expression: This is the model property expression for which you want the control to bind.
  2. InputType inputType: This is to specify what type of input control you want. The frame work supports all HTML 5 input types - Text, Color, Date, Datetime, DatetimeLocal, Email, Month, Number, Range, Search,Tel, Time, Url, Week
  3. object htmlAttributes: This is for any html attributes that you would wanted to be generated.
  4. object dataAttributes: This is for generating html 5 data attributes (data-*)
  5. KOBinding<TModel>[] koBindAttributes: This is for generating Knockout data bind attributes. The specialty about this parameter is that you can bind Knockout bindings based on server model itself
  6. object koCustomBindAttributes: Use this parameter for Knockout data bind attributes to bind some properties to custom defined JS model properties or methods which will not be in server side model.

All of the above parameters (except the 1st one) are optional parameters.

Specific to HTML 5 attributes, these controls will generate the following additional attributes also:

  1. placeholder (based on data annotation meta attribute Html5PlaceholderAttribute)
  2. required (based on data annotation RequiredAttribute which is system attribute)
  3. patters (based on data annotation RegularExpressionAttribute which is system attribute)

Note: Html5PlaceholderAttribute is custom attribute defined in this framework, if you wish to use this attribute, please register the CustomModelMetadataProvider (defined as part of this framework) in your global.ascx file.

image

Usage

image

will generate

image

Validation Framework

The following attributes are defined which can be used for Model validation.

  • Comparer. Use this to have some comparison rule between model properties. Example:

imageimage                          This attribute supports following comparison attributes: EqualTo, NotEqualTo, GreaterThan, GreaterThanEqualTo, LessThan, LessThanEqualTo

  • RequiredIf: Required validation based on other property. Example: image

Also validation framework has utilities that can validate the model at any time (at presentation layer, business layer etc.) to ensure its following all the rules defined to it has validation attributes.

image

 

The code hosted at http://mvctoolkit.codeplex.com/ also has web sample that has usage of these framework features. So go ahead and start using it.

I wish to see some more ideas to extend this framework and comments on this. Please reach out to me at suresh.balla@neudesic.com for any questions/ suggestions. Contributions are welcome for this framework.

I plan to add more complex controls in future and extend this framework. Follow the project at codeplex for the updates. I will also post the updates here in my blog.