Search code examples
c#asp.net-mvcjsonwcfmodel-binding

Why does the ASP.Net MVC model binder bind an empty JSON array to null?


Here is my model class:

public class MyModel
{
    public Employees[] MyEmpls{get;set;}
    public int Id{get;set;}
    public OrgName{get;set;}
}

Passing the below JSON structure object with MyEmpls as empty array to MVC controller.

["Id":12, "MyEmpls":[], "OrgName":"Kekran Mcran"]

Controller

[HttpPost]
public ActionResult SaveOrg(MyModel model)
{
  //model.MyEmpls is null here
}

I am expecting mode.MyEmpls to be an empty c# array, not a null. Is a custom model binder necessary to achieve an empty array?


Solution

  • I think that some of the other answers have missed the meaning of the question: why does the default MVC model binder bind an empty Json array to null instead of an empty C# array?

    Well, I can't tell you why they did that, but I can show you where it happens. The source for MVC can be found on CodePlex here: http://aspnetwebstack.codeplex.com/SourceControl/latest. The file you're looking for is ValueProviderResult.cs where you can see:

        private static object UnwrapPossibleArrayType(CultureInfo culture, object value, Type destinationType)
        {
            if (value == null || destinationType.IsInstanceOfType(value))
            {
                return value;
            }
    
            // array conversion results in four cases, as below
            Array valueAsArray = value as Array;
            if (destinationType.IsArray)
            {
                Type destinationElementType = destinationType.GetElementType();
                if (valueAsArray != null)
                {
                    // case 1: both destination + source type are arrays, so convert each element
                    IList converted = Array.CreateInstance(destinationElementType, valueAsArray.Length);
                    for (int i = 0; i < valueAsArray.Length; i++)
                    {
                        converted[i] = ConvertSimpleType(culture, valueAsArray.GetValue(i), destinationElementType);
                    }
                    return converted;
                }
                else
                {
                    // case 2: destination type is array but source is single element, so wrap element in array + convert
                    object element = ConvertSimpleType(culture, value, destinationElementType);
                    IList converted = Array.CreateInstance(destinationElementType, 1);
                    converted[0] = element;
                    return converted;
                }
            }
            else if (valueAsArray != null)
            {
                // case 3: destination type is single element but source is array, so extract first element + convert
                if (valueAsArray.Length > 0)
                {
                    value = valueAsArray.GetValue(0);
                    return ConvertSimpleType(culture, value, destinationType);
                }
                else
                {
                    // case 3(a): source is empty array, so can't perform conversion
                    return null;
                }
            }
            // case 4: both destination + source type are single elements, so convert
            return ConvertSimpleType(culture, value, destinationType);
        }
    }
    

    The interesting part is "case 3":

    else
    {
        // case 3(a): source is empty array, so can't perform conversion
        return null;
    }
    

    You can sidestep this issue by initialising your array on the model in its constructor. In my quick reading of the source I can't tell you why they can't return an empty array or why they decide not to, but it should make for interesting reading.