MVC Beta to RTW upgrade issue – AddModelError and NullReferenceExceptions


Since we use quite a lot of the MVC extension points on our current project, we knew that we’d suffer some upgrade pains going from release to release of MVC.  This isn’t that new for us, as we use a variety of open source projects.  But for a Beta product, it’s almost guaranteed that you’ll run into some issues with upgrades.

Our upgrade has been…less than fun.  Besides the heinous Visual Studio crashing issues, we were bitten by a wide variety of API changes.  One in particular gave me the fits for about two weeks, a multi-headed hydra that I couldn’t quite fix.

In our applications, we use ModelState errors to bubble up validation errors to the UI.  Most of these aren’t manually pushed in, but rather done through model binding.  We use a variety of validation in our application, including Castle Validators, as well as custom business rules that still bubble up to the UI.  From “Start Date is required” to “Start Date cannot be a future date” to “Start Date cannot come before the first contract start date”.

We hid all this behind a few classes, but at some point, we had to add something like this:

public void SomeAction()
{
    ModelState.AddModelError("SomeKey", "I am a required field.");
}

Very innocuous.  We had a required field, it wasn’t submitted as part of the form (and didn’t even come across the request), and we added a model error.  The problem shows it ugly head if you try and use MVC’s built-in HtmlHelper to generate HTML.  Its input element generators are “aware” of ModelState and ViewState, and will try and use these pieces to do things like populate the value of an HTML element.  What we wound up getting was an error something like this:

[NullReferenceException: Object reference not set to an instance of an object.]
   System.Web.Mvc.HtmlHelper.GetModelStateValue(String key, Type destinationType) +63
   System.Web.Mvc.Html.InputExtensions.InputHelper(HtmlHelper htmlHelper, InputType inputType, String name, Object value, Boolean useViewData, Boolean isChecked, Boolean setId, Boolean isExplicitValue, IDictionary`2 htmlAttributes) +519
   System.Web.Mvc.Html.InputExtensions.TextBox(HtmlHelper htmlHelper, String name, Object value, IDictionary`2 htmlAttributes) +34
   System.Web.Mvc.Html.InputExtensions.TextBox(HtmlHelper htmlHelper, String name, Object value) +61

The fix wound up being for us to create a “SafeAddModelError” extension method.  The HtmlHelpers expect some ModelState ValueProviderResult to exist, but it doesn’t because we do our own custom validation.  Long, long story short, here was our fix:

public static void SafeAddModelError(this ModelStateDictionary state, string key, string errorMessage)
{
    state.AddModelError(key, errorMessage);
    if (state[key].Value == null)
    {
        state[key].Value = new ValueProviderResult(null, null, null);
    }
}

I have no idea if this error helps anyone but me, but hey, I enjoy sharing my pain.

A better Model Binder