模型绑定ASP.NET Core中的JSON POST(你的.net core 为什么无法接收JSON参数)

有一天,当我出现一个关于模型绑定的问题时,我正在追赶最新的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的单独ApiControllerController类(以及所有相关的命名空间混淆)。在ASP.NET Core中,管道都已合并,只有单个Controller类。

当您的GET请求正常工作时,您知道您的大部分管道(例如路由)可能已正确配置。您甚至可以提交一个测试表单,该表单将一个发送POST到控制器并接收它发回的JSON值。一切都很好看。

将x-www-url-formencoded内容发布到ASP.NET Core控制器

作为拼图的最后一部分,你测试发送一个POST带有JSON数据的AJAX ,它们都崩溃了 - 你收到了一个200 OK,但你对象上的所有属性都是空的。但为什么?

将JSON内容发布到ASP.NET Core控制器

什么是模型绑定?

在我们详细了解这里发生的事情之前,我们需要对模型绑定有一个基本的了解。模型绑定是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会引发以下控制台响应:

ASP.NET 4控制器成功发布的图片

在这两种情况下,控制器都绑定到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不是。

x-www-url-formencoded帖子绑定正确,但JSON帖子不正确

为了在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现在再次像魔术一样!

JSON绑定与FromBody属性一起正常工作

所以只包括[FromBody]?

因此,如果您认为您可以随时使用[FromBody]您的方法,请抓住您的马匹。让我们看看当您使用x-www-url-formencoded请求命中新端点时会发生什么:

使用x-www-url-formencoded with FromBody属性的不支持的媒体类型

噢亲爱的。在这种情况下,我们特意告诉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://andrewlock.net/model-binding-json-posts-in-asp-net-core/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章