Archive for August 22nd, 2010
Asp.net MVC 2: Creating a SimpleValuesModelBinder
I have been thinking about what it would take to make a custom model binder for the PayPal IPN service the past week or so, and tonight I finally had the chance to look into it. The goals is simple – I just want to pass the IPN form values to a model that has properties with different names. I don’t want to do any custom model binding, perhaps a small bit of string parsing but that’s about it.
I looked into model binding at the MSDN library, Steve Sanderson’s book, and the TekPub MVC series, they all were good, but what I wanted to do was simple. On thing I did get out of it was that I would need both a custom ModelBinder implementing IModelBinder and possibly a custom ValuesProvider as well.
Finally, I looked into the MVC source for the DefaultModelBinder to see how it worked and came up with pure gold. Here is the basic premise of the solution:
- Create an abstract model binder class that uses the DefaultModelBinder class to do its work.
- Leverage the NameValueCollectionValueProvider class to provide simple Key/Value pairs to the DefaultModelBinder .
- Create a custom copy of the supplied ModelBindingContext that connects the binder and provider together.
Here’s the code with comments:
////all complicated binding set up is abstracted
//DefaultModelBinder is used for ModelBinding
//NameValueCollectionValueProvider is used as the ValueProvider
public abstract class SimpleValuesModelBinder : IModelBinder
{
protected abstract void AppendValues(ControllerContext controllerContext, NameValueCollection collection);
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
//first we want to call AppendValues
//the collection is supplied to the SimpleValuesProvider
//we use the NameValueCollectionValueProvider because it uses key/value pairs
var collection = new NameValueCollection();
AppendValues(controllerContext, collection);
//create a custom binding context
//we pass thru properties except the value provider
//we use the new TestValueProvider here
var customContext = new ModelBindingContext()
{
ModelMetadata = bindingContext.ModelMetadata,
ModelState = bindingContext.ModelState,
PropertyFilter = bindingContext.PropertyFilter,
ValueProvider = new NameValueCollectionValueProvider(collection, CultureInfo.CurrentCulture)
};
//pass our custom binding context the default binding model and bind
return new DefaultModelBinder().BindModel(controllerContext, customContext);
}
}
Creating a custom model binder from this class is very simple. Here is a simple class:
{
public string Value { get; set; }
public DateTime Current { get; set; }
}
… and here is custom binder using the abstract class above:
{
protected override void AppendValues(ControllerContext controllerContext, NameValueCollection collection)
{
collection["Value"] = Guid.NewGuid().ToString();
collection["Current"] = DateTime.Now.ToString();
}
}
The resulting custom model binder is very simple. All you have to do is override one method, AppendValues, and your done. In this case I am supplying arbitrary values as a test, but you can leverage any values you want from the ControllerContext parameter.
Of course set up is easy too, ASP.net MVC allows several ways to do this. For a test, I’ll just apply to a parameter in an action method.
{
return Content(string.Format("Value: {0}; Current: {1}",Test.Value,Test.Current));
}
And the the less than glamorous result:
So, if your looking to do some simple value parsing this may help.