ASP.NET MVC 2.0 Undocumented Model String Property Binding Breaking Change
September 16, 2010 4 Comments
The default behavior of Model binding in ASP.NET MVC 1.0 was to initialize strings to string.Empty. However, MVC 2.0 defaults to initializing strings to null. Unfortunately, this is not listed as one of the breaking changes from MVC 1.0.
This can be a big problem if you have a substantial site built in MVC 1.0 and you import it into Visual Studio 2010 and use MVC 2.0. You may find that you are suddenly throwing NullReferenceException during model binding on code that is working in production.
Locating the Issue
ASP.NET MVC source code is available under the Ms-PL open source license which means that it is possible to diff the two versions of MVC and figure out what changed without having to guess or disassemble anything.
protected virtual object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder) { object value = propertyBinder.BindModel(controllerContext, bindingContext); if (bindingContext.ModelMetadata.ConvertEmptyStringToNull && Object.Equals(value, String.Empty)) { return null; } return value; }
MVC 2.0 adds some indirection to the binding behavior of the DefaultModelBinder class via a new ModelMetadata class which governs some of the behavior of DefaultModelBinder. Of particular interest here is that there is a property named ConvertEmptyStringToNull which, when true, causes an empty string to be returned as null instead.
It also turns out that when ModelMetaData is constructed, the value of ConvertEmptyStringToNull is set to true and there is no code in MVC which changes that default value.
Restoring MVC 1.0 Model String Property Binding Behavior
I think the quickest solution is going to be to set that ConvertEmptyStringToNull property to false by providing a creating a new, custom default implementation of IModelBinder by inheriting from DefaultModelBinder and changing the ConvertEmptyStringToNull value to false on the internal ModelMetaData object.
public sealed class EmptyStringModelBinder : DefaultModelBinder { public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { bindingContext.ModelMetadata.ConvertEmptyStringToNull = false; return base.BindModel(controllerContext, bindingContext); } }
Then the Application_Start() method of Global.asax.cs, we set the DefaultBinder property of ModelBinderDictionary to use our custom type instead of leaving it as the default. This is exposed through the Binders property on the static ModelBinders class so that we get our IModelBinder as the default instead of DefaultModelBinder.
protected void Application_Start() { ModelBinders.Binders.DefaultBinder = new EmptyStringModelBinder(); RegisterRoutes( RouteTable.Routes ); }
Don’t work for such types
public partial class BilingualString
{
public string RuString { get; set; }
public string EnString { get; set; }
}
public partial class Member
{
public Member()
{
this.DisplayName = new BilingualString();
}
public BilingualString DisplayName { get; set; }
}
Pingback: MVC.NET 2 Breaking Changes – Simon Dean’s Blog
Pingback: [Gist] ASP.NET MVC bind String property as string.Empty instead of null » Johannes' Blog
Pingback: Dynamics GP Land