65.9K
CodeProject 正在变化。 阅读更多。
Home

.NET Core WebAPI(自动)绑定参数在通过 JavaScript Fetch 调用时

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2024年6月9日

CPOL

7分钟阅读

viewsIcon

18279

downloadIcon

252

尝试简化调用 .NET Core WebAPI,当通过 JS Fetch 调用时(大量示例)

GitHub - raddevus/BindApi: 使用 dotnet webapi 和 JavaScript Fetch 中自动绑定的文章示例代码[^]

引言

这是一篇快速文章,包含多个示例,说明如何利用 .NET Core WebAPI 的自动绑定功能。我知道这通常被称为模型绑定,但我的示例涵盖了您只向 WebAPI 方法传递一个参数值的情况。

我还在我的博客上写了这篇文章,文章略微粗糙,如果你喜欢,可以在那里查看更多详细信息并发表评论:JavaScript Fetch 与 .NET Core WebAPI:Fetch 方法自动绑定示例 – Build In Public Developer[^]

本文将如何运作

我将为每个示例提供两件事,以便您可以确切地了解 .NET Core WebAPI 中的自动绑定功能是如何工作的。

  1. WebAPI 方法定义(通过最小 API 的示例代码)
  2. JavaScript Fetch 示例,您可以用来 POST 到 WebAPI 并查看结果

WebAPI 参数属性

.NET Core WebAPI 方法允许您使用许多不同的属性标记参数

  1. [FromQuery]
  2. [FromForm]
  3. [FromBody]
  4. [FromHeader]
  5. [FromServices]*
  6. [FromKeyedServices]*

*最后两个是 .NET Core 8.x 的全新功能,我将不在这里介绍。但是,我将通过完整的示例介绍前四个属性。

背景

在编写另一篇文章(即将发布)时,我遇到了 .NET Core WebAPI 中自动绑定参数的问题,该文章中每个用户都有自己的数据库。

我写过很多 .NET Core WebAPI,但我注意到有时比其他时候更容易。我通常喜欢我的 WebAPI 使用 [FromBody] 从发布的正文中获取数据。

但是,我发现了在某些情况下它对我失效的确切原因。

在 WebAPI 参数上使用 FromQuery

让我们从我个人认为最简单的数据发布方法开始,即在 WebAPI 方法上使用 [FromQuery] 属性。

源代码

  1. 我将在这里添加最少的代码,以便进行快速讨论,但如果您想查看源代码,可以从本文顶部下载或从我的 GitHub 仓库获取。
  2. 我将创建一个非常小的最小 WebAPI 项目,并且这些方法的名称在几乎所有情况下都相同,只是它们将为每个示例包含一个数字,因此如果您愿意,可以在本地启动 WebAPI 并自行尝试这些示例。

启动 WebAPI:使用浏览器测试

启动 WebAPI(从下载的代码中)后,您可以加载 URL 并使用浏览器控制台使用 JavaScript Fetch API POST 到 WebAPI。下面是它的截图

[FromQuery] 示例代码

[HttpPost] public ActionResult RegisterUser1([FromQuery] string uuid)

在我们的最小 API 中,您将看到此方法按以下方式定义

app.MapPost("/RegisterUser1", IResult ([FromQuery] string uuid) =>   {
    return Results.Ok(new { Message = $"QUERY - You sent in uuid: {uuid}" });
});

JavaScript Fetch 对于 [FromQuery]

fetch(`https://:5247/RegisterUser1?uuid=987-fake-uuid-1234`,
      {method: 'POST'})
        .then(response => response.json())
        .then(data => console.log(data));

那个很简单。如果您在 URL 中提供查询字符串项(?uuid),那么该项将自动绑定到 uuid 字符串变量,您将获得一个有效的结果。但是,如果您不提供查询字符串值,那么当 WebAPI 尝试自动绑定时,将发生错误。

Error(错误)

 An unhandled exception has occurred while executing the request.
      Microsoft.AspNetCore.Http.BadHttpRequestException: 
