无法在 ASP.NET 核心中往返 JSON - 因为我做了一些愚蠢的事情
Unable to round-trip JSON in ASP.NET Core - because I did something stupid
我正在尝试将 ASP.NET/MVC-.NET4.5 Web 应用程序移动到 ASP.NET CORE .NET 5.0,但我无法获得 json serialization/deserialization 上班。
这取决于服务器能够向浏览器发送 json 对象,并让浏览器将它们发回 - 而 serialization/deserialization 根本不起作用。
我已经创建了一个简单的测试应用程序,它失败了,没有多余的复杂性。
我从 dotnet new webapp -o JsonTesting
开始。
我添加了一个 ApiController
。
我的创业公司:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
[...]
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapDefaultControllerRoute();
});
}
我的模型(注意 - 我故意使用不一致的大小写):
public class Foo
{
public int AnInt { get; set; }
public double aDouble { get; set; }
public decimal ADecimal { get; set; }
public string aString { get; set; }
public DateTime aDateTime { get; set; }
public DateTimeOffset ADateTimeOffset { get; set; }
}
我的控制器:
[ApiController]
[Area("api")]
[Route("[area]/[controller]")]
public class FooController : ControllerBase
{
private List<Foo> _foos;
public FooController()
{
_foos = new List<Foo>
{
new Foo{
AnInt = 1,
aDouble = 1.1,
ADecimal = 1m,
aString = "One",
aDateTime = DateTime.Parse("2020-01-01T01:01:01"),
ADateTimeOffset = DateTimeOffset.Parse("2020-01-01T01:01:01")
},
new Foo{
AnInt = 2,
aDouble = 2.2,
ADecimal = 2m,
aString = "Two",
aDateTime = DateTime.Parse("2020-01-02T02:02:02"),
ADateTimeOffset = DateTimeOffset.Parse("2020-01-02T02:02:02")
}
};
}
[HttpGet]
public ActionResult<IEnumerable<Foo>> Get()
{
return _foos;
}
[HttpPost]
public ActionResult<bool> Post(Foo foo)
{
var match = _foos.SingleOrDefault(f => f.AnInt == foo.AnInt);
if (match == null)
return false;
if (foo.aDouble != match.aDouble)
return false;
if (foo.ADecimal != match.ADecimal)
return false;
if (foo.aString != match.aString)
return false;
if (foo.aDouble != match.aDouble)
return false;
if (foo.aDateTime != match.aDateTime)
return false;
if (foo.ADateTimeOffset != match.ADateTimeOffset)
return false;
return true;
}
}
Get returns 一个 Foos 列表,一个 Post 发送一个 Foo 并且 returns 如果它在列表中则为真,否则为假。
然后我在Pages/Index.cshtml:
中添加了一个table
<div class="text-center">
<table>
<th>
<td>Initial</td>
<td>Buttons</td>
</th>
<tbody id="thebody"></tbody>
</table>
</div>
然后一些 Javascript 调用 GET 端点来填充 table,然后在单击其中一个按钮时调用 PUT 端点:
@section Scripts {
<script type="text/javascript">
debugger;
$(document).ready(function ()
{
var $thebody = $("#thebody");
$thebody.empty();
$.ajax({
url: '/api/Foo',
type: 'GET',
dataType: 'json',
contentType: 'application/json',
data: null
}).done(function (data, textStatus, jqXHR)
{
data.forEach(element =>
{
var $tr = $("<tr />");
var $td1 = $("<td />");
$td1.text(JSON.stringify(element));
$tr.append($td1);
var $td2 = $("<td />");
var $btn = $("<input type='button' />");
$btn.val(element.aString)
$btn.data('foo', element);
$td2.append($btn);
$tr.append($td2);
var $td3 = $("<td class='out'/>");
$tr.append($td3);
$btn.on('click', function (event)
{
var $target = $(event.target);
var foo = $target.data('foo');
$.ajax({
url: '/api/Foo',
type: 'POST',
dataType: 'json',
contentType: 'application/json',
data: JSON.stringify({ 'foo': foo })
}).done(function (data, textStatus, jqXHR)
{
if (data)
{
alert('Matched');
} else
{
alert('Didn\'t match');
}
}).fail(function (jqXHR, textStatus, errprThrow)
{
alert(textStatus);
});
});
$thebody.append($tr);
});
}).fail(function (jqXHR, textStatus, errprThrow)
{
alert(textStatus);
});;
});
</script>
}
在调试器中检查它,看起来 Foo 对象被正确发送到浏览器,并且被正确保存。当我单击其中一个按钮时,我在 Chrome DTools 中看到的内容看起来是正确的:
foo: {
"anInt": 1,
"aDouble": 1.1,
"aDecimal": 1,
"aString": "One",
"aDateTime": "2020-01-01T01:01:01",
"aDateTimeOffset": "2020-01-01T01:01:01-06:00"
}
但是 FooController.Post() 中出现的内容被清零了。 None 个字段已根据浏览器提供的值设置。
然而,当我查看 DevTools 网络选项卡时,我在请求负载中看到的内容看起来是正确的:
{"foo":{"anInt":1,"aDouble":1.1,"aDecimal":1,"aString":"One","aDateTime":"2020-01-01T01:01:01","aDateTimeOffset":"2020-01-01T01:01:01-06:00"}}
关于我做错了什么有什么想法吗?
这在 .NET 4.5 中使用 NewtonSoft.JSON 才行得通。我没想到迁移到 .NET 5.0 会遇到问题。
===
好的,事情就是这样。我主要是后端开发人员,我正在复制我们的前端开发人员常用的模式。或者复制一半。
在 JQuery POST 上,我在 data
字段中输入的内容需要匹配端点函数的参数。当然可以。
我们的前端开发人员通常使用数据传输对象 (DTO) 执行此操作。我在 javascript 中提供了 DTO,但我没有在端点中接受 DTO。
一个解决方法是传递 Foo 对象本身,如 Brando Zhang 的回答中所建议的:
data: JSON.stringify(foo)
并且端点继续接受 Foo:
[HttpPost]
public ActionResult<bool> Post(Foo foo)
{
[...]
}
另一种是继续传一个DTO:
data: JSON.stringify({ 'foo': foo })
然后更改端点以接受 DTO:
public class FooDto
{
public Foo Foo { get; set; }
}
[HttpPost]
public ActionResult<bool> Post(FooDto dto)
{
Foo foo = dto.Foo;
[...]
}
两者都有效。
您需要使用 FromBodyAttribute
:
注释您的 POST 参数
[HttpPost]
public ActionResult<bool> Post([FromBody] Foo foo)
据我所知,您的 Json 格式有问题,正确的 Json 格式应该如下所示:
{"anInt":1,"aDouble":1.1,"aDecimal":1,"aString":"One","aDateTime":"2020-01-01T01:01:01","aDateTimeOffset":"2020-01-01T01:01:01-06:00"}
所以你应该像下面这样修改 jquery:
@section Scripts {
<script type="text/javascript">
debugger;
$(document).ready(function ()
{
var $thebody = $("#thebody");
$thebody.empty();
$.ajax({
url: '/api/Foo',
type: 'GET',
dataType: 'json',
contentType: 'application/json',
data: null
}).done(function (data, textStatus, jqXHR)
{
data.forEach(element =>
{
var $tr = $("<tr />");
var $td1 = $("<td />");
$td1.text(JSON.stringify(element));
$tr.append($td1);
var $td2 = $("<td />");
var $btn = $("<input type='button' />");
$btn.val(element.aString)
$btn.data('foo', element);
$td2.append($btn);
$tr.append($td2);
var $td3 = $("<td class='out'/>");
$tr.append($td3);
$btn.on('click', function (event)
{
var $target = $(event.target);
var foo = $target.data('foo');
$.ajax({
url: '/api/Foo',
type: 'POST',
dataType: 'json',
contentType: 'application/json',
data: JSON.stringify( foo )
}).done(function (data, textStatus, jqXHR)
{
if (data)
{
alert('Matched');
} else
{
alert('Didn\'t match');
}
}).fail(function (jqXHR, textStatus, errprThrow)
{
alert(textStatus);
});
});
$thebody.append($tr);
});
}).fail(function (jqXHR, textStatus, errprThrow)
{
alert(textStatus);
});;
});
</script>
}
结果:
我正在尝试将 ASP.NET/MVC-.NET4.5 Web 应用程序移动到 ASP.NET CORE .NET 5.0,但我无法获得 json serialization/deserialization 上班。
这取决于服务器能够向浏览器发送 json 对象,并让浏览器将它们发回 - 而 serialization/deserialization 根本不起作用。
我已经创建了一个简单的测试应用程序,它失败了,没有多余的复杂性。
我从 dotnet new webapp -o JsonTesting
开始。
我添加了一个 ApiController
。
我的创业公司:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
[...]
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapDefaultControllerRoute();
});
}
我的模型(注意 - 我故意使用不一致的大小写):
public class Foo
{
public int AnInt { get; set; }
public double aDouble { get; set; }
public decimal ADecimal { get; set; }
public string aString { get; set; }
public DateTime aDateTime { get; set; }
public DateTimeOffset ADateTimeOffset { get; set; }
}
我的控制器:
[ApiController]
[Area("api")]
[Route("[area]/[controller]")]
public class FooController : ControllerBase
{
private List<Foo> _foos;
public FooController()
{
_foos = new List<Foo>
{
new Foo{
AnInt = 1,
aDouble = 1.1,
ADecimal = 1m,
aString = "One",
aDateTime = DateTime.Parse("2020-01-01T01:01:01"),
ADateTimeOffset = DateTimeOffset.Parse("2020-01-01T01:01:01")
},
new Foo{
AnInt = 2,
aDouble = 2.2,
ADecimal = 2m,
aString = "Two",
aDateTime = DateTime.Parse("2020-01-02T02:02:02"),
ADateTimeOffset = DateTimeOffset.Parse("2020-01-02T02:02:02")
}
};
}
[HttpGet]
public ActionResult<IEnumerable<Foo>> Get()
{
return _foos;
}
[HttpPost]
public ActionResult<bool> Post(Foo foo)
{
var match = _foos.SingleOrDefault(f => f.AnInt == foo.AnInt);
if (match == null)
return false;
if (foo.aDouble != match.aDouble)
return false;
if (foo.ADecimal != match.ADecimal)
return false;
if (foo.aString != match.aString)
return false;
if (foo.aDouble != match.aDouble)
return false;
if (foo.aDateTime != match.aDateTime)
return false;
if (foo.ADateTimeOffset != match.ADateTimeOffset)
return false;
return true;
}
}
Get returns 一个 Foos 列表,一个 Post 发送一个 Foo 并且 returns 如果它在列表中则为真,否则为假。
然后我在Pages/Index.cshtml:
中添加了一个table
<div class="text-center">
<table>
<th>
<td>Initial</td>
<td>Buttons</td>
</th>
<tbody id="thebody"></tbody>
</table>
</div>
然后一些 Javascript 调用 GET 端点来填充 table,然后在单击其中一个按钮时调用 PUT 端点:
@section Scripts {
<script type="text/javascript">
debugger;
$(document).ready(function ()
{
var $thebody = $("#thebody");
$thebody.empty();
$.ajax({
url: '/api/Foo',
type: 'GET',
dataType: 'json',
contentType: 'application/json',
data: null
}).done(function (data, textStatus, jqXHR)
{
data.forEach(element =>
{
var $tr = $("<tr />");
var $td1 = $("<td />");
$td1.text(JSON.stringify(element));
$tr.append($td1);
var $td2 = $("<td />");
var $btn = $("<input type='button' />");
$btn.val(element.aString)
$btn.data('foo', element);
$td2.append($btn);
$tr.append($td2);
var $td3 = $("<td class='out'/>");
$tr.append($td3);
$btn.on('click', function (event)
{
var $target = $(event.target);
var foo = $target.data('foo');
$.ajax({
url: '/api/Foo',
type: 'POST',
dataType: 'json',
contentType: 'application/json',
data: JSON.stringify({ 'foo': foo })
}).done(function (data, textStatus, jqXHR)
{
if (data)
{
alert('Matched');
} else
{
alert('Didn\'t match');
}
}).fail(function (jqXHR, textStatus, errprThrow)
{
alert(textStatus);
});
});
$thebody.append($tr);
});
}).fail(function (jqXHR, textStatus, errprThrow)
{
alert(textStatus);
});;
});
</script>
}
在调试器中检查它,看起来 Foo 对象被正确发送到浏览器,并且被正确保存。当我单击其中一个按钮时,我在 Chrome DTools 中看到的内容看起来是正确的:
foo: {
"anInt": 1,
"aDouble": 1.1,
"aDecimal": 1,
"aString": "One",
"aDateTime": "2020-01-01T01:01:01",
"aDateTimeOffset": "2020-01-01T01:01:01-06:00"
}
但是 FooController.Post() 中出现的内容被清零了。 None 个字段已根据浏览器提供的值设置。
然而,当我查看 DevTools 网络选项卡时,我在请求负载中看到的内容看起来是正确的:
{"foo":{"anInt":1,"aDouble":1.1,"aDecimal":1,"aString":"One","aDateTime":"2020-01-01T01:01:01","aDateTimeOffset":"2020-01-01T01:01:01-06:00"}}
关于我做错了什么有什么想法吗?
这在 .NET 4.5 中使用 NewtonSoft.JSON 才行得通。我没想到迁移到 .NET 5.0 会遇到问题。
===
好的,事情就是这样。我主要是后端开发人员,我正在复制我们的前端开发人员常用的模式。或者复制一半。
在 JQuery POST 上,我在 data
字段中输入的内容需要匹配端点函数的参数。当然可以。
我们的前端开发人员通常使用数据传输对象 (DTO) 执行此操作。我在 javascript 中提供了 DTO,但我没有在端点中接受 DTO。
一个解决方法是传递 Foo 对象本身,如 Brando Zhang 的回答中所建议的:
data: JSON.stringify(foo)
并且端点继续接受 Foo:
[HttpPost]
public ActionResult<bool> Post(Foo foo)
{
[...]
}
另一种是继续传一个DTO:
data: JSON.stringify({ 'foo': foo })
然后更改端点以接受 DTO:
public class FooDto
{
public Foo Foo { get; set; }
}
[HttpPost]
public ActionResult<bool> Post(FooDto dto)
{
Foo foo = dto.Foo;
[...]
}
两者都有效。
您需要使用 FromBodyAttribute
:
[HttpPost]
public ActionResult<bool> Post([FromBody] Foo foo)
据我所知,您的 Json 格式有问题,正确的 Json 格式应该如下所示:
{"anInt":1,"aDouble":1.1,"aDecimal":1,"aString":"One","aDateTime":"2020-01-01T01:01:01","aDateTimeOffset":"2020-01-01T01:01:01-06:00"}
所以你应该像下面这样修改 jquery:
@section Scripts {
<script type="text/javascript">
debugger;
$(document).ready(function ()
{
var $thebody = $("#thebody");
$thebody.empty();
$.ajax({
url: '/api/Foo',
type: 'GET',
dataType: 'json',
contentType: 'application/json',
data: null
}).done(function (data, textStatus, jqXHR)
{
data.forEach(element =>
{
var $tr = $("<tr />");
var $td1 = $("<td />");
$td1.text(JSON.stringify(element));
$tr.append($td1);
var $td2 = $("<td />");
var $btn = $("<input type='button' />");
$btn.val(element.aString)
$btn.data('foo', element);
$td2.append($btn);
$tr.append($td2);
var $td3 = $("<td class='out'/>");
$tr.append($td3);
$btn.on('click', function (event)
{
var $target = $(event.target);
var foo = $target.data('foo');
$.ajax({
url: '/api/Foo',
type: 'POST',
dataType: 'json',
contentType: 'application/json',
data: JSON.stringify( foo )
}).done(function (data, textStatus, jqXHR)
{
if (data)
{
alert('Matched');
} else
{
alert('Didn\'t match');
}
}).fail(function (jqXHR, textStatus, errprThrow)
{
alert(textStatus);
});
});
$thebody.append($tr);
});
}).fail(function (jqXHR, textStatus, errprThrow)
{
alert(textStatus);
});;
});
</script>
}
结果: