Saturday 21 November 2009

NullReferenceException if not using default binding

If you are not using the default binding mechanism in MVC and you are doing you own data validation (i.e. adding your own errors to the ModelState) the HtmlHelper class can throw a NullReferenceException. This occurs when there are validation errors.

System.NullReferenceException was unhandled by user code
  Message="Object reference not set to an instance of an object."
  Source="System.Web.Mvc"
  StackTrace:
       at System.Web.Mvc.HtmlHelper.GetModelStateValue(String key, Type destinationType)
       at 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)
       at System.Web.Mvc.Html.InputExtensions.TextBox(HtmlHelper htmlHelper, String name, Object value, IDictionary`2 htmlAttributes)
       at System.Web.Mvc.Html.InputExtensions.TextBox(HtmlHelper htmlHelper, String name, Object value)
       at ASP.views_salutation_edit_aspx.__RenderContent2(HtmlTextWriter __w, Control parameterContainer) in c:\source\MUI.MVC\Views\Salutation\Edit.aspx:line 25
       at System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children)
       at System.Web.UI.Control.RenderChildren(HtmlTextWriter writer)
       at System.Web.UI.Control.Render(HtmlTextWriter writer)
       at System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter)
       at System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter)
       at System.Web.UI.Control.RenderControl(HtmlTextWriter writer)
       at ASP.views_shared_site_master.__Render__control1(HtmlTextWriter __w, Control parameterContainer) in c:\source\UI.MVC\Views\Shared\Site.Master:line 29
       at System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children)
       at System.Web.UI.Control.RenderChildren(HtmlTextWriter writer)
       at System.Web.UI.Control.Render(HtmlTextWriter writer)
       at System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter)
       at System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter)
       at System.Web.UI.Control.RenderControl(HtmlTextWriter writer)
       at System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children)
       at System.Web.UI.Control.RenderChildren(HtmlTextWriter writer)
       at System.Web.UI.Page.Render(HtmlTextWriter writer)
       at System.Web.Mvc.ViewPage.Render(HtmlTextWriter writer)
       at System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter)
       at System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter)
       at System.Web.UI.Control.RenderControl(HtmlTextWriter writer)
       at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

The MVC Framework will try to find an AttemptedValue for every error it finds so you must add them or MVC will throw an exception.

AttemptedValues are automatically populated when you use default binding, if you call UpdateModel() or by passing the object to bind as a parameter. For example:

public ActionResult Create(MyClass myObject);

If you want to do it yourself you need to call ModelState.SetModelValue. For example:

ModelState.SetModelValue("Name", new ValueProviderResult(ValueProvider["Name"].AttemptedValue, salutation.Name, System.Globalization.CultureInfo.CurrentCulture));