Required parameter "string uuid" was not provided from query string.

在 WebAPI 参数上使用 FromForm

让我们使用 [FromForm] 属性定义我们的第二个 WebAPI 方法。

app.MapPost("/RegisterUser2", IResult ([FromForm] string uuid) =>   {
    return Results.Ok(new { Message = $"FORM - You sent in uuid: {uuid}" });
})

注意 - 防伪

我一开始对上面的命令运行 Fetch 时,WebAPI 端开始出现一个奇怪的错误消息,看起来像

Unhandled exception. System.InvalidOperationException: Unable to find the required
 services. Please add all the required services by calling 
'IServiceCollection.AddAntiforgery' in the application startup code.
   at Microsoft.AspNetCore.Builder
.AntiforgeryApplicationBuilderExtensions
.VerifyAntiforgeryServicesAreRegistered(IApplicationBuilder builder)

.NET Core 最小 WebAPI 的重大更改

幸运的是,我能够搜索并发现问题是什么以及如何解决它。唉!

 重大更改:IFormFile 参数需要防伪检查 - .NET | Microsoft Learn[^]

FromForm 的 WebAPI 略有改变

app.MapPost("/RegisterUser2", IResult ([FromForm] string uuid) =>   {
    return Results.Ok(new { Message = $"FORM - You sent in uuid: {uuid}" });
})
.DisableAntiforgery()

唉!总是有些事情!


FromForm 的 JS Fetch 调用

需要一些设置才能在 Web 表单上传递我们的数据。首先,我们必须创建 FormData 对象并添加我们的名称/值对。之后,我们可以发布数据。

// 1. Create the FormData object
var fd = new FormData();
// 2. Append the name/value pair(s) for values you want to send
fd.append("uuid", "123-test2-2345-uuid-551");
fetch(`https://:5247/RegisterUser2`,{
      method:'POST',
     body:fd,   // add the FormData object to the body data which will be posted
})
.then(response => response.json())
.then(data => console.log(data));

现在我们已经使用了两个效果良好的不同属性。让我们深入研究使用 [FromBody] 属性,这将带来很大的困难。

在 WebAPI 参数上使用 FromBody

app.MapPost("/RegisterUser3", IResult ([FromBody] string uuid) =>   {
    return Results.Ok(new { Message = $"FORM - You sent in uuid: {uuid}" });
})

FromBody 的 JavaScript Fetch 调用存在问题

乍一看,这个应该很容易,因为你可能认为你应该可以直接在 body 中传入一个字符串值。反正我是这么想的。

不起作用

fetch(`https://:5247/RegisterUser3`,{
      method:'POST',
     body:"yaka-yaka",   
})
.then(response => response.json())
.then(data => console.log(data));

然而,这甚至无法通过您的浏览器,因为它期望您为 body 定义一个对象(在两个 { } 花括号之间)。

接下来,您可能会认为您可以只创建一个包含目标参数 (uuid) 名称的对象并传递它,如下所示

不起作用 2

var data = {"uuid":"yaka-yaka"};
fetch(`https://:5247/RegisterUser3`,{
      method:'POST',
     body:data,   
})
.then(response => response.json())
.then(data => console.log(data));

这也不起作用,也无法通过您的浏览器。同样,它认为 body 对象的构造不正确。

不起作用 3

fetch(`https://:5247/RegisterUser3`,{
      method:'POST',
     body:{"uuid":"this-is-uuid-123"},   
})
.then(response => response.json())
.then(data => console.log(data));

仍然不起作用。您将收到 415 不支持的媒体类型。因此,您需要添加 Content-Type 对象并将其设置为 JSON,以便 Fetch 调用知道您打算使用 JSON 进行调用。这之所以发生,是因为您在 body 中添加了花括号。

不起作用 4:但确实击中了 WebAPI

这个确实击中了 WebAPI。

fetch(`https://:5247/RegisterUser3`,{
      method:'POST',
     body:{"uuid":"this-is-uuid-123"},   
    headers: {
           'Content-type':'application/json; charset=UTF-8',
       },
})
.then(response => response.json())
.then(data => console.log(data));

