MVC3 RTM 反序列化 JSON 时数字类型强制失败
简单地将数据序列化为“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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
原因是当 MVC 遇到数字时,它会将其视为
Int32
。因此,出于某种原因,没有从Int32
到Decimal
或Nullable
的转换器。有几种方法可以解决这个问题。字符串,就像您的项目中已有的那样或用于创建自定义模型绑定器。我不完全确定为什么我们仍然无法从
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, fromInt32
to say aDecimal
or aNullable<Int64>
. There are a couple ways around this issue. Strings, as you already have in your project or to create a custom model binder.I'm not entirely sure why we still can't convert from
Int32
toDecimal
but the problem exists within MVC'sValueProviderResult.ConvertSimpleType
. It's usingTypeDescriptor.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.
我只看了你的问题,所以如果这不适用,我深表歉意。但我记得遇到了一些关于 Json 的问题:
I only glanced at your question, so I apologize if this doesn't apply. But I remember encountering a few issues with Json:
我下载了你的代码并运行了示例。当我单击该按钮时,我收到一个 JavaScript 错误(Microsoft JScript 运行时错误:对象不支持此属性或方法):
从您的描述中我也不清楚“之前/之后”应该是什么。您能仅用一个示例将其简化为绝对最简单的情况吗?我假设如果我们能够解决这种情况,那么它将适用于其余情况。
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:
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.