Image Image Image Image Image
Scroll to Top

To Top

AOP Aspect-oriented programming (AOP) is a pragmatic passion that I document here.

27

Jan
2013

inAOP

vonJohannes Hoppe

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

On 27, Jan 2013 | inAOP | vonJohannes Hoppe

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!