【问题标题】:MVC3 RTM fails number type coercion when deserializing JSON反序列化 JSON 时 MVC3 RTM 失败数字类型强制
【发布时间】:2011-06-30 01:24:45
【问题描述】:

简单地将序列化数据放入“application/json;charset=utf-8”格式在 MVC3(可能还有旧版本)中行为不端。发生的情况是一个可以为空的数字都以 null 结尾,而“十进制”类型的数字在 javascript 对象(到 JSON)中序列化它们并将它们保留为数字而不是字符串时以 0 结尾。

这里是说明这种不当行为的示例代码
- - 此示例是使用 jquery-1.4.4.js 和 jquery.json-2.2.js 创建的 - - - -

HomeController.cs:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        ViewBag.SaveUrl = Url.Action("Save", "Home", new { inspectionFormID = Guid.Empty }, Request.Url.Scheme);
        return View();
    }

    public JsonResult Save(Guid inspectionFormID, JsonTest result)
    {
        return Json(result);
    }

    public class JsonTest 
    {
        public double Double { get; set; }
        public double? DoubleNull { get; set; }

        public decimal Decimal { get; set; }
        public decimal? DecimalNull { get; set; }

        public Double Double2 { get; set; }
        public Double? Double2Null { get; set; }

        public Decimal Decimal2 { get; set; }
        public Decimal? Decimal2Null { get; set; }

        public Single Single { get; set; }
        public Single? SingleNull { get; set; }

        public float Float { get; set; }
        public float? FloatNull { get; set; }

        public int Int { get; set; }
        public int? IntNull { get; set; }

        public Int64 Int64 { get; set; }
        public Int64? Int64Null { get; set; }
    }

}

