Asp.Net Web API 2第十四課——Content Negotiation(內容協商)


前言

閱讀本文之前,您也可以到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,並根據請求的報送對其進行匹配(如果有)。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM