Friday, July 17, 2009

ModelBinders and Factory Methods

Where I am working now all our models use a factory to create instanced versions of themselves. The constructors are made private to enforce this business rule. Therefor, in order to get a new customer object:

    var contact Contact.Create();

(Sorry for the formatting below... still trying to work out the prettifier tool)

However, when you are using the DefaultModelBinder in ASP.NET MVC to populate your models this can create an issue for you. Take this totally average example:

    [AcceptVerbs(HttpVerbs.Post)]

    
public ActionResult Index([Bind(Exclude="Id")] Contact contact)

    
{

      
if (!ModelState.IsValid)



         
return View();



      
contact.Save();

      
return RedirectToAction("Thanks");

    
}



The problem exposed here is that the DefaultModelBinder which runs in the background to populate (and validate) the incoming contact will cause an exception. It requires a public, parameter-less constructor to work.

The most flexible way to get around this (without breaking our business rule of not exposing constructors) is to make your own model binder that understands how to deal with these factory methods.

Your new class should inherit from DefaultModelBinder and the method you need to override is CreateModel. Here is an example of what I did:


    
public class XLDataAnnotationsModelBinder DefaultModelBinder

    {



        
/// <summary>

        /// We override this because most of our models do not have publicly accessible constructors, which the the

        /// default model binder requires.  Here we check to see if there is a public, static Create method available

        /// and, if so, use that to create the blank model.

        /// </summary>

        
protected override object CreateModel(ControllerContext controllerContextModelBindingContext bindingContextSystem.Type modelType)

        
{

            Type typeToCreate 
modelType;



            
// most XL models have a static, parameterless Create() method we should use ...

            
var createMethod GetParameterlessCreate(typeToCreate);

            
if(createMethod != null)

            
{

                
return createMethod.Invoke(nullnull);

            
}

            

            
// we can understand some collection interfaces, e.g. IList<>, IDictionary<,>

            
if (modelType.IsGenericType{

                Type genericTypeDefinition 
modelType.GetGenericTypeDefinition();

                
if (genericTypeDefinition == typeof(IDictionary<,>)) {

                    typeToCreate 
= typeof(Dictionary<,>).MakeGenericType(modelType.GetGenericArguments());

                
}

                
else if (genericTypeDefinition == typeof(IEnumerable<>) || genericTypeDefinition == typeof(ICollection<>) || genericTypeDefinition == typeof(IList<>)) {

                    typeToCreate 
= typeof(List<>).MakeGenericType(modelType.GetGenericArguments());

                
}

            }



            
// fallback to the type's default constructor

            
return Activator.CreateInstance(typeToCreate);

            

        
}



        
private MethodInfo GetParameterlessCreate(Type typeToCreate)

        
{

            var methodInfos 
typeToCreate.GetMethods(BindingFlags.Static BindingFlags.Public);

            
foreach (var methodInfo in methodInfos)

            
{

                
if (methodInfo.IsPublic && methodInfo.IsStatic && methodInfo.Name.Equals("Create") && methodInfo.GetParameters().Length == 0)

                    
return methodInfo;

            
}



            
return null;

        
}