Index.cshtml:

    @{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

<b>@ViewBag.SaveUrl</b>
<br />
<hr />
<br />

<h3>Integral Numbers</h3>
<button type="button" class="a">Clicky</button>
<div></div>

<h3>Decimal Numbers (xx.0)</h3>
<button type="button" class="b">Clicky</button>
<div></div>

<h3>Decimal Numbers (xx.5)</h3>
<button type="button" class="c">Clicky</button>
<div></div>

<h3>Integral Numbers as strings</h3>
<button type="button" class="d">Clicky</button>
<div></div>

<h3>Decimal Numbers as strings (xx.5)</h3>
<button type="button" class="e">Clicky</button>
<div></div>

<script type="text/javascript">

    $(function () {
        var saveUrl = '@ViewBag.SaveUrl';

        var printObj = function (inObj, destx) {
            var dest = $('<table>').appendTo(destx),
                dst1 = $('<tr>').appendTo(dest),
                dst2 = $('<tr>').appendTo(dest);
            for (var p in inObj) {
                $('<th>', { text: p, css: { color: 'red', padding: '3px', background: '#dedede' } }).appendTo(dst1);
                $('<td>', { text: inObj[p] || 'null' }).appendTo(dst2);
            }
        };

        $('button.a').click(function () {
            var curr = $(this).next(),
                outR = {
                    Double: 12,
                    DoubleNull: 13,
                    Decimal: 14,
                    DecimalNull: 15,
                    Double2: 16,
                    Double2Null: 17,
                    Decimal2: 18,
                    Decimal2Null: 19,
                    Single: 20,
                    SingleNull: 21,
                    Float: 22,
                    FloatNull: 23,
                    Int: 24,
                    IntNull: 25,
                    Int64: 26,
                    Int64Null: 27
                };

            $('<hr />').appendTo(curr);
            printObj(outR, curr);

            $.ajax({
                type: 'POST',
                url: saveUrl,
                contentType: "application/json; charset=utf-8",
                dataType: 'json',

                data: $.toJSON({
                    inspectionFormID: 'fbde6eda-dde6-4ba9-b82d-3a35349415f0',

                    result: outR
                }),

                error: function (jqXHR, textStatus, errorThrown) {
                    alert('save failed');
                },

                success: function (data, textStatus, jqXHR) {
                    printObj(data, curr);
                }
            });
        });

        $('button.b').click(function () {
            var curr = $(this).next(),
                outR = {
                    Double: 12.0,
                    DoubleNull: 13.0,
                    Decimal: 14.0,
                    DecimalNull: 15.0,
                    Double2: 16.0,
                    Double2Null: 17.0,
                    Decimal2: 18.0,
                    Decimal2Null: 19.0,
                    Single: 20.0,
                    SingleNull: 21.0,
                    Float: 22.0,
                    FloatNull: 23.0,
                    Int: 24.0,
                    IntNull: 25.0,
                    Int64: 26.0,
                    Int64Null: 27.0
                };

            $('<hr />').appendTo(curr);
            printObj(outR, curr);

            $.ajax({
                type: 'POST',
                url: saveUrl,
                contentType: "application/json; charset=utf-8",
                dataType: 'json',

                data: $.toJSON({
                    inspectionFormID: 'fbde6eda-dde6-4ba9-b82d-3a35349415f0',

                    result: outR
                }),

                error: function (jqXHR, textStatus, errorThrown) {
                    alert('save failed');
                },

                success: function (data, textStatus, jqXHR) {
                    printObj(data, curr);
                }
            });
        });

        $('button.c').click(function () {
            var curr = $(this).next(),
                outR = {
                    Double: 12.5,
                    DoubleNull: 13.5,
                    Decimal: 14.5,
                    DecimalNull: 15.5,
                    Double2: 16.5,
                    Double2Null: 17.5,
                    Decimal2: 18.5,
                    Decimal2Null: 19.5,
                    Single: 20.5,
                    SingleNull: 21.5,
                    Float: 22.5,
                    FloatNull: 23.5,
                    Int: 24.5,
                    IntNull: 25.5,
                    Int64: 26.5,
                    Int64Null: 27.5
                };

            $('<hr />').appendTo(curr);
            printObj(outR, curr);

            $.ajax({
                type: 'POST',
                url: saveUrl,
                contentType: "application/json; charset=utf-8",
                dataType: 'json',

                data: $.toJSON({
                    'inspectionFormID': 'fbde6eda-dde6-4ba9-b82d-3a35349415f0',

                    'result': outR
                }),

                error: function (jqXHR, textStatus, errorThrown) {
                    alert('save failed');
                },

                success: function (data, textStatus, jqXHR) {
                    printObj(data, curr);
                }
            });
        });

        $('button.d').click(function () {
            var curr = $(this).next(),
                outR = {
                    Double:         '12',
                    DoubleNull:     '13',
                    Decimal:        '14',
                    DecimalNull:    '15',
                    Double2:        '16',
                    Double2Null:    '17',
                    Decimal2:       '18',
                    Decimal2Null:   '19',
                    Single:         '20',
                    SingleNull:     '21',
                    Float:          '22',
                    FloatNull:      '23',
                    Int:            '24',
                    IntNull:        '25',
                    Int64:          '26',
                    Int64Null:      '27'
                };

            $('<hr />').appendTo(curr);
            printObj(outR, curr);

            $.ajax({
                type: 'POST',
                url: saveUrl,
                contentType: "application/json; charset=utf-8",
                dataType: 'json',
                data: $.toJSON({
                    'inspectionFormID': 'fbde6eda-dde6-4ba9-b82d-3a35349415f0',

                    'result': outR
                }),

                error: function (jqXHR, textStatus, errorThrown) {
                    alert('save failed');
                },

                success: function (data, textStatus, jqXHR) {
                    printObj(data, curr);
                }
            });
    });

    $('button.e').click(function () {
        var curr = $(this).next(),
                outR = {
                    Double:         '12.5',
                    DoubleNull:     '13.5',
                    Decimal:        '14.5',
                    DecimalNull:    '15.5',
                    Double2:        '16.5',
                    Double2Null:    '17.5',
                    Decimal2:       '18.5',
                    Decimal2Null:   '19.5',
                    Single:         '20.5',
                    SingleNull:     '21.5',
                    Float:          '22.5',
                    FloatNull:      '23.5',
                    Int:            '24.5',
                    IntNull:        '25.5',
                    Int64:          '26.5',
                    Int64Null:      '27.5'
                };

        $('<hr />').appendTo(curr);
        printObj(outR, curr);

        $.ajax({
            type: 'POST',
            url: saveUrl,
            contentType: "application/json; charset=utf-8",
            dataType: 'json',
            data: $.toJSON({
                'inspectionFormID': 'fbde6eda-dde6-4ba9-b82d-3a35349415f0',

                'result': outR
            }),

            error: function (jqXHR, textStatus, errorThrown) {
                alert('save failed');
            },

            success: function (data, textStatus, jqXHR) {
                printObj(data, curr);
            }
        });
});
    });

</script>

运行它单击每个按钮一次,然后查看之前/之后。提前感谢您为解决此问题提供的任何见解、更正或帮助。

您还可以下载上面列出的代码示例并在此链接查看官方错误报告:http://aspnet.codeplex.com/workitem/8114


编辑:我加入这张图片是为了帮助大家了解这里发生了什么

click here to see the screenshot of the included example running

基本上:{propertyThatIsADecimal: 54 } 在服务器上变成 {propertyThatIsADecimal: 0 } 用于不同场景中的多种不同数字类型,似乎没有任何押韵或理由。

【问题讨论】:

  • 我想是两部分,寻找合适的非黑客修复并询问这是否是预期的行为(来自 MVC 专业人士)
  • 自定义 JSON 序列化程序看起来是目前最好的解决方法,这个问题在 MVC 错误跟踪器上并不重要(低)-_-

标签: asp.net-mvc model-view-controller c#-4.0 asp.net-mvc-3


【解决方案1】:

原因是当 MVC 遇到一个数字时,它会将其视为Int32。因此,出于某种原因,没有转换器从Int32DecimalNullable&lt;Int64&gt;。有几种方法可以解决这个问题。字符串,就像您在项目中已有的那样,或者用于创建自定义模型绑定器。

public class JsonTestModelBinder : IModelBinder {
    public virtual object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
        JsonTest result = new JsonTest();

        foreach (var property in typeof(JsonTest).GetProperties()) {
            //the value provider starts with the name of the property we're binding to
            //i'm not sure if this changed or not as i don't recall having to do this
            //before - you can remove "result." if your needs don't require it
            var value = bindingContext.ValueProvider.GetValue("result." + property.Name);
            if (value != null && value.RawValue != null) {
                //are we binding to a nullable?
                if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition().Equals(typeof(Nullable<>))) {
                    property.SetValue(result, Convert.ChangeType(value.AttemptedValue, new NullableConverter(property.PropertyType).UnderlyingType), null);
                } else {
                    property.SetValue(result, Convert.ChangeType(value.AttemptedValue, property.PropertyType), null);
                }
            }
        }

        return result;
    }
}

