的JavaScriptSerializer和ASP.Net MVC模式的结合产生不同的结果 [英] JavaScriptSerializer and ASP.Net MVC model binding yield different results

查看:288
本文介绍了的JavaScriptSerializer和ASP.Net MVC模式的结合产生不同的结果的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我看到一个JSON反序列化的问题,我无法解释或修复。

I'm seeing a JSON deserialization problem which I can not explain or fix.

public class Model
{
    public List<ItemModel> items { get; set; }
}
public class ItemModel
{

    public int sid { get; set; }
    public string name { get; set; }
    public DataModel data { get; set; }
    public List<ItemModel> items { get; set; }
}

public class DataModel
{
    public double? d1 { get; set; }
    public double? d2 { get; set; }
    public double? d3 { get; set; }
}

public ActionResult Save(int id, Model model) {
}

数据

{'项目:[{SID:3157,名称:名字,项目:[{SID:3158,'名':'孩子的名字,数据:{D1:2,D2:空,D3:2}}]}]}

var jss = new JavaScriptSerializer();
var m = jss.Deserialize<Model>(json);
Assert.Equal(2, m.items.First().items.First().data.d1);

问题

同样的JSON字符串,当发送到保存操作,不会被反序列化以同样的方式,特别是D1,D2和D3值都设置为空值。始终。

The problem

the same JSON string, when sent to the Save action, doesn't get deserialized the same way, specially the D1, D2, and D3 values are all set to NULL. Always.

这是怎么回事,我该怎么解决这个问题?

推荐答案

这听起来违反直觉,但你应该把那些双打作为JSON字符串:

It might sound counter-intuitive, but you should send those doubles as strings in the json:

'data':{'d1':'2','d2':null,'d3':'2'}

下面是我完整的测试code调用使用AJAX这个控制器操作,并允许绑定到模型中的每个值:

Here is my complete test code that invokes this controller action using AJAX, and allows binding to every value of the model:

$.ajax({
    url: '@Url.Action("save", new { id = 123 })',
    type: 'POST',
    contentType: 'application/json',
    data: JSON.stringify({
        items: [
            {
                sid: 3157,
                name: 'a name',
                items: [
                    {
                        sid: 3158,
                        name: 'child name',
                        data: {
                            d1: "2",
                            d2: null,
                            d3: "2"
                        }
                    }
                ]
            }
        ]
    }),
    success: function (result) {
        // ...
    }
});

和只是为了说明试图从JSON反序列化数值类型的问题的严重程度,让我们来举几个例子:

