[Gist] ASP.NET MVC bind String property as string.Empty instead of null

Currently I’m migrating a good-old ASP.NET MVC v1 project to v4.
In ASP.NET MVC 1.0 the default behavior of Model Binding was to initialize strings to String.Empty. Imagine this simple method:

public ActionResult Hello(string name)
{
    if (name.Contains("Bob"))
    {
        return Content("Hi my friend!");
    }
 
    return Content("Hi stranger!");
}

However, later versions of ASP.NET MVC initialize Strings to null. This results in some nasty NullReferenceExceptions all over the code. On the one hand this change of the framework might be reasonable. The default value of a String is null. But on the other hand we should try to avoid null values at all. As soon as we start to tolerate null values we are going to clutter the code with guards against them:

public ActionResult Hello(string name)
{
    if (name != null &&
        name.Contains("Bob"))
    {
        return Content("Hi my friend!");
    }
 
    return Content("Hi stranger!");
}

This is ugly and totally useless, isn’t it? And chances are very high that one check will be forgotten somewhere, too!

One solution would be a modified DefaultModelBinder. A typical solution can be found here. It will set the magical, undocumented Property ConvertEmptyStringToNull of the original Model Binder:

public class EmptyStringModelBinder : DefaultModelBinder 
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        bindingContext.ModelMetadata.ConvertEmptyStringToNull = false;
        return base.BindModel(controllerContext, bindingContext);
    }
}

This works great for simple strings.
But it’s a pity that this won’t work for properties of objects:

public class Person
{
    public string Name { get; set; }
}
 
public ActionResult Hello(Person person)
{
    if (person.Name.Contains("Bob"))
    {
        return Content("Hi my friend!");
    }
 
    return Content("Hi stranger!");
}

The property will be null again! There might be people, who are brave enough to write a suitable Model Binder. But a short peek at the original DefaultModelBinder source code will destroy all hope for a simple solution. So there are people that are going to use this hack:

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

Please, don’t do that! ;-)

I’m going to present a new solution for the problem. My solution makes use of the Aspect Oriented Programming (AOP) framework PostSharp. This framework is the jumbo Swiss army knife for your C# development!

The following aspect will modify all input after the original model binding has been applied:

using System;
using System.Linq;
using System.Reflection;
using MySolution;
using PostSharp.Aspects;
using PostSharp.Extensibility;
 
[assembly: EmptyStringModelBindingAspect(
    AttributeTargetTypes = @"regex:[^\.]*\.Controllers\..*Controller",
    AttributeTargetTypeAttributes = MulticastAttributes.Public,
    AttributeTargetElements = MulticastTargets.Method,
    AttributeTargetMemberAttributes = MulticastAttributes.Public)]
 
namespace MySolution
{
    [Serializable]
    public class EmptyStringModelBindingAspect : MethodInterceptionAspect
    {
        public override void OnInvoke(MethodInterceptionArgs args)
        {
            for (int i = 0; i < args.Arguments.Count; i++)
            {
                FixString(args, i);
                FixStringsInObjects(args.Arguments[i]);
            }
            args.Proceed();
        }
 
        private static void FixString(MethodInterceptionArgs args, int i)
        {
            if (args.Arguments[i] is string &&
                args.Arguments[i] == null)
            {
                args.Arguments.SetArgument(i, string.Empty);
            }
        }
 
        private static void FixStringsInObjects(object obj)
        {
            if (obj == null)
            {
                return;
            }
 
            Type type = obj.GetType();
            PropertyInfo[] properties = (from p in type.GetProperties()
                                         where p.PropertyType == typeof(string) &&
                                               p.CanRead &&
                                               p.CanWrite &&
                                               p.GetValue(obj, null) == null
                                         select p).ToArray();
 
            foreach (PropertyInfo item in properties)
            {
                item.SetValue(obj, string.Empty, null);
            }
        }
 
        public override bool CompileTimeValidate(MethodBase method)
        {
            return !(method.Name.StartsWith("get_") || method.Name.StartsWith("set_"));
        }
    }
}

This Gist is available on GitHub.

The aspect will be applied automatically to all public methods of all controllers. Just install PostSharp via Nuget (the free Starter Editions of v2 or the v3-alpha will both work fine) and paste the given file into your solution. Problem solved!

Trackback URL

3 Comments on "[Gist] ASP.NET MVC bind String property as string.Empty instead of null"

  1. 1. Johannes Hoppe (@JohannesHoppe)
    28/01/2013 at 00:43

    http://t.co/ytJZr40v – model bind String property as string.Empty instead of null #aspnetmvc #postsharp #gist

  2. 2. Lelala
    14/12/2013 at 13:01

    Why not using the annotattions?

    >> [DisplayFormat(ConvertEmptyStringToNull = false)]

    This is what the thing was invented for, or am i wrong?

    Regards

    • 3. Johannes Hoppe
      16/12/2013 at 17:30

      You are right. And you might want to take a closer look at the article, I discussed ConvertEmptyStringToNull and the reason not to use it. Maybe the postsharp aspect is a bit overdesigned. I spotted this on StackOverlow:

      
      public class CustomModelMetadataProvider : DataAnnotationsModelMetadataProvider
      {
          protected override ModelMetadata CreateMetadata(IEnumerable attributes, System.Type containerType, System.Func modelAccessor, System.Type modelType, string propertyName)
          {
              var modelMetadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
              if (string.IsNullOrEmpty(propertyName)) return modelMetadata;
      
              if (modelType == typeof(String))
                      modelMetadata.ConvertEmptyStringToNull = false;
      
          }
      }
      

      See: http://stackoverflow.com/questions/7459560/set-default-for-displayformatattribute-convertemptystringtonull-to-false-across


Connect with Facebook

Hi Stranger, leave a comment:

ALLOWED XHTML TAGS:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">

Subscribe to Comments
Projekt-
Verfügbarkeit:
ab 15.01.2013