Content Negotiation in ASP.NET Web API
本文描述Web API实现内容协商(content negotiation)。
The HTTP specification (RFC 2616) defines content negotiation as “the process of selecting the best representation for a given response when there are multiple representations available.”
HTTP中内容协商机制是由以下请求头实现的:
- Accept: 响应支持的媒体类型, 比如 “application/json,” “application/xml,” 或者自定义的媒体类型如 "application/vnd.example+xml"
- Accept-Charset: 接受哪种字符集,例如 UTF-8 或 ISO 8859-1.
- Accept-Encoding: 接受哪种内容编码,例如 gzip.
- Accept-Language: 优先支持的自然语言,例如 “en-us”.
如果没有Accept头部,服务器也可以通过查看其它部分来决定。例如,如果头部包含X-Requested-With,表明是一个AJAX请求,那么服务器默认选择JSON格式。
本文中,我们将看到Web API使用Accept和Accept-Charset头部.(目前为止,没有内置的实现支持Accept-Encoding或者Accept-Language)
1. 序列化
如果Web API返回一个CLR类型,那么闲序列化,然后写到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}
序列化资源的对象称为媒体格式化器(media formatter)。查看MediaTypeFormatter类,Web API默认支持XML和JSON,也可以自定义。
2. 内容协商怎么工作?
首先,Web API管道从HttpConfiguration对象获取IContentNegotiator服务,IContentNegotiator服务从HttpConfiguration.Formatters集合获取到_媒体格式化器_,
接着,管道调用IContentNegotiatior.Negotiate,传递:
-
要序列化的类型
-
媒体格式化器列表
-
HTTP 请求
Negotiate方法返回两部分信息:
-
使用哪个媒体格式化器(media formatter)
-
返回什么媒体类型(media type)
如果找不到格式器,Negotiate方法返回null,客户端收到一个406(Not Acceptable)错误。
解释内容协商的演示代码:
public HttpResponseMessage GetProduct(int id, bool contentNegotiate)
{
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的默认实现。
首先,媒体格式化器(formatter)要能够序列化当前类型,CanWriteType方法来验证的。
接着,内容协商器查看每一个媒体格式化器,并计算和HTTP请求的匹配度。
-
SupportedMediaTypes集合。内容协商器匹配每一个Accept头部
-
MediaTypeMappings集合。
如果有多个匹配,选择质量因子最高的一个。
Accept: application/json, application/xml; q=0.9, */*; q=0.1
如上,application/json将是最优的。
如果没有任何的匹配,那么内容协商器将匹配请求体,如果请求体是JSON数据,那么内容协商器将寻找一个JSON序列化器。
如果仍没有匹配,那么内容协商器将简单地选择第一个能够序列化当前类型的序列化器。
3. 字符编码选择
序列化器选定之后,通过序列化器上的SupportedEncodings属性来选择最好的内容编码,并且与Accept-Charset头部能够匹配。