ASP.NET Web API中的内容协商
本文介绍 ASP.NET Web API如何实现 ASP.NET 4.x 的内容协商。
HTTP 规范 (RFC 2616) 将内容协商定义为“当有多个表示形式可用时,为给定响应选择最佳表示形式的过程”。HTTP 中内容协商的主要机制是以下请求标头:
- 接受: 响应可接受的媒体类型,例如“application/json”、“application/xml”或自定义媒体类型(如“application/vnd.example+xml” )
- Accept-Charset: 哪些字符集是可接受的,例如 UTF-8 或 ISO 8859-1。
- Accept-Encoding: 哪些内容编码是可接受的,例如 gzip。
- Accept-Language: 首选的自然语言,例如“en-us”。
服务器还可以查看 HTTP 请求的其他部分。 例如,如果请求包含指示 AJAX 请求的 X-Requested-With 标头,则如果没有 Accept 标头,服务器可能默认为 JSON。
本文介绍 Web API 如何使用 Accept 和 Accept-Charset 标头。 (目前,没有对 Accept-Encoding 或 Accept-Language.)
序列化
如果 Web API 控制器以 CLR 类型返回资源,则管道将序列化返回值并将其写入 HTTP 响应正文。
例如,请考虑以下控制器操作:
public Product GetProduct(int id)
{
var item = _products.FirstOrDefault(p => p.ID == id);
if (item == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return item;
}
客户端可能会发送此 HTTP 请求:
GET http://localhost.:21069/api/products/1 HTTP/1.1
Host: localhost.:21069
Accept: application/json, text/javascript, */*; q=0.01
作为响应,服务器可能会发送:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 57
Connection: Close
{"Id":1,"Name":"Gizmo","Category":"Widgets","Price":1.99}
在此示例中,客户端请求 JSON、Javascript 或“任何内容” (*/*) 。 服务器使用 对象的 JSON 表示形式 Product
进行响应。 请注意,响应中的 Content-Type 标头设置为“application/json”。
控制器还可以返回 HttpResponseMessage 对象。 若要为响应正文指定 CLR 对象,请调用 CreateResponse 扩展方法:
public HttpResponseMessage GetProduct(int id)
{
var item = _products.FirstOrDefault(p => p.ID == id);
if (item == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return Request.CreateResponse(HttpStatusCode.OK, item);
}
使用此选项可以更好地控制响应的详细信息。 可以设置状态代码、添加 HTTP 标头等。
序列化资源的对象称为 媒体格式化程序。 媒体格式化程序派生自 MediaTypeFormatter 类。 Web API 为 XML 和 JSON 提供媒体格式化程序,你可以创建自定义格式化程序以支持其他媒体类型。 有关编写自定义格式化程序的信息,请参阅 媒体格式化程序。
内容协商的工作原理
首先,管道从 HttpConfiguration 对象获取 IContentNegotiator 服务。 它还从 HttpConfiguration.Formatters 集合中获取媒体格式化程序的列表。
接下来,管道调用 IContentNegotiator.Negotiate,传入:
- 要序列化的对象的类型
- 媒体格式化程序集合
- HTTP 请求
Negotiate 方法返回两条信息:
- 要使用的格式化程序
- 响应的媒体类型
如果未找到格式化程序,则 Negotiate 方法返回 null,并且客户端收到 HTTP 错误 406 (“不可接受) ”。
以下代码演示控制器如何直接调用内容协商:
public HttpResponseMessage GetProduct(int id)
{
var product = new Product()
{ Id = id, Name = "Gizmo", Category = "Widgets", Price = 1.99M };
IContentNegotiator negotiator = this.Configuration.Services.GetContentNegotiator();
ContentNegotiationResult result = negotiator.Negotiate(
typeof(Product), this.Request, this.Configuration.Formatters);
if (result == null)
{
var response = new HttpResponseMessage(HttpStatusCode.NotAcceptable);
throw new HttpResponseException(response));
}
return new HttpResponseMessage()
{
Content = new ObjectContent<Product>(
product, // What we are serializing
result.Formatter, // The media formatter
result.MediaType.MediaType // The MIME type
)
};
}
此代码等效于管道自动执行的操作。
默认内容协商程序
DefaultContentNegotiator 类提供 IContentNegotiator 的默认实现。 它使用多个条件来选择格式化程序。
首先,格式化程序必须能够序列化类型。 这可以通过调用 MediaTypeFormatter.CanWriteType 进行验证。
接下来,内容协商程序会查看每个格式化程序,并评估它与 HTTP 请求的匹配程度。 为了评估匹配项,内容协商程序在格式化程序上查看两项内容:
- SupportedMediaTypes 集合,其中包含受支持媒体类型的列表。 内容协商程序尝试将此列表与请求 Accept 标头匹配。 请注意,Accept 标头可以包含范围。 例如,“text/plain”是 text/* 或 */*的匹配项。
- MediaTypeMappings 集合,其中包含 MediaTypeMapping 对象的列表。 MediaTypeMapping 类提供一种通用方法,用于将 HTTP 请求与媒体类型匹配。 例如,它可以将自定义 HTTP 标头映射到特定媒体类型。
如果有多个匹配项,则具有最高质量因素的匹配将获胜。 例如:
Accept: application/json, application/xml; q=0.9, */*; q=0.1
在此示例中,application/json 的隐含质量系数为 1.0,因此它优先于 application/xml。
如果未找到匹配项,则内容协商程序会尝试在请求正文的媒体类型(如果有)上匹配。 例如,如果请求包含 JSON 数据,则内容协商程序会查找 JSON 格式化程序。
如果仍然没有匹配项,则内容协商程序只需选取可序列化该类型的第一个格式化程序。
选择字符编码
选择格式化程序后,内容协商程序会通过查看格式化程序上的 SupportedEncodings 属性来选择最佳字符编码,并将其与请求 (Accept-Charset标头(如果有任何) )进行匹配。