MVC3 RTM 反序列化 JSON 时数字类型强制失败

发布于 2024-10-18 06:01:25 字数 10567 浏览 1 评论 0原文

简单地将数据序列化为“application/json; charset=utf-8”格式在 MVC3(也可能是旧版本)中会出现错误行为。发生的情况是,可空数字全部以空结束,而“十进制”类型的数字在 javascript 对象(到 JSON)内序列化时以数字结束,并将它们保留为数字而不是字符串。

这是说明此不当行为的示例代码
- - - 此示例是使用 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; }
    }

}

索引。 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


EDIT: I am including this image to help everyone get what's going on here

单击此处查看所包含示例运行的屏幕截图

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

Simply put serializing data in the "application/json; charset=utf-8" format misbehaves in MVC3 (and possibly older versions). What happens is a nullable numbers all end up null, and numbers of "decimal" type end up 0 when serializing them inside a javascript object (to JSON) and leaving them as numbers and not strings.

Here is the example code that illustrates this misbehavior
- - - this example was created using jquery-1.4.4.js and 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>

Run it click each button once, and look at the before/after. Thanks in advance for any insight, correction or help that you can provide to resolve this issue.

you can also download the code samples listed above and see the official bug report for this at this link: http://aspnet.codeplex.com/workitem/8114


EDIT: I am including this image to help everyone get what's going on here

click here to see the screenshot of the included example running

Basically: { propertyThatIsADecimal: 54 } becomes { propertyThatIsADecimal: 0 } on the server for multiple different number types in varying scenarios which there doesn't seem to be any rhyme or reason to.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

白鸥掠海 2024-10-25 06:01:25

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

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) 并且这些类型没有可用的转换。

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

The reason is because when MVC encounters a number it treats it as an Int32. As such there are no converters, for some reason, from Int32 to say a Decimal or a Nullable<Int64>. There are a couple ways around this issue. Strings, as you already have in your project or to create a custom model binder.

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

I'm not entirely sure why we still can't convert from Int32 to Decimal but the problem exists within MVC's ValueProviderResult.ConvertSimpleType. It's using TypeDescriptor.GetConverter(your propertyType) and there's no conversions available for those types.

I don't like this particular method but it's the only one available to you for now.

柏拉图鍀咏恒 2024-10-25 06:01:25

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

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

I only glanced at your question, so I apologize if this doesn't apply. But I remember encountering a few issues with Json:

  1. Was this a binding issue? If so, maybe you can implement a custom binding. (Apparently MVC3 uses the JsonFactory already).
  2. I can't remember the issue, but I needed to add JsonRequestBehaviour.AllowGet when making jQuery calls using .getJSON.
  3. Does it matter that you don't have a [HttpGet] or [HttpPost] on your Save method?
讽刺将军 2024-10-25 06:01:25

我下载了你的代码并运行了示例。当我单击该按钮时,我收到一个 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);
            }
        });

从您的描述中我也不清楚“之前/之后”应该是什么。您能仅用一个示例将其简化为绝对最简单的情况吗?我假设如果我们能够解决这种情况,那么它将适用于其余情况。

I downloaded your code and ran the example. When I clicked the button I received a JavaScript error (Microsoft JScript runtime error: Object doesn't support this property or method) at:

$.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);
            }
        });

It was also not clear to me from your description what the "before/after" was supposed to be. Can you distill this down to the absolute simplest case with just a single example? I'm assuming that if we can figure it out for that case, then it will apply to the rest.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文