A bold statement, but I stand by it.
I have done numerous searches on how to implement custom model binders in ASP.NET MVC, and all of them are variations of:
public override object GetValue(ControllerContext ctx, string modelName, Type modelType, ModelStateDictionary state){Customer customer = new Customer();customer.FirstName = ctx.HttpContext.Request["FirstName"];customer.LastName = ctx.HttpContext.Request["LastName"];// ... other properties ...return customer;}
But that’s counter productive, isn’t it? ASP.NET MVC is all about conventions and flexibility. The last thing I want from my model binders are to hardcode expected property names, which would fail anyway. If my type is ReportIdentificator, I certainly don’t want to give all my properties and parameters the very same name. I want my freedom to call them foo and Bar, if that’s what I need.
So I finally went to the source, so to speak. The ASP.NET MVC source code is available, and inside of the DefaultModelBinder, I found the following gem:
if (!performedFallback) {ValueProviderResult vpResult;bindingContext.ValueProvider.TryGetValue(bindingContext.ModelName, out vpResult);if (vpResult != null) {return BindSimpleModel(controllerContext, bindingContext, vpResult);}}
The getaway here is bindingContext.ModelName. That property is the key to get the proper ValueProviderResult, which contains the posted values for the model. With that information, I am finally able to create the model binder I want.
The usual binder registration:
ModelBinders.Binders.Add(typeof (ReportIdentifier), new ReportIdentifierBinder());
And my binder:
public class ReportIdentifierBinder : DefaultModelBinder{public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext){var model = (ReportSectionIdentifier)base.BindModel(controllerContext, bindingContext);ValueProviderResult serialized;if (model == null && bindingContext.ValueProvider.TryGetValue(bindingContext.ModelName, out serialized)){var values = serialized.AttemptedValue.Split("-".ToCharArray());model = new ReportSectionIdentifier{Organization = int.Parse(values[0]),Period = int.Parse(values[1]),Report = int.Parse(values[2]),};}return model;}}
I let the base try to create the model first, in those cases where the usual conventions are followed. My implementation deserialize the model from a value on the form “1-2-3”. That code enables me to support drop down lists bound to a report name and its identifier, which posts a value on the form “1-2-3”, or use the identifier in action links, which appends the property values to the querystring according to the conventions.
No comments:
Post a Comment