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;

        
}

Monday, December 22, 2008

Word War 2 : Updated to version 1.1

I've made several changes to the game over the last several days.

  • added 8 letter words to the dictionary
  • in solo player mode, the review screen now shows all possible words
  • better sorting the award list in the review screen
  • many new awards
  • Thursday, December 18, 2008

    Word Wars II : No Mercy

    Its here! Finally...



    Word Wars II is a small word game you can play by yourself or with friends. I built this to learn Windows Presentation Foundation (the look and feel parts of the game) and to refresh myself Windows Communication Foundation (the network part of the game).

    You can get the game from this link.

    WARNING: If you don't already have .NET 3.5 installed, the first time downloading will take a really long time!

    Its a ClickOnce application which means that it will install the game and any required things from Microsoft (.NET 3.5 service pack 1). Any updates I make to the game in the future will automatically update you whenever you launch the game.

    The game puts the icon in the Magrathea Labs folder in the Start Menu.

    Some things you should know (since I don't adequately explain them in the game):

    1) When you start the game, go to Options.
    2) Select the Default user from the list.
    3) Click the Rename button.
    4) Type in a new name and press Enter
    5) Hit the ESC key.

    Also, in the chat window you can type /help and hit Enter. A couple of commands are there.

    Other items of note:

    1) The dictionary has vulgar words in it. Use caution in what words you make when playing with children.

    2) The dictionary is currently lacking 8 letter words. It won't know what you mean if you correctly enter one. Sorry, but the dictionary is a huge project. If anyone want to help me improve it let me know via a comment here.

    3) There are occasionally bugs that crash the game... usually while trying to connect to another game. This is usually caused by the host, not the client. The host will likely need to restart the game to fix this. I'm still trying to find a way to fix this.

    4) Technical note: the games defaults to port 9002. Also, when hosting a game on a LAN, the host broadcasts to all machines on the LAN using port 5050.

    Have fun!

    Wednesday, December 10, 2008

    Beware the Constructor!

    If you find yourself getting the dreaded Loading Error on a WPF page the first place you should look is the constructor of the control is trying to load.

    I had a problem for two day which was finally resolved by moving this line:

    ListBoxMessages.ItemsSource = Helper.Messages;

    From the UserControl constructor to the Loaded event handler.