前言
閱讀本文之前,您也可以到Asp.Net Web API 2 系列導航進行查看 http://www.cnblogs.com/aehyok/p/3446289.html
本文描述ASP.NET Web API如何實現內容協商。
HTTP規范(RFC 2616)將內容協商定義為“在有多個表現可用時,為一個給定的響應選擇最佳表現的過程”。在HTTP中內容協商的主要機制是以下請求報頭:
- Accept:響應可接收的媒體類型,如“application/json”、“application/xml”,或者自定義媒體類型,如“application/vnd.example+xml”。
- Accept-Charset:可接收的字符集,如“UTF-8”或“ISO 8859-1”。
- Accept-Encoding:可接收的內容編碼,如“gzip”。
- Accept-Language:優先選用的自然語言,如“en-us”。
服務器也可以查看HTTP請求的其它選項。例如,如果該請求含有一個X-Requested-With報頭,它指示這是一個AJAX請求,在沒有Accept報頭的情況下,服務器可能會默認使用JSON。
本文將考察Web API如何使用Accept和Accept-Charset報頭。(目前,還沒有對Accept-Encoding或Accept-Language的內建支持。)
Serialization——序列化
如果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、或“任意格式(*/*)”。服務器以一個Product對象的JSON表示作出了響應。注意,響應中的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, product); }
該選項讓你能夠對響應細節進行更多的控制。你可以設置狀態碼、添加HTTP報頭等等。
對資源進行序列化的對象叫做媒體格式化器。媒體格式化器派生於MediaTypeFormatter類。Web API提供了XML和JSON的媒體格式化器,因而你可以創建自定義的格式化器,以支持其它媒體類型。更多關於編寫自定義格式化器的信息 http://www.cnblogs.com/aehyok/p/3460164.html。
內容協商的工作機制
首先,管線會獲取HttpConfiguration對象的IContentNegotiator服務。它也會得到HttpConfiguration.Formatters集合的媒體格式化器列表。
接着,管線會調用IContentNegotiatior.Negotiate,在其中傳遞:
- 要序列化的對象類型
- 媒體格式化器集合
- HTTP請求
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(MIME類型) ) }; }
上述代碼等價於管線的自動完成。
默認的內容協定
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,並根據請求的報送對其進行匹配(如果有)。