有一天,当我出现一个关于模型绑定的问题时,我正在追赶最新的ASP.NET社区Standup,这个问题我之前没有接过(你可以在46:30左右看到这个问题)。它指出在ASP.NET Core(ASP.NET 5 的新名称)中,您不能再简单地将JSON数据发布到MVC控制器并自动绑定它,您以前可以在ASP.NET 4 / MVC中执行此操作5。
在这篇文章中,我将展示如果您将项目转换为ASP.NET Core并且发现您的JSON POST不起作用该怎么办。我将演示MVC 5模型绑定和MVC Core模型绑定之间的区别,突出显示两者之间的差异,以及如何根据您期望的数据为您的项目设置控制器。
TL; DR:将[FromBody]属性添加到ASP.NET Core控制器操作中的参数注意,如果您使用的是ASP.NET Core 2.1,则还可以使用该[ApiController]属性自动推断[FromBody]复杂操作方法参数的绑定源。有关详细信息,请参阅文档
我的数据在哪里?
想象一下,您已经创建了一个闪亮的新ASP.NET核心项目,您正在使用它来重写现有的ASP.NET 4应用程序(当然只是出于明智的原因!)您将旧的WebApi控制器复制并粘贴到.NET Core Controller中,清理命名空间,测试GET操作,一切似乎都运行良好。
注意:在ASP.NET 4中,尽管MVC和WebApi管道的行为非常相似,但它们完全是分开的。因此,您分别拥有WebApi和Mvc的单独ApiController和Controller类(以及所有相关的命名空间混淆)。在ASP.NET Core中,管道都已合并,只有单个Controller类。
当您的GET请求正常工作时,您知道您的大部分管道(例如路由)可能已正确配置。您甚至可以提交一个测试表单,该表单将一个发送POST到控制器并接收它发回的JSON值。一切都很好看。
作为拼图的最后一部分,你测试发送一个POST带有JSON数据的AJAX ,它们都崩溃了 - 你收到了一个200 OK,但你对象上的所有属性都是空的。但为什么?
什么是模型绑定?
在我们详细了解这里发生的事情之前,我们需要对模型绑定有一个基本的了解。模型绑定是MVC或WebApi管道获取原始HTTP请求并将其转换为控制器上的操作方法调用的参数的过程。
例如,考虑以下WebApi控制器和Person类: - public class PersonController : ApiController
- {
- [HttpPost]
- public Person Index(Person person)
- {
- return person;
- }
- }
- public class Person
- {
- public string FirstName { get; set; }
- public string LastName { get; set; }
- public int Age { get; set; }
- }
复制代码
我们可以看到控制器上有一个动作方法,一个POST动作,它接受一个参数 - 一个Person类的实例。然后控制器只是回显该对象,然后回到响应中。
那么Person参数来自哪里?模型绑定救援!模型绑定器可以在许多不同的位置查找数据以便水合人物对象。模型绑定器具有高度可扩展性,并允许自定义实现,但常见绑定包括:
- 路由值 - 导航到诸如{controller}/{action}/{id}允许绑定到id参数的路由
- 查询字符串 - 如果您已将变量作为查询字符串参数传递,例如?FirstName=Andrew,则FirstName可以绑定参数。
- 正文 - 如果您在帖子的正文中发送数据,则可以将其绑定到Person对象
- 标头 - 您也可以绑定到HTTP标头值,尽管这种情况不太常见。
因此,您可以看到有多种方法可以将数据发送到服务器,并让模型绑定器自动为您创建正确的方法参数。有些需要explcit配置,而有些则需要免费获得。例如,路径值和查询字符串参数始终是绑定的,对于复杂类型(即不是像string或的基元int),主体也是绑定的。
重要的是要注意,如果模型绑定器由于某种原因未能绑定参数,它们将不会抛出错误,而是您将收到一个默认对象,没有设置任何属性,这是我们之前显示的行为。
它在ASP.NET 4中的工作原理
为了解决这里发生的事情,我创建了两个项目,一个使用ASP.NET 4,另一个使用最新的ASP.NET Core(在编写本文时非常接近RC2)。你可以在这里和这里的 GitHub上找到它们。
在ASP.NET WebApi项目中,有一个简单的控制器,它接受一个Person对象并简单地返回该对象,如我在上一节中所示。
在一个简单的网页上,我们然后制作POSTs(为方便起见使用jQuery),发送请求x-www-form-urlencoded(正如您从普通表单中获得的POST)或作为JSON。 - //form encoded data
- var dataType = 'application/x-www-form-urlencoded; charset=utf-8';
- var data = $('form').serialize();
- //JSON data
- var dataType = 'application/json; charset=utf-8';
- var data = {
- FirstName: 'Andrew',
- LastName: 'Lock',
- Age: 31
- }
- console.log('Submitting form...');
- $.ajax({
- type: 'POST',
- url: '/Person/Index',
- dataType: 'json',
- contentType: dataType,
- data: data,
- success: function(result) {
- console.log('Data received: ');
- console.log(result);
- }
- });
复制代码
这将为编码POST类似于(简称为简洁)的表单创建HTTP请求: - POST /api/Person/UnProtected HTTP/1.1
- Host: localhost:5000
- Accept: application/json, text/javascript, */*; q=0.01
- Content-Type: application/x-www-form-urlencoded; charset=UTF-8
- FirstName=Andrew&LastName=Lock&Age=31
复制代码
并为JSON帖子: - POST /api/Person/UnProtected HTTP/1.1
- Host: localhost:5000
- Accept: application/json, text/javascript, */*; q=0.01
- Content-Type: application/json; charset=UTF-8
- {"FirstName":"Andrew","LastName":"Lock","Age":"31"}
复制代码
发送这两个POSTs会引发以下控制台响应:
在这两种情况下,控制器都绑定到HTTP请求的主体,并且我们发送的参数被返回给我们,而我们不必做任何声明性的操作。模型粘合剂为我们做了所有的魔术。请注意,虽然我一直在使用WebApi控制器,但MVC控制器模型绑定器在此示例中的行为相同,并且将绑定两个POSTs。
ASP.NET Core中的新方法
因此,继续使用ASP.NET Core,我们创建了一个类似的控制器,使用与Person以前相同的类作为参数: - public class PersonController : Controller
- {
- [HttpPost]
- public IActionResult Index(Person person)
- {
- return Json(person);
- }
- }
复制代码
使用与以前相同的HTTP请求,我们看到以下控制台输出,其中x-www-url-formencoded POST绑定正确,但JSON POST不是。
为了在ASP.NET Core中正确绑定JSON,您必须修改操作以[FromBody]在参数中包含该属性。这告诉框架使用content-type请求的标头来决定使用哪个配置的IInputFormatters进行模型绑定。
默认情况下,当你打电话AddMvc()的Startup.cs,一个JSON格式,JsonInputFormatter被自动配置,但如果需要,你可以添加额外的格式化,例如XML绑定到一个对象。
考虑到这一点,我们的新控制器如下所示: - public class PersonController : Controller
- {
- [HttpPost]
- public IActionResult Index([FromBody] Person person)
- {
- return Json(person);
- }
- }
复制代码
而我们的JSON POST现在再次像魔术一样!
所以只包括[FromBody]?
因此,如果您认为您可以随时使用[FromBody]您的方法,请抓住您的马匹。让我们看看当您使用x-www-url-formencoded请求命中新端点时会发生什么:
噢亲爱的。在这种情况下,我们特意告诉ModelBinder绑定帖子的主体,即FirstName=Andrew&LastName=Lock&Age=31使用IInputFormatter。不幸的是,JSON格式化程序是我们唯一的格式化程序,并且与我们的内容类型不匹配,因此我们得到415错误响应。
为了专门绑定到表单参数,我们可以删除FromBody属性或添加替代FromForm属性,这两个属性都允许我们的表单数据绑定,但同样会再次阻止JSON绑定。
但是,如果我需要绑定两种数据类型呢?
在某些情况下,您可能需要能够将两种类型的数据绑定到操作。在这种情况下,你有点卡住,因为不可能让相同的终点接收两组不同的数据。
相反,您需要创建两个不同的操作方法,这些方法可以专门绑定您需要发送的数据,然后将处理调用委托给一个公共方法: - public class PersonController : Controller
- {
- //This action at /Person/Index can bind form data
- [HttpPost]
- public IActionResult Index(Person person){
- return DoSomething(person);
- }
- //This action at /Person/IndexFromBody can bind JSON
- [HttpPost]
- public IActionResult IndexFromBody([FromBody] Person person){
- return DoSomething(person);
- }
- private IActionResult DoSomething(Person person){
- // do something with the person here
- // ...
- return Json(person);
- }
- }
复制代码
您可能会发现必须使用两种不同的路径才能实现基本相同的操作。不幸的是,路径显然在模型绑定发生之前映射到动作,因此模型绑定器不能用作鉴别器。如果您尝试将上述两个操作映射到同一路径,则会收到错误消息Request matched multiple actions resulting in ambiguity。有可能创建一个自定义路由来根据标头值调用相应的操作,但很可能只会比它的价值更多的努力!
为什么要改变?
那为什么这一切都改变了?旧方式不简单易行吗?好吧,也许,但是有许多的陷阱需要提防的,尤其是当POST荷兰国际集团基本类型。
根据Damian Edwards在社区站立中的主要原因是出于安全原因,特别是跨站点请求伪造(CSRF)预防。我将在ASP.NET Core中稍后发布关于反CSRF的帖子,但实质上,当模型绑定可以从多个不同的源发生时,就像在ASP.NET 4中那样,默认情况下生成的堆栈不安全。我承认我没有理解为什么现在还是如何被利用,但我认为这与FormToken你从多个来源获取数据时识别你的反CSRF有关。
摘要
简而言之,如果您的模型绑定无法正常工作,请确保它尝试从请求的正确部分进行绑定,并且您已注册了相应的格式化程序。如果它是你正在做的JSON绑定,那么添加[FromBody]你的参数就可以了!
参考
- https://docs.asp.net/en/latest/mvc/models/model-binding.html
- https://lbadri.wordpress.com/2014/11/23/web-api-model-binding-in-asp-net-mvc-6-asp-net-5/
原文链接:https://andrewlock.net/model-binding-json-posts-in-asp-net-core/ 来源:https://blog.csdn.net/yeqiufeng/article/details/89307029 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |