Question

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?

Était-ce utile?

La 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.

Autres conseils

You're getting a null value as this is the default value for a reference type in C#. In order to get an empty array you will need to initialise the array in your model using a constructor. However as you will need to define the size of the array when it's initialized it might be better using another type of collection such as a List:

public class MyModel
{
    public List<Employees> MyEmpls{get;set;}
    public int Id{get;set;}
    public OrgName{get;set;}

    public MyModel() 
    {
         MyEmpls = new List<Employees>();
    }
}

You will then get an empty list when an empty array is passed from the json.

If you really have to use an array just initialise it with a size:

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

    public MyModel() 
    {
         MyEmpls = new Employees[/*enter size of array in here*/];
    }
}

If you are getting model.MyEmpls as null then you can create a condition on server side to stop raising exception like:

if(model.MyEmpls !=null){
...
}

And you are getting it null because your MyEmpls is a Custom Class array and you are sending just [].

Hope this helps you.

Try to create a model binder as in below

public class MyModelBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        try
        {                
            var request = controllerContext.HttpContext.Request;

            return new MyModel
            {
                MyEmpls = request[] ?? new Employees[0],
                Id = request["Id"] ?? "",
                OrgName = request["OrgName"] ?? ""

            };
        }
        catch 
        {
            //do required exception handling
        }
    }
}

In Application_Start register the model binder

ModelBinders.Binders.Add(typeof(MyModel), new MyModelBinder())

And modify the controller as

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

you can define a setter that checks if the value is null

public class MyModel
{
    private _myEmpls{get;set;}
    public Employees[] MyEmpls{
     get{return _myEmpls;}
     set{_myEmpls=(value==null?new List<Employees>():value);}
    }

    public int Id{get;set;}
    public OrgName{get;set;}
}
[HttpPost]
public ActionResult SaveOrg(MyModel model)
{
    var serializer = new JavaScriptSerializer();
    var stream = System.Web.HttpContext.Current.Request.InputStream;
    var reader = new StreamReader(stream);
    stream.Position = 0;
    var json = reader.ReadToEnd();
    model= serializer.Deserialize<MyModel>(json);
    //model.MyEmpls is [] here
}

JavaScriptSerializer deserializes empty arrays properly. So you can ignore the passed-in model and rebuild it from the input request stream. Probably not the correct way, but if you only need to do it once it saves some effort. You'll need to reference System.Web.Extensions.

The default behavior of C# array initialization is to null not empty array

So if you send an empty array you will not initiate an empty array, but you will trigger the default initialization to null

See the following link http://www.dotnetperls.com/null-array

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top