And just to illustrate the extent of the problem of trying to deserialize numeric types from JSON, let's take a few examples:


  • 公共双?富{搞定;组; }

    • {富:2} =>富=空

    • {富:2.0} =>富=空

    • {富:2.5} =>富=空

    • {富:'2.5'} =>富= 2.5

    • public double? Foo { get; set; }
      • { foo: 2 } => Foo = null
      • { foo: 2.0 } => Foo = null
      • { foo: 2.5 } => Foo = null
      • { foo: '2.5' } => Foo = 2.5

      • 公众持股量?富{搞定;组; }

        • {富:2} =>富=空

        • {富:2.0} =>富=空

        • {富:2.5} =>富=空

        • {富:'2.5'} =>富= 2.5

        • public float? Foo { get; set; }
          • { foo: 2 } => Foo = null
          • { foo: 2.0 } => Foo = null
          • { foo: 2.5 } => Foo = null
          • { foo: '2.5' } => Foo = 2.5

          • 公共小数?富{搞定;组; }

            • {富:2} =>富=空

            • {富:2.0} =>富=空

            • {富:2.5} =>富= 2.5

            • {富:'2.5'} =>富= 2.5

            • public decimal? Foo { get; set; }
              • { foo: 2 } => Foo = null
              • { foo: 2.0 } => Foo = null
              • { foo: 2.5 } => Foo = 2.5
              • { foo: '2.5' } => Foo = 2.5

              现在,让我们做同样与非可空类型:

              Now let's do the same with non-nullable types:


              • 公共双富{搞定;组; }

                • {富:2} =>富= 2.0

                • {富:2.0} =>富= 2.0

                • {富:2.5} =>富= 2.5

                • {富:'2.5'} =>富= 2.5

                • public double Foo { get; set; }
                  • { foo: 2 } => Foo = 2.0
                  • { foo: 2.0 } => Foo = 2.0
                  • { foo: 2.5 } => Foo = 2.5
                  • { foo: '2.5' } => Foo = 2.5

                  • 公众持股量的Foo {搞定;组; }

                    • {富:2} =>富= 2.0

                    • {富:2.0} =>富= 2.0

                    • {富:2.5} =>富= 2.5

                    • {富:'2.5'} =>富= 2.5

                    • public float Foo { get; set; }
                      • { foo: 2 } => Foo = 2.0
                      • { foo: 2.0 } => Foo = 2.0
                      • { foo: 2.5 } => Foo = 2.5
                      • { foo: '2.5' } => Foo = 2.5

                      • 公共小数富{搞定;组; }

                        • {富:2} =>富= 0

                        • {富:2.0} =>富= 0

                        • {富:2.5} =>富= 2.5

                        • {富:'2.5'} =>富= 2.5

                        • public decimal Foo { get; set; }
                          • { foo: 2 } => Foo = 0
                          • { foo: 2.0 } => Foo = 0
                          • { foo: 2.5 } => Foo = 2.5
                          • { foo: '2.5' } => Foo = 2.5

                          结论:JSON反序列化数值类型是一大地狱的一一塌糊涂。使用字符串中的JSON。当然,当你使用字符串,小心小数点分隔符,因为它是文化相关的。

                          Conclusion: deserializing numeric types from JSON is one big hell-of-a mess. Use strings in the JSON. And of course, when you use strings, be careful with the decimal separator as it is culture dependent.

                          我一直在问在评论部分为什么这个经过单元测试,但在ASP.NET MVC不起作用。答案很简单:这是因为ASP.NET MVC做很多事情不是简单的呼叫​​到 JavaScriptSerializer.Deserialize ,这是单元测试做什么。所以你基本上比较苹果和橘子。

                          I have been asked in the comments section why this passes unit tests, but doesn't work in ASP.NET MVC. The answer is simple: It's because ASP.NET MVC does many more things than a simple call to a JavaScriptSerializer.Deserialize, which is what the unit test does. So you are basically comparing apples to oranges.

                          让我们更深入地会发生什么。在ASP.NET MVC 3有一个内置的 JsonValueProviderFactory 内部使用了 JavaScriptDeserializer 类反序列化JSON。这工作,因为你已经看到,在单元测试。但是,有一个在ASP.NET MVC更给它,因为它也使用默认的模型绑定,负责实例化您的操作参数。

                          Let's dive deeper into what happens. In ASP.NET MVC 3 there's a built-in JsonValueProviderFactory which internally uses the JavaScriptDeserializer class to deserialize the JSON. This works, as you have already seen, in the unit test. But there's much more to it in ASP.NET MVC, as it also uses a default model binder that is responsible for instantiating your action parameters.

                          如果你看一下ASP.NET源$ C ​​$ C MVC 3,更具体的DefaultModelBinder.cs类,你会发现下面的方法被调用每个属性,将有一个要设置的值

                          And if you look at the source code of ASP.NET MVC 3, and more specifically the DefaultModelBinder.cs class, you will notice the following method which is invoked for each property that will have a value to be set:

                          public class DefaultModelBinder : IModelBinder {
                          
                              ...............
                          
                              [SuppressMessage("Microsoft.Globalization", "CA1304:SpecifyCultureInfo", MessageId = "System.Web.Mvc.ValueProviderResult.ConvertTo(System.Type)", Justification = "The target object should make the correct culture determination, not this method.")]
                              [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We're recording this exception so that we can act on it later.")]
                              private static object ConvertProviderResult(ModelStateDictionary modelState, string modelStateKey, ValueProviderResult valueProviderResult, Type destinationType) {
                                  try {
                                      object convertedValue = valueProviderResult.ConvertTo(destinationType);
                                      return convertedValue;
                                  }
                                  catch (Exception ex) {
                                      modelState.AddModelError(modelStateKey, ex);
                                      return null;
                                  }
                              }
                          
                              ...............
                          
                          }
                          

                          让我们更具体地集中在以下行:

                          Let's focus more specifically on the following line:

                          object convertedValue = valueProviderResult.ConvertTo(destinationType);
                          

                          如果我们假设你有类型的属性可空&LT;双&GT; ,这里是这将是什么样子,当您调试应用程序:

                          If we suppose that you had a property of type Nullable<double>, here's what this would look like when you debug your application:

                          destinationType = typeof(double?);
                          

                          这里没有惊喜。我们的目标类型为翻番?,因为这是我们在我们的视图模型使用。

                          No surprises here. Our destination type is double? because that's what we used in our view model.

                          然后看看在 valueProviderResult

                          请参阅本 RawValue 属性在那里?你能猜出它的类型?

                          See this RawValue property out there? Can you guess its type?

                          所以这个方法只是抛出一个异常,因为它显然不能转换小数 2.5 的值设置为双?

                          So this method simply throws an exception because it obviously cannot convert the decimal value of 2.5 to a double?.

                          你是否注意到在这种情况下返回什么价值?这就是为什么你在你的模型结束。

                          Do you notice what value is returned in this case? That's why you end up with null in your model.

                          这是很容易验证。简单地检查你的控制器动作中的 ModelState.IsValid 属性,你会发现,这是。而当你检查已添加到你会看到这个模型的状态模型误差:

                          That's very easy to verify. Simply inspect the ModelState.IsValid property inside your controller action and you will notice that it is false. And when you inspect the model error that was added to the model state you will see this:

                          从类型System.Decimal到类型参数的转换
                            System.Nullable`1 [System.Double,mscorlib程序,版本= 4.0.0.0,
                            文化=中性公钥= b77a5c561934e089]]'失败,因为没有
                            类型转换器可以将这些类型之间的转换。

                          The parameter conversion from type 'System.Decimal' to type 'System.Nullable`1[[System.Double, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]' failed because no type converter can convert between these types.

                          您现在可能会问,为什么是十进制类型的ValueProviderResult内RawValue财产?再次,答案就在ASP.NET MVC 3源$ C ​​$ C(是的,你应该现在下载它)里面。让我们来看看 JsonValueProviderFactory.cs 文件,更具体的 GetDeserializedObject 方法:

                          You may now ask, "But why is the RawValue property inside the ValueProviderResult of type decimal?". Once again the answer lies inside the ASP.NET MVC 3 source code (yeah, you should have downloaded it by now). Let's take a look at the JsonValueProviderFactory.cs file, and more specifically the GetDeserializedObject method:

                          public sealed class JsonValueProviderFactory : ValueProviderFactory {
                          
                              ............
                          
                              private static object GetDeserializedObject(ControllerContext controllerContext) {
                                  if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase)) {
                                      // not JSON request
                                      return null;
                                  }
                          
                                  StreamReader reader = new StreamReader(controllerContext.HttpContext.Request.InputStream);
                                  string bodyText = reader.ReadToEnd();
                                  if (String.IsNullOrEmpty(bodyText)) {
                                      // no JSON data
                                      return null;
                                  }
                          
                                  JavaScriptSerializer serializer = new JavaScriptSerializer();
                                  object jsonData = serializer.DeserializeObject(bodyText);
                                  return jsonData;
                              }
                          
                              ............
                          
                          }
                          

                          你注意到以下行:

                          Do you notice the following line:

                          JavaScriptSerializer serializer = new JavaScriptSerializer();
                          object jsonData = serializer.DeserializeObject(bodyText);
                          

                          你能猜到下面的代码片段将打印在控制台上?

                          Can you guess what the following snippet will print on your console?

                          var serializer = new JavaScriptSerializer();
                          var jsonData = (IDictionary<string, object>)serializer
                              .DeserializeObject("{\"foo\":2.5}");
                          Console.WriteLine(jsonData["foo"].GetType());
                          

                          没错,你猜对了,这是一个小数

                          您现在可能会问:但是为什么他们使用的serializer.DeserializeObject方法代替serializer.Deserialize在我的单元测试?这是因为ASP.NET MVC队取得设计决定实施JSON请求使用绑定 ValueProviderFactory ,不知道你的模型的类型。

                          You may now ask, "But why did they use the serializer.DeserializeObject method instead of serializer.Deserialize as in my unit test?" It's because the ASP.NET MVC team made the design decision to implement JSON request binding using a ValueProviderFactory, which doesn't know the type of your model.

                          现在见你的单元测试是如何比ASP.NET MVC 3的幕后到底发生了什么完全不同?通常应该解释为什么它通过了,为什么控制器操作不会得到正确的模型值?

                          See now how your unit test is completely different than what really happens under the covers of ASP.NET MVC 3? Which normally should explain why it passes, and why the controller action doesn't get a correct model value?

                          这篇关于的JavaScriptSerializer和ASP.Net MVC模式的结合产生不同的结果的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