Inhaltsverhandlung in ASP.NET-Web-API

In diesem Artikel wird beschrieben, wie ASP.NET-Web-API Inhaltsverhandlung für ASP.NET 4.x implementiert.

Die HTTP-Spezifikation (RFC 2616) definiert die Inhaltsverhandlung als "den Prozess der Auswahl der besten Darstellung für eine bestimmte Antwort, wenn mehrere Darstellungen verfügbar sind". Der primäre Mechanismus für die Inhaltsverhandlung in HTTP sind die folgenden Anforderungsheader:

  • Akzeptieren: Welche Medientypen für die Antwort akzeptabel sind, z. B. "application/json", "application/xml" oder ein benutzerdefinierter Medientyp wie "application/vnd.example+xml"
  • Accept-Charset: Welche Zeichensätze zulässig sind, z. B. UTF-8 oder ISO 8859-1.
  • Accept-Encoding: Welche Inhaltscodierungen zulässig sind, z. B. gzip.
  • Accept-Language: Die bevorzugte natürliche Sprache, z. B. "en-us".

Der Server kann sich auch andere Teile der HTTP-Anforderung ansehen. Wenn die Anforderung beispielsweise einen X-Requested-With-Header enthält, der eine AJAX-Anforderung angibt, kann der Server standardmäßig JSON verwenden, wenn kein Accept-Header vorhanden ist.

In diesem Artikel wird erläutert, wie die Web-API die Header Accept und Accept-Charset verwendet. (Derzeit gibt es keine integrierte Unterstützung für Accept-Encoding oder Accept-Language.)

Serialisierung

Wenn ein Web-API-Controller eine Ressource als CLR-Typ zurückgibt, serialisiert die Pipeline den Rückgabewert und schreibt ihn in den HTTP-Antworttext.

Betrachten Sie beispielsweise die folgende Controlleraktion:

public Product GetProduct(int id)
{
    var item = _products.FirstOrDefault(p => p.ID == id);
    if (item == null)
    {
        throw new HttpResponseException(HttpStatusCode.NotFound);
    }
    return item; 
}

Ein Client kann diese HTTP-Anforderung senden:

GET http://localhost.:21069/api/products/1 HTTP/1.1
Host: localhost.:21069
Accept: application/json, text/javascript, */*; q=0.01

Als Antwort sendet der Server möglicherweise Folgendes:

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}

In diesem Beispiel hat der Client entweder JSON, Javascript oder "anything" (*/*) angefordert. Der Server hat mit einer JSON-Darstellung des Product -Objekts geantwortet. Beachten Sie, dass der Content-Type-Header in der Antwort auf "application/json" festgelegt ist.

Ein Controller kann auch ein HttpResponseMessage-Objekt zurückgeben. Um ein CLR-Objekt für den Antworttext anzugeben, rufen Sie die CreateResponse-Erweiterungsmethode auf:

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);
}

Diese Option bietet Ihnen mehr Kontrolle über die Details der Antwort. Sie können den status Code festlegen, HTTP-Header hinzufügen usw.

Das Objekt, das die Ressource serialisiert, wird als Medienformatierer bezeichnet. Medienformatierer werden von der MediaTypeFormatter-Klasse abgeleitet. Die Web-API stellt Medienformatierer für XML und JSON bereit, und Sie können benutzerdefinierte Formatierer erstellen, um andere Medientypen zu unterstützen. Informationen zum Schreiben eines benutzerdefinierten Formatierers finden Sie unter Medienformatierer.

Funktionsweise der Inhaltsverhandlung

Zunächst ruft die Pipeline den IContentNegotiator-Dienst aus dem HttpConfiguration-Objekt ab . Außerdem wird die Liste der Medienformatierer aus der HttpConfiguration.Formatters-Auflistung abgerufen.

Als Nächstes ruft die Pipeline IContentNegotiator.Negotiate auf und übergibt Folgendes:

  • Der Typ des zu serialisierenden Objekts.
  • Die Auflistung von Medienformatierern
  • Die HTTP-Anforderung

Die Negotiate-Methode gibt zwei Informationen zurück:

  • Welcher Formatierer verwendet werden soll
  • Der Medientyp für die Antwort

Wenn kein Formatierer gefunden wird, gibt die Negotiate-MethodeNULL zurück, und der Client empfängt den HTTP-Fehler 406 (Nicht akzeptabel).

Der folgende Code zeigt, wie ein Controller die Inhaltsverhandlung direkt aufrufen kann:

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
        )
    };
}

Dieser Code entspricht dem, was die Pipeline automatisch ausführt.

Standard-Inhaltsunterhandlung

Die DefaultContentNegotiator-Klasse stellt die Standardimplementierung von IContentNegotiator bereit. Es werden mehrere Kriterien verwendet, um einen Formatierer auszuwählen.

Zunächst muss der Formatierer in der Lage sein, den Typ zu serialisieren. Dies wird durch Aufrufen von MediaTypeFormatter.CanWriteType überprüft.

Als Nächstes prüft der Inhaltsunterhändler jeden Formatierer und wertet aus, wie gut er mit der HTTP-Anforderung übereinstimmt. Um die Übereinstimmung auszuwerten, untersucht der Inhaltsverhandler zwei Dinge auf dem Formatierer:

  • Die SupportedMediaTypes-Auflistung , die eine Liste der unterstützten Medientypen enthält. Der Inhaltsunterhändler versucht, diese Liste mit dem Accept-Anforderungsheader abzugleichen. Beachten Sie, dass der Accept-Header Bereiche enthalten kann. Beispielsweise ist "text/plain" eine Übereinstimmung für text/* oder */*.
  • Die MediaTypeMappings-Auflistung , die eine Liste von MediaTypeMapping-Objekten enthält. Die MediaTypeMapping-Klasse bietet eine generische Möglichkeit zum Abgleichen von HTTP-Anforderungen mit Medientypen. Beispielsweise könnte ein benutzerdefinierter HTTP-Header einem bestimmten Medientyp zugeordnet werden.

Wenn mehrere Übereinstimmungen vorhanden sind, gewinnt die Übereinstimmung mit dem höchsten Qualitätsfaktor. Zum Beispiel:

Accept: application/json, application/xml; q=0.9, */*; q=0.1

In diesem Beispiel weist application/json einen impliziten Qualitätsfaktor von 1,0 auf, sodass er gegenüber application/xml bevorzugt wird.

Wenn keine Übereinstimmungen gefunden werden, versucht der Inhaltsverhandler, den Medientyp des Anforderungstexts abzugleichen, falls vorhanden. Wenn die Anforderung beispielsweise JSON-Daten enthält, sucht der Inhaltsverhandler nach einem JSON-Formatierer.

Wenn immer noch keine Übereinstimmungen vorhanden sind, wählt der Inhaltsverhandler einfach den ersten Formatierer aus, der den Typ serialisieren kann.

Auswählen einer Zeichencodierung

Nachdem ein Formatierer ausgewählt wurde, wählt der Inhaltsverhandler die beste Zeichencodierung aus, indem er die SupportedEncodings-Eigenschaft im Formatierer betrachtet und mit dem Accept-Charset-Header in der Anforderung abgleicht (falls vorhanden).