【问题标题】:Why does the ASP.Net MVC model binder bind an empty JSON array to null?为什么 ASP.Net MVC 模型绑定器将空 JSON 数组绑定到 null?
【发布时间】:2014-05-31 06:09:55
【问题描述】:

这是我的模型类:

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

将下面带有MyEmpls as empty array的JSON结构对象传递给MVC控制器。

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

控制器

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

我希望 mode.MyEmpls 是一个空的 c# 数组,而不是 null。实现空数组是否需要自定义模型绑定器?

【问题讨论】:

  • 当您通过 JSON 提供员工时,MyEmpls 不为空?
  • 如果 myEmpls 为空,为什么不检查您的操作方法,然后将其定义为一个空的 c# 数组?

标签: c# asp.net-mvc json wcf model-binding


【解决方案1】:

我认为其他一些答案错过了问题的含义:为什么默认的 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);
    }
}

有趣的部分是“案例3”:

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

您可以通过在其构造函数中初始化模型上的数组来回避这个问题。在我对源代码的快速阅读中,我无法告诉你为什么他们不能返回一个空数组或为什么他们决定不返回,但它应该是有趣的阅读。

【讨论】:

  • 问题似乎是destinationType.IsArrayList&lt;&gt; 类返回false,它适用于数组类型。
  • 在 OP 发布的代码中,他 使用了 Employee[] 而不是 List,所以除非这是一个错误,否则我会不知所措...跨度>
  • 我认为实际问题出在 DefaultModelBinder github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/… 第 711 行,如果构建的 objectList 不包含任何内容,它将返回 null。看看这个:lostechies.com/jimmybogard/2013/11/07/…
  • 有没有办法覆盖DefaultModelBinder的定义,让一个空数组在反序列化后保留为一个空数组?这个想法是我的 Controller 方法与 HTTPPatch 相关联,并且这个特定的数组预计为空,以便对其进行处理。
  • 请注意,在 MVC Core 中,此行为已更改为空 JSON 数组绑定到空集合的预期行为。
【解决方案2】:

您将得到一个空值,因为这是 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*/];
    }
}

【讨论】:

  • 这是一个 WCF 代理类,用作MyModel。所以我认为他可能无法控制将Employee[] 更改为List&lt;Employee&gt;
  • Null 和empty 意味着非常不同的东西。就我而言 - 指定对模型的更改。 Null 表示忽略此属性,保持原样。空意味着删除所有元素。
【解决方案3】:

如果您将 model.MyEmpls 设为 null,那么您可以在服务器端创建一个条件来停止引发异常,例如:

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

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

希望对你有所帮助。

【讨论】:

    【解决方案4】:

    尝试如下创建模型绑定器

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

    在 Application_Start 中注册模型绑定器

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

    并将控制器修改为

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

    【讨论】:

      【解决方案5】:

      你可以定义一个设置器来检查值是否为空

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

      【讨论】:

        【解决方案6】:
        [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。

        【讨论】:

          【解决方案7】:

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

          所以如果你发送一个空数组你不会初始化一个空数组,但是你会触发默认初始化为null

          查看以下链接 http://www.dotnetperls.com/null-array

          【讨论】:

          • 我认为问题在于 JSON 中的 [] 被反序列化为 null 它是 C# 而不是 new [] {}。这种行为对我来说似乎很奇怪,因为我认为空 C# 数组比 null 更接近 JSON 数组。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-03-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-05-27
          • 2011-01-06
          相关资源
          最近更新 更多