为什么ASP.Net MVC模型绑定器将空JSON数组绑定到null?

40

这是我的模型类:

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

将下面的JSON结构对象传递给MVC控制器,并使用MyEmpls作为空数组。

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

控制器

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

我期望 mode.MyEmpls 是一个空的 C# 数组,而不是 null。是否需要使用自定义模型绑定器才能实现空数组?


当您通过JSON提供员工时,“MyEmpls”不为空吗? - sroes
为什么您不在操作方法中检查myEmpls是否为null,如果是,则将其定义为一个空的c#数组? - stackunderflow
7个回答

37
我认为其他答案中有些人没有理解这个问题的含义:为什么默认的MVC模型绑定器将空的Json数组绑定为null而不是空的C#数组?
好吧,我不能告诉你他们为什么那样做,但我可以向你展示它发生的位置。MVC的源代码可以在CodePlex上找到,链接为:http://aspnetwebstack.codeplex.com/SourceControl/latest。您需要查看的文件是ValueProviderResult.cs,您可以在其中看到以下内容:
    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);
    }
}

有趣的部分是“case 3”:
else
{
    // case 3(a): source is empty array, so can't perform conversion
    return null;
}

您可以通过在模型的构造函数中初始化数组来避免此问题。在我快速阅读源代码时,我无法告诉您为什么它们不能返回空数组或者为什么它们决定不这样做,但这应该会让阅读更有趣。

1
似乎问题在于destinationType.IsArray对于List<>类返回false,而对于数组类型则有效。 - ghord
在代码中,OP发布了Employee[]而不是List<Employee>,所以除非这是一个错误,否则我就不知道该怎么办了... - Richiban
我认为实际问题在DefaultModelBinder https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Mvc/DefaultModelBinder.cs的711行,如果构建的`objectList`为空,则返回null。请查看此链接:https://lostechies.com/jimmybogard/2013/11/07/null-collectionsarrays-from-mvc-model-binding/ - bigbearzhu
有没有一种方法可以覆盖DefaultModelBinder的定义,以便在反序列化后保留空数组作为空数组?这个想法是我的控制器方法与HTTPPatch相关联,并且期望特定的数组为空,以便进行处理。 - Rohit V
2
请注意,在MVC Core中,此行为已更改为预期的空JSON数组绑定到空集合的行为。 - Derek Greer
显示剩余2条评论

30

你得到了一个空值,因为在C#中引用类型的默认值就是空值。如果你想得到一个空数组,你需要在你的模型中使用构造函数初始化数组。然而,由于在初始化时需要定义数组的大小,所以最好使用另一种类型的集合,比如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>();
    }
}
当从json中传入一个空数组时,您将会得到一个空列表。
如果您真的必须使用数组,请初始化其大小:
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*/];
    }
}

这是一个被用作 MyModel 的 WCF 代理类。因此,我认为他可能无法控制将 Employee[] 更改为 List<Employee> - HCJ
2
空值和空白含义完全不同。在我的情况下 - 指定对模型的更改。空值表示忽略此属性,保持原样。空白表示删除所有元素。 - Julian Mann

0
[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可以正确地反序列化空数组。 因此,您可以忽略传入的模型并从输入请求流中重新构建它。 可能不是正确的方法,但如果您只需要执行一次,则可以节省一些工作量。 您需要引用System.Web.Extensions。


0
如果您发现model.MyEmpls为空,那么您可以在服务器端创建一个条件来阻止引发异常,例如:
if(model.MyEmpls !=null){
...
}

你得到了null,因为你的MyEmpls是一个自定义类数组,而你只发送了[]。

希望这可以帮助你。


0
尝试按照以下方式创建模型绑定器。
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
        }
    }
}

在“应用程序启动”事件里注册模型绑定器。
ModelBinders.Binders.Add(typeof(MyModel), new MyModelBinder())

并将控制器修改为

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

0

你可以定义一个setter来检查值是否为空

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;}
}

-1

C#数组初始化的默认行为是null而不是空数组

因此,如果您发送一个空数组,您将不会初始化一个空数组,而是触发默认的null初始化

请参见以下链接http://www.dotnetperls.com/null-array


我认为这个问题所关注的问题是,在C#中,JSON中的[]被反序列化为null而不是new [] {}。这种行为对我来说似乎很奇怪,因为我认为一个空的C#数组比null更接近于JSON数组。 - Sam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接