Négociation de contenu dans API Web ASP.NET

Cet article explique comment API Web ASP.NET implémente la négociation de contenu pour ASP.NET 4.x.

La spécification HTTP (RFC 2616) définit la négociation du contenu comme « le processus de sélection de la meilleure représentation pour une réponse donnée lorsqu’il existe plusieurs représentations disponibles ». Le mécanisme principal pour la négociation du contenu dans HTTP sont les en-têtes de requête suivants :

  • Accepter: Quels types de médias sont acceptables pour la réponse, tels que « application/json », « application/xml » ou un type de média personnalisé tel que « application/vnd.example+xml »
  • Accept-Charset : Quels jeux de caractères sont acceptables, tels que UTF-8 ou ISO 8859-1.
  • Accept-Encoding : Quels encodages de contenu sont acceptables, tels que gzip.
  • Accept-Language : Le langage naturel préféré, tel que « en-us ».

Le serveur peut également examiner d’autres parties de la requête HTTP. Par exemple, si la requête contient un en-tête X-Requested-With, indiquant une requête AJAX, le serveur peut avoir la valeur JSON par défaut s’il n’y a pas d’en-tête Accept.

Dans cet article, nous allons examiner comment l’API web utilise les en-têtes Accepter et Accept-Charset. (À l’heure actuelle, il n’existe aucune prise en charge intégrée pour Accept-Encoding ou Accept-Language.)

Sérialisation

Si un contrôleur d’API web retourne une ressource en tant que type CLR, le pipeline sérialise la valeur de retour et l’écrit dans le corps de la réponse HTTP.

Par exemple, considérez l’action de contrôleur suivante :

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

Un client peut envoyer cette requête HTTP :

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

En réponse, le serveur peut envoyer :

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}

Dans cet exemple, le client a demandé JSON, Javascript ou « tout » (*/*). Le serveur a répondu avec une représentation JSON de l’objet Product . Notez que l’en-tête Content-Type dans la réponse est défini sur « application/json ».

Un contrôleur peut également retourner un objet HttpResponseMessage . Pour spécifier un objet CLR pour le corps de la réponse, appelez la méthode d’extension 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);
}

Cette option vous donne plus de contrôle sur les détails de la réponse. Vous pouvez définir le code status, ajouter des en-têtes HTTP, etc.

L’objet qui sérialise la ressource est appelé formateur multimédia. Les formateurs multimédias dérivent de la classe MediaTypeFormatter . L’API web fournit des formateurs multimédias pour XML et JSON, et vous pouvez créer des formateurs personnalisés pour prendre en charge d’autres types de médias. Pour plus d’informations sur l’écriture d’un formateur personnalisé, consultez Media Formatters.

Fonctionnement de la négociation de contenu

Tout d’abord, le pipeline obtient le service IContentNegotiator à partir de l’objet HttpConfiguration . Il obtient également la liste des formateurs multimédias à partir de la collection HttpConfiguration.Formatters .

Ensuite, le pipeline appelle IContentNegotiator.Negotiate, en passant :

  • Type d’objet à sérialiser
  • Collection de formateurs multimédias
  • Requête HTTP

La méthode Negotiate retourne deux informations :

  • Quel formateur utiliser
  • Type de média pour la réponse

Si aucun formateur n’est trouvé, la méthode Negotiate retourne null et le client reçoit l’erreur HTTP 406 (Non acceptable).

Le code suivant montre comment un contrôleur peut appeler directement la négociation de contenu :

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

Ce code équivaut à ce que fait automatiquement le pipeline.

Négociateur de contenu par défaut

La classe DefaultContentNegotiator fournit l’implémentation par défaut d’IContentNegotiator. Il utilise plusieurs critères pour sélectionner un formateur.

Tout d’abord, le formateur doit être en mesure de sérialiser le type. Cela est vérifié en appelant MediaTypeFormatter.CanWriteType.

Ensuite, le négociateur de contenu examine chaque formateur et évalue dans quelle mesure il correspond à la requête HTTP. Pour évaluer la correspondance, le négociateur de contenu examine deux éléments sur le formateur :

  • Collection SupportedMediaTypes , qui contient une liste des types de médias pris en charge. Le négociateur de contenu tente de faire correspondre cette liste à l’en-tête Accept de la demande. Notez que l’en-tête Accept peut inclure des plages. Par exemple, « text/plain » correspond à text/* ou */*.
  • Collection MediaTypeMappings , qui contient une liste d’objets MediaTypeMapping . La classe MediaTypeMapping fournit un moyen générique de faire correspondre les requêtes HTTP aux types de médias. Par exemple, il peut mapper un en-tête HTTP personnalisé à un type de média particulier.

S’il existe plusieurs correspondances, la correspondance avec le facteur de qualité le plus élevé gagne. Par exemple :

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

Dans cet exemple, application/json a un facteur de qualité implicite de 1.0, il est donc préféré à application/xml.

Si aucune correspondance n’est trouvée, le négociateur de contenu tente de faire correspondre le type de média du corps de la demande, le cas échéant. Par exemple, si la demande contient des données JSON, le négociateur de contenu recherche un formateur JSON.

S’il n’y a toujours aucune correspondance, le négociateur de contenu choisit simplement le premier formateur qui peut sérialiser le type.

Sélection d’un encodage de caractères

Une fois qu’un formateur est sélectionné, le négociateur de contenu choisit le meilleur encodage de caractères en examinant la propriété SupportedEncodings sur le formateur et en la faisant correspondre à l’en-tête Accept-Charset dans la demande (le cas échéant).