Home > Uncategorized > UrlBinder for model properties using Url as a type

UrlBinder for model properties using Url as a type

February 27th, 2011 Leave a comment Go to comments

I had the simple problem that my Url properties wouldn’t bind when return back into an action on a controller (in asp.net mvc 2). Take the model below, I want to have a destination as an Url. Seems fair?

  public class Banner
      public string Name { get; set; }
      public Url Destination { get; set; }

Looking at the controller code below, when I enter the action where I had done a form post to I just couldn’t see a value in the banner.Destination but I could see one in banner.Name.

  public ActionResult Create(Banner banner)
       // banner had no Destination value but did have Name  
       // ... rest of code

Wow, I thought. No Url. HHhhhhmmmm. But I really want to be strongly typed – all my unit and integration tests worked and I felt that I wanted my domain model to remain, well, true. Given how long it took me to research the solution one suspects I should have just made the Url and string. But I didn’t and I’m glad. The solutions is simple and I understand the pipeline much better.

I need to implement IModelBinder for the model Url (which is used by the property). While Url isn’t a custom class for our domain it is treated as one by mvc. Here is my UrlModelBinder implementation and hooking it up.

  public class UrlBinder : IModelBinder
      public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
          if (!String.IsNullOrEmpty(bindingContext.ModelName))
              var name = bindingContext.ModelName;
              var rawvalue = bindingContext.ValueProvider.GetValue(name).AttemptedValue;
              var value = new ValueProviderResult(rawvalue, rawvalue, CultureInfo.CurrentCulture);
              bindingContext.ModelState.SetModelValue(name, value);
              return new Url(rawvalue);  // I'm unclear that this needs to be returned
          return null;

And then ensuring that it resolves against the Url.

  protected override void  Application_Start()
      ModelBinders.Binders.Add(typeof(Url), new UrlBinder());

So, the key to this solution is understanding – and this is quite obvious now that I understand the implementation in DefaultModelBinder – is that Url is a model and not a property. I kept thinking that I needed bind the form value to the property. Well I do but for a “custom” type. Url is custom because (as the documentation clearly states) it is does not inherit from string. Only string properties are bind by default – that includes enumerations of string (eg IList). So once you know that, it is straightforward that the solution is to implement your own IModelBinder for Url. (If you are interested use Reflector or the source on CodePlex and look through the DefaultModelBinder – you’ll see the way they implement BindSimpleModel and BindComplexModel)

This binder is pretty naive so I expect as I learn more it will be improved. Something I didn’t expect was that I don’t really need to construct an Url because in the model binding at this stage I just need be able to have a binder for the type rather than worry about constructing the type. I do this by putting rawvalue into the ValueProviderResult – it only accepts strings. This code then gets picked up again in the DefaultModelBinder when it constructs the model (in this case Banner) which it then easily creates a new Url(value).

A simple question, why isn’t there a binder for Url already in the code? Is not Url effectively primitive for the web? Clearly not.

If you’ve got this far and are interested there are other ways that you really don’t want to use to solve this problem:

  • use an action filter ([Bind(exclude="Destination")]) and then read it from the Request.Form
  • implement my own IValueProvider on the property banner
  • rewrite the ModelState explicitly clearing errors first
  • implement an IModelBinder for the Banner class
  • and the biggest and most heavy-handed of all, subclass DefaultModelBinder

Luckily, none of these solutions are needed. None will feel right because they are either too specific to the class or property, too kludgy because they use Request.Form and just way too big for a small problem.

Categories: Uncategorized Tags:
  1. April 14th, 2011 at 07:48 | #1

    Wow! Great thiinnkg! JK

  1. No trackbacks yet.