Negociación de contenido de ASP.NET Web API

En este artículo se describe cómo ASP.NET API Web implementa la negociación de contenido para ASP.NET 4.x.

La especificación HTTP (RFC 2616) define la negociación de contenido como "el proceso de selección de la mejor representación para una respuesta determinada cuando hay varias representaciones disponibles". El mecanismo principal para la negociación de contenido en HTTP son estos encabezados de solicitud:

  • Accept: qué tipos de elementos multimedia son aceptables para la respuesta, como "application/json", "application/xml" o un tipo de medio personalizado, como "application/vnd.example+xml"
  • Accept-Charset: qué conjuntos de caracteres son aceptables, como UTF-8 o ISO 8859-1.
  • Accept-Encoding: qué codificaciones de contenido son aceptables, como gzip.
  • Accept-Language: el lenguaje natural preferido, como "en-us".

El servidor también puede examinar otras partes de la solicitud HTTP. Por ejemplo, si la solicitud contiene un encabezado X-Requested-With, que indica una solicitud AJAX, el servidor podría establecer el valor predeterminado en JSON si no hay ningún encabezado Accept.

En este artículo, veremos cómo la API web usa los encabezados Accept y Accept-Charset. (En este momento, no hay compatibilidad integrada con Accept-Encoding o Accept-Language).

Serialización

Si un controlador de API web devuelve un recurso como tipo CLR, la canalización serializa el valor devuelto y lo escribe en el cuerpo de la respuesta HTTP.

Por ejemplo, considere la siguiente acción del controlador:

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

Un cliente puede enviar esta solicitud HTTP:

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

En respuesta, el servidor podría enviar:

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}

En este ejemplo, el cliente solicitó JSON, Javascript o "cualquier cosa" (*/*). El servidor respondió con una representación JSON del objeto Product. Observe que el encabezado Content-Type de la respuesta está establecido en "application/json".

Un controlador también puede devolver un objeto HttpResponseMessage. Para especificar un objeto CLR para el cuerpo de la respuesta, llame al método de extensión 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);
}

Esta opción proporciona más control sobre los detalles de la respuesta. Puede establecer el código de estado, agregar encabezados HTTP, etc.

El objeto que serializa el recurso se denomina formateador de medios. Los formateadores multimedia derivan de la clase MediaTypeFormatter. La API web proporciona formateadores de elementos multimedia para XML y JSON, y puede crear formateadores personalizados para admitir otros tipos de medios. Para obtener información sobre cómo escribir un formateador personalizado, consulte Formateadores multimedia.

Funcionamiento de la negociación de contenido

En primer lugar, la canalización obtiene el servicio IContentNegotiator del objeto HttpConfiguration. También obtiene la lista de formateadores multimedia de la colección HttpConfiguration.Formatters.

A continuación, la canalización llama a IContentNegotiator.Negotiate, pasando:

  • El tipo de objeto que se va a serializar
  • La colección de formateadores multimedia
  • La solicitud HTTP

El método Negotiate devuelve dos fragmentos de información:

  • Qué formateador usar
  • Tipo de elementos multimedia para la respuesta

Si no se encuentra ningún formateador, el método Negotiate devuelve null y el cliente recibe el error HTTP 406 (Not Acceptable).

El siguiente código muestra cómo un controlador puede invocar directamente la negociación de contenido:

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

Este código es equivalente a lo que hace automáticamente la canalización.

Negociación de contenido predeterminada

La clase DefaultContentNegotiator proporciona la implementación predeterminada de IContentNegotiator. Usa varios criterios para seleccionar un formateador.

En primer lugar, el formateador debe ser capaz de serializar el tipo. Esto se comprueba llamando a MediaTypeFormatter.CanWriteType.

A continuación, el negociador de contenido examina cada formateador y evalúa cómo coincide con la solicitud HTTP. Para evaluar la coincidencia, el negociador de contenido examina dos cosas en el formateador:

  • La colección SupportedMediaTypes, que contiene una lista de tipos de elementos multimedia admitidos. El negociador de contenido intenta hacer coincidir esta lista con el encabezado Accept de solicitud. Tenga en cuenta que el encabezado Accept puede incluir intervalos. Por ejemplo, "text/plain" es una coincidencia para text/* o */*.
  • La colección MediaTypeMappings, que contiene una lista de objetos MediaTypeMapping. La clase MediaTypeMapping proporciona una manera genérica de hacer coincidir las solicitudes HTTP con tipos de elementos multimedia. Por ejemplo, podría asignar un encabezado HTTP personalizado a un tipo de elemento multimedia determinado.

Si hay varias coincidencias, la coincidencia con el factor de mayor calidad gana. Por ejemplo:

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

En este ejemplo, application/json tiene un factor de calidad implícito de 1.0, por lo que se prefiere sobre application/xml.

Si no se encuentran coincidencias, el negociador de contenido intenta buscar coincidencias en el tipo de medio del cuerpo de la solicitud, si existe. Por ejemplo, si la solicitud contiene datos JSON, el negociador de contenido busca un formateador JSON.

Si todavía no hay coincidencias, el negociador de contenido simplemente elige el primer formateador que puede serializar el tipo.

Selección de una codificación de caracteres

Una vez seleccionado un formateador, el negociador de contenido elige la mejor codificación de caracteres examinando la propiedad SupportedEncodings en el formateador y haciéndolo coincidir con el encabezado Accept-Charset de la solicitud (si existe).