我不完全确定为什么我们仍然无法从 Int32 转换为 Decimal,但问题存在于 MVC 的 ValueProviderResult.ConvertSimpleType 中。它正在使用TypeDescriptor.GetConverter(your propertyType),并且这些类型没有可用的转换。

我不喜欢这种特殊的方法,但它是您目前唯一可用的方法。

【讨论】:

  • 对我来说听起来像是有人变得懒惰和捎带转换或其他东西,我真的希望这能在某个时候得到修复,因为这是我个人最喜欢的异步通信方式,也是 Web 表单的 MS Ajax 方式默认情况下工作......更手动的方式不是破坏交易,但它似乎有点hacky。无论如何,感谢您的宝贵时间。
【解决方案2】:

看了你的问题,如果这不适用,我深表歉意。但我记得在使用 Json 时遇到了一些问题:

  1. 这是一个绑定问题吗?如果是这样,也许您可​​以实现自定义绑定。 (显然 MVC3 已经使用了 JsonFactory)。
  2. 我不记得这个问题了,但我需要在使用 .getJSON 进行 jQuery 调用时添加 JsonRequestBehaviour.AllowGet。
  3. 您的 Save 方法中没有 [HttpGet] 或 [HttpPost] 是否重要?

【讨论】:

  • 感谢您的回复,但不幸的是,不,这似乎都不适用。在 MVC 堆栈中的某处,JSON 要么不被视为 JSON,要么被视为 JSON,并且在数字类型之间移动时,在映射到具体对象期间未正确强制对 Dictionary 进行反序列化(例如int到十进制)但奇怪的是(如我的示例所示)字符串强制转换为十进制或其他预期的工作。在序列化之前强制你的 javascript 将数字转换为字符串只是一个无赖和一个 hack。
  • 我下载了您的代码并运行了示例,但从您的描述中我不清楚“之前和之后”应该是什么。你能把它提炼成一个例子吗?我假设如果我们能解决这个问题,那么我们就可以解决剩下的问题。
  • 至于错误,你需要两个:jquery-1.4.4.js 和 jquery.json-2.2.js (ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.js AND jquery-json.googlecode.com/files/jquery.json-2.2.js)。最简单的情况是“data: $.toJSON({ param: { val: 5 } })” 和“data: $.toJSON({ param: { val: 5.5 } })” 接收动作签名看起来像: public JsonResult Save(simpleExample param) 和 simpleExample 对象看起来像: public class simpleExample { public decimal val {get;set;} }
  • 哦,之前:“在被发送到服务器之前”&之后:“在服务器上/在返回中”(因为动作返回了发送的内容,并且没有问题从服务器->客户端,只是客户端->服务器),感谢您的时间。该示例在返回时显示之前和之后。
【解决方案3】:

我下载了您的代码并运行了示例。当我单击该按钮时,我在以下位置收到 JavaScript 错误(Microsoft JScript 运行时错误:对象不支持此属性或方法):

$.ajax({
            type: 'POST',
            url: saveUrl,
            contentType: "application/json; charset=utf-8",
            dataType: 'json',

            data: $.toJSON({
                inspectionFormID: 'fbde6eda-dde6-4ba9-b82d-3a35349415f0',

                result: outR
            }),

            error: function (jqXHR, textStatus, errorThrown) {
                alert('save failed');
            },

            success: function (data, textStatus, jqXHR) {
                printObj(data, curr);
            }
        });

从您的描述中我也不清楚“之前/之后”应该是什么。你能用一个例子把它提炼成最简单的例子吗?我假设如果我们能解决这个问题,那么它将适用于其余的情况。

【讨论】:

  • 至于错误,你需要两个:jquery-1.4.4.js 和 jquery.json-2.2.js (ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery .js 和 jquery-json.googlecode.com/files/jquery.json-2.2.js)。 最简单的情况是, data: $.toJSON({ param: { val: 5 } }) data: $.toJSON({ param: { val: 5.5 } }) 接收操作签名如下所示: ` public JsonResult Save(simpleExample param ) ` simpleExample 对象看起来像: public class simpleExample { public decimal val { get; set; } }
  • 哦,之前:“在被发送到服务器之前”&之后:“在服务器上/在返回中”(因为动作返回了发送的内容,并且没有问题从服务器->客户端,只是客户端->服务器),感谢您的时间。该示例在返回时显示之前和之后。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-07
  • 1970-01-01
相关资源
最近更新 更多