Saturday, April 3, 2010

Breaking? Change In Asp.Net MVC2

In MVC1, by default an empty textbox was sent as string.Empty to the controller on POST. In MVC2, it now sends the value as NULL. See ModelMetadata.ConvertEmptyStringToNull. I think there are some valid reasons for why they made the change – see Brad Wilson’s comment for more. If your system relies on string.Empty, you’ve got a problem.

There are several options for handling this. The first is using the DisplayFormatAttribute on every property that needs it. Painful. Here’s an example:

[DisplayFormat(ConvertEmptyStringToNull=false)]
public string FirstName { get; set; }

You can also write a custom binder that sets this value to false. Like so:

public class CustomStringModelBinder : DefaultModelBinder 
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {

        var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);   
        if (bindingContext.ModelType == typeof(string))
        {   
            bindingContext.ModelMetadata.ConvertEmptyStringToNull = false;
            if (value == null || string.IsNullOrEmpty(value.AttemptedValue))   
                return "";
        }
        return value.AttemptedValue;
    }
}

You’ll need to add the following to the Global.asax Application_Start method to use your custom model binder.

ModelBinders.Binders.Add(typeof(string), new CustomStringModelBinder());

One thing to note, if you use the RequiredAttribute, you don’t need to worry about the null because it will invalidate the model based on the NULL.

Which option is better? Neither. Simply pick the default for ConvertEmptyStringToNull and then use DisplayFormat to handle the exceptions. Not perfect but not bad.

I do wish that this was configurable in some way instead of creating a ModelBinder.

1 comment:

Nicholas Piasecki said...

Thanks for this tip. I was able to use a more concise version of the above:

public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{ bindingContext.ModelMetadata.ConvertEmptyStringToNull = false;

return base.BindModel(controllerContext, bindingContext);
}