但是,现在您收到来自服务器的错误,其中指出

自动绑定错误:WebAPI 无法绑定到变量

Microsoft.AspNetCore.Http.BadHttpRequestException: Failed to read parameter 
"string uuid" from the request body as JSON.

这是一个自动绑定错误,因为 WebAPI 没有看到 uuid 值,即使我们确实通过 body 对象传递了它。

此时我惊呆了。

问题出在哪里:如何解决?

嗯,你不能在 JavaScript 端解决它。结果发现,你不能直接将字符串值作为参数传递给 WebAPI。相反,你必须创建一个与 客户端对象 匹配的 服务器端对象

创建服务器端对象

这是我们将添加到 Program.cs 的新类(仅用于示例目的)

record UuidHolder{
    public string uuid{get;set;}
}

定义 RegisterUser4 作为工作示例

我们将使用以下 WebAPI 作为我们的工作示例

app.MapPost("/RegisterUser4", IResult ([FromBody] UuidHolder uuid) =>   {
    return Results.Ok(new { Message = $"BODY - You sent in uuid: {uuid.uuid}" });
})

这将保留 RegisterUser3 WebAPI 方法,以便您可以看到它无法绑定。

现在,我们必须稍微修改我们的 JavaScript Fetch 调用,使其也使用 JSON.stringify(),然后一切都会正常工作。

FromBody 的 JS Fetch 和使用 JSON.Stringify

fetch(`https://:5247/RegisterUser4`,{
      method:'POST',
     body:JSON.stringify({"uuid":"this-is-uuid-123"}),   
    headers: {
           'Content-type':'application/json; charset=UTF-8',
       },
})
.then(response => response.json())
.then(data => console.log(data));

它奏效了!

我以为在正文中发送数据会是最简单的方法,但它是最困难的。让我们介绍最后一个(FromHeader)并总结一下。

在 WebAPI 参数上使用 FromHeader

这是最后一个,它允许我们将数据放在头部并发布。

app.MapPost("/RegisterUser5", IResult ([FromHeader] string uuid) =>   {
    return Results.Ok(new { Message = $"HEADER - You sent in uuid: {uuid}" });
})

JavaScript Fetch 用于 [FromHeader]

这个很容易做到,但我完全不确定我们为什么要使用头部发布数据。

但是,它确实有助于我们将数据自动绑定到我们可能拥有的某个头部值。

fetch(`https://:5247/RegisterUser5`,{
      method:'POST', 
    headers: {
           'Content-type':'application/json; charset=UTF-8',
            "uuid":"123-456-789"
       },
})
.then(response => response.json())
.then(data => console.log(data));

现在,您可以使用任何基本属性在 .NET Core WebAPI 方法中自动绑定,并且您的数据将通过。当它不起作用时,您就会知道去哪里查找。

额外材料 OpenApi 文档

哇!我刚刚(在发表我的文章之后)发现,我实际上通过 OpenApi 获得了一些免费的 API 文档。

dotnet 项目模板在 Program.cs 文件中添加了几行,用于生成文档。

 app.UseSwagger();
 app.UseSwaggerUI();

然后,在每个 post 方法上,我都添加了以下调用

.WithOpenApi();

如何查看 OpenApi 文档?

我搜索了整个网络,并浏览了这份解释 OpenApi 文档的冗长文件,但它从未向我展示如何查看文档。真是太疯狂了!我终于在这里找到了查看自动生成文档的方法。

要查看文档,请启动 webapi 并转到:https://:5247/swagger

您将看到以下内容

您也可以通过 Curl 试用 API

该文档是活动的,您现在可以通过 UI 试用 API。它在后台使用 curl 发布到 webapi,您将看到 curl 可以正确发布到 body 方法。但是,它在 FromForm 方法上失败了。真的很有趣。

历史

2024年6月9日:首次发布

© . All rights reserved.