Negocjowanie zawartości w interfejsie API sieci Web ASP.NET
W tym artykule opisano, jak ASP.NET internetowy interfejs API implementuje negocjacje zawartości dla ASP.NET 4.x.
Specyfikacja HTTP (RFC 2616) definiuje negocjacje zawartości jako "proces wybierania najlepszej reprezentacji dla danej odpowiedzi, gdy dostępnych jest wiele reprezentacji". Podstawowy mechanizm negocjacji zawartości w protokole HTTP to następujące nagłówki żądań:
- Zaakceptować: Które typy multimediów są dopuszczalne dla odpowiedzi, takie jak "application/json", "application/xml" lub niestandardowy typ nośnika, taki jak "application/vnd.example+xml"
- Accept-Charset: Które zestawy znaków są dopuszczalne, takie jak UTF-8 lub ISO 8859-1.
- Kodowanie akceptowanych: Które kodowanie zawartości jest dopuszczalne, takie jak gzip.
- Akceptuj język: Preferowany język naturalny, taki jak "en-us".
Serwer może również przyjrzeć się innym fragmentom żądania HTTP. Jeśli na przykład żądanie zawiera nagłówek X-Requested-With wskazujący żądanie AJAX, serwer może domyślnie ustawić wartość JSON, jeśli nie ma nagłówka Accept.
W tym artykule przyjrzymy się, w jaki sposób internetowy interfejs API używa nagłówków Accept i Accept-Charset. (Obecnie nie ma wbudowanej obsługi Accept-Encoding lub Accept-Language).
Serializacja
Jeśli kontroler internetowego interfejsu API zwraca zasób jako typ CLR, potok serializuje wartość zwracaną i zapisuje ją w treści odpowiedzi HTTP.
Rozważmy na przykład następującą akcję kontrolera:
public Product GetProduct(int id)
{
var item = _products.FirstOrDefault(p => p.ID == id);
if (item == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return item;
}
Klient może wysłać to żądanie HTTP:
GET http://localhost.:21069/api/products/1 HTTP/1.1
Host: localhost.:21069
Accept: application/json, text/javascript, */*; q=0.01
W odpowiedzi serwer może wysłać:
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}
W tym przykładzie klient zażądał JSON, Javascript lub "wszystko" (*/*). Serwer odpowiedział reprezentacją obiektu w formacie JSON Product
. Zwróć uwagę, że nagłówek Content-Type w odpowiedzi ma wartość "application/json".
Kontroler może również zwrócić obiekt HttpResponseMessage . Aby określić obiekt CLR dla treści odpowiedzi, wywołaj metodę rozszerzenia 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);
}
Ta opcja zapewnia większą kontrolę nad szczegółami odpowiedzi. Możesz ustawić kod stanu, dodać nagłówki HTTP itd.
Obiekt, który serializuje zasób, jest nazywany formaterem multimediów. Formatery multimediów pochodzą z klasy MediaTypeFormatter . Internetowy interfejs API udostępnia formatery multimediów dla formatu XML i JSON oraz można tworzyć niestandardowe formatery do obsługi innych typów multimediów. Aby uzyskać informacje na temat pisania niestandardowego formatu, zobacz Media Formatters (Formatery multimediów).
Jak działa negocjowanie zawartości
Najpierw potok pobiera usługę IContentNegotiator z obiektu HttpConfiguration . Pobiera również listę formaterów multimediów z kolekcji HttpConfiguration.Formatters .
Następnie potok wywołuje metodę IContentNegotiator.Negotiate, przekazując:
- Typ obiektu do serializacji
- Kolekcja formaterów multimediów
- Żądanie HTTP
Metoda Negotiate zwraca dwie informacje:
- Który formater do użycia
- Typ nośnika odpowiedzi
Jeśli nie znaleziono formatatora, metoda Negotiate zwraca wartość null, a klient otrzymuje błąd HTTP 406 (Nie do przyjęcia).
Poniższy kod pokazuje, jak kontroler może bezpośrednio wywoływać negocjacje zawartości:
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
)
};
}
Ten kod jest odpowiednikiem tego, co potok wykonuje automatycznie.
Domyślny negocjator zawartości
Klasa DefaultContentNegotiator udostępnia domyślną implementację IContentNegotiator. Używa kilku kryteriów do wybrania formatnika.
Najpierw formater musi mieć możliwość serializacji typu. Jest to weryfikowane przez wywołanie elementu MediaTypeFormatter.CanWriteType.
Następnie negocjator zawartości analizuje każdy formater i ocenia, jak dobrze pasuje do żądania HTTP. Aby ocenić dopasowanie, negocjator zawartości analizuje dwie elementy w formacie:
- Kolekcja SupportedMediaTypes zawierająca listę obsługiwanych typów multimediów. Negocjator zawartości próbuje dopasować tę listę do nagłówka Accept żądania. Należy pamiętać, że nagłówek Accept może zawierać zakresy. Na przykład "tekst/zwykły" jest dopasowaniem tekstu/* lub */*.
- Kolekcja MediaTypeMappings zawierająca listę obiektów MediaTypeMapping . Klasa MediaTypeMapping zapewnia ogólny sposób dopasowania żądań HTTP do typów multimediów. Na przykład może mapować niestandardowy nagłówek HTTP na określony typ nośnika.
Jeśli istnieje wiele meczów, mecz z najwyższą jakością wygrywa. Na przykład:
Accept: application/json, application/xml; q=0.9, */*; q=0.1
W tym przykładzie plik application/json ma dorozumiany współczynnik jakości 1.0, dlatego jest preferowany przez plik application/xml.
Jeśli nie znaleziono dopasowań, negocjator zawartości próbuje dopasować typ nośnika treści żądania, jeśli istnieje. Jeśli na przykład żądanie zawiera dane JSON, negocjator zawartości szuka formatu JSON.
Jeśli nadal nie ma dopasowań, negocjator zawartości po prostu wybiera pierwszy formater, który może serializować typ.
Wybieranie kodowania znaków
Po wybraniu modułu formatującego negocjator zawartości wybiera najlepsze kodowanie znaków, przeglądając właściwość SupportedEncodings w module formatującym i pasując do nagłówka Accept-Charset w żądaniu (jeśli istnieje).