为什么我们需要指定FromBody和FromUri?

202

为什么在ASP.NET Web API中需要使用FromBodyFromUri属性?

使用这些属性和不使用它们有什么区别?


15
使用[FromBody]注解的一个实用场景是:在URL中编码发送静态凭据(如用户名/密码)作为参数时,这是一种不良实践。即使SSL加密可以防止第三方读取URL参数,但仍然是不良实践,因为这些凭据可能存储在浏览器日志和缓存中,这绝对是不希望看到的。在这种情况下,可以使用[FromBody]注解,将参数强制存储在HTTP消息正文中,从而提高安全性。 - Chris
4个回答

238
当ASP.NET Web API在控制器上调用方法时,它必须为参数设置值,这个过程称为“参数绑定”。默认情况下,Web API使用以下规则来绑定参数:
  • 如果参数是“简单类型”,Web API会尝试从URI中获取值。简单类型包括.NET基元类型(int、bool、double等)、TimeSpan、DateTime、Guid、decimal和string,以及任何具有类型转换器可以从字符串转换的类型。
  • 对于复杂类型,Web API尝试使用媒体类型格式化程序从消息正文中读取值。
因此,如果您想覆盖上述默认行为并强制Web API从URI中读取复杂类型,请将[FromUri]属性添加到参数中。要强制Web API从请求正文中读取简单类型,请将[FromBody]属性添加到参数中。
因此,回答你的问题,Web API 中需要使用 [FromBody][FromUri] 属性只是为了覆盖默认行为(如上所述),如果必要的话。请注意,你可以在控制器方法中同时使用这两个属性,但只能用于不同的参数,如 here 所示。
如果你搜索“web api parameter binding”,会有 a lot more information 可供参考。

6
如果您遵循默认行为,那么您不需要使用这些属性,但如果您想改变默认行为,就需要使用它们。请注意,翻译后的内容不能改变原意,并且不提供额外的解释或信息。 - djikay
1
如果它正在执行其默认行为,那么为什么我们需要覆盖它?如果我们提到这个属性,我们将获得什么好处? - Rajneesh
1
@user3510527你不需要覆盖重写。你可以直接使用默认行为。有一个例子,如果某人希望在请求正文中提供一个简单的整数,因为默认情况下它会在URI中寻找它,那么他们可能希望覆盖默认行为。基本上,如果你想要,你可以保留默认行为或者进行覆盖重写,这只是你拥有的选项。我不明白混淆出在哪里。 - djikay
8
我想知道是否可以创建一个名为JustGetIt的属性,它的作用与添加多个属性如[FromBody, FromQuery]等相同。 - The Muffin Man

139
默认行为如下:
  1. 如果参数是一个基本类型(例如:intbooldouble等),Web API会尝试从HTTP请求的URI获取该值。

  2. 对于复杂类型(例如:您自己的对象,例如:Person),Web API会尝试从HTTP请求的主体中读取该值。

因此,如果您有:

  • 在URI中有一个基本类型,或者
  • 在主体中有一个复杂类型

...那么您不需要添加任何属性(既不是[FromBody]也不是[FromUri])。

但是,如果您在主体中有一个基本类型,则必须在WebAPI控制器方法中的基本类型参数前面添加[FromBody]。(因为默认情况下,WebAPI正在查找HTTP请求的URI中的基本类型。)

或者,如果您在URI中有一个复杂类型,则必须添加[FromUri]。(因为默认情况下,WebAPI正在HTTP请求的主体中查找复杂类型。)

基本类型:

public class UsersController : ApiController
{
    // api/users
    public HttpResponseMessage Post([FromBody]int id)
    {

    }
    // api/users/id
    public HttpResponseMessage Post(int id)
    {

    }       
}

复杂类型:

public class UsersController : ApiController
{       
    // api/users
    public HttpResponseMessage Post(User user)
    {

    }

    // api/users/user
    public HttpResponseMessage Post([FromUri]User user)
    {

    }       
}

只要您在HTTP请求中发送一个参数,这就可以正常工作。但是,当发送多个参数时,您需要创建一个自定义模型,该模型包含您所有的参数,如下所示:

public class MyModel
{
    public string MyProperty { get; set; }
    public string MyProperty2 { get; set; }
}

[Route("search")]
[HttpPost]
public async Task<dynamic> Search([FromBody] MyModel model)
{
    // model.MyProperty;
    // model.MyProperty2;
}

以下是微软关于ASP.NET Web API参数绑定的文档:

When a parameter has [FromBody], Web API uses the Content-Type header to select a formatter. In this example, the content type is "application/json" and the request body is a raw JSON string (not a JSON object). At most one parameter is allowed to read from the message body.

This should work:

public HttpResponseMessage Post([FromBody] string name) { ... }

This will not work:

// Caution: This won't work!    
public HttpResponseMessage Post([FromBody] int id, [FromBody] string name) { ... }

The reason for this rule is that the request body might be stored in a non-buffered stream that can only be read once.


12
“最多只允许从消息体中读取一个参数”是非常有帮助的信息。 - Ryan
这不是真的。我在URL中使用int Id,在正文中使用对象,它可以正常工作。但是我的对象上确实有[FromBody],所以可能需要这个。 - PRMan

23

仅为以上答案添加信息...

[FromUri]也可以用来从URI参数绑定复杂类型,而不是通过查询字符串传递参数。

例如...

public class GeoPoint
{
    public double Latitude { get; set; } 
    public double Longitude { get; set; }
}

[RoutePrefix("api/Values")]
public ValuesController : ApiController
{
    [Route("{Latitude}/{Longitude}")]
    public HttpResponseMessage Get([FromUri] GeoPoint location) { ... }
}

可以这样调用:

http://localhost/api/values/47.678558/-122.130989

21
当参数被标记为[FromBody]时,Web API会使用Content-Type头来选择格式化程序。在这个例子中,内容类型是“application/json”,请求体是一个原始的JSON字符串(不是一个JSON对象)。 最多只允许一个参数从消息体中读取数据。所以以下代码将不起作用:
// Caution: Will not work!    
public HttpResponseMessage Post([FromBody] int id, [FromBody] string name) { ... }

这个规则的原因是请求正文可能存储在非缓冲流中,该流只能被读取一次。

请访问以下网站了解更多详情: https://learn.microsoft.com/en-us/aspnet/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接