ASP.NET Web API 中的內容協商
本文介紹 ASP.NET Web API 如何實作 ASP.NET 4.x 的內容協商。
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 的內建支援。)
序列化
如果 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, 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 標頭 (如果有) 進行匹配來選擇最佳字元編碼。