Negociação de conteúdo em ASP.NET Web API
Este artigo descreve como ASP.NET Web API implementa a negociação de conteúdo para ASP.NET 4.x.
A especificação HTTP (RFC 2616) define a negociação de conteúdo como "o processo de seleção da melhor representação para uma determinada resposta quando há várias representações disponíveis". O mecanismo principal para negociação de conteúdo em HTTP são estes cabeçalhos de solicitação:
- Aceitar: Quais tipos de mídia são aceitáveis para a resposta, como "application/json", "application/xml" ou um tipo de mídia personalizado, como "application/vnd.example+xml"
- Accept-Charset: Quais conjuntos de caracteres são aceitáveis, como UTF-8 ou ISO 8859-1.
- Accept-Encoding: Quais codificações de conteúdo são aceitáveis, como gzip.
- Accept-Language: A linguagem natural preferida, como "en-us".
O servidor também pode examinar outras partes da solicitação HTTP. Por exemplo, se a solicitação contiver um cabeçalho X-Requested-With, indicando uma solicitação AJAX, o servidor poderá usar json como padrão se não houver cabeçalho Accept.
Neste artigo, examinaremos como a API Web usa os cabeçalhos Accept e Accept-Charset. (No momento, não há suporte interno para Accept-Encoding ou Accept-Language.)
Serialização
Se um controlador de API Web retornar um recurso como tipo CLR, o pipeline serializará o valor retornado e o gravará no corpo da resposta HTTP.
Por exemplo, considere a seguinte ação do controlador:
public Product GetProduct(int id)
{
var item = _products.FirstOrDefault(p => p.ID == id);
if (item == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return item;
}
Um cliente pode enviar essa solicitação HTTP:
GET http://localhost.:21069/api/products/1 HTTP/1.1
Host: localhost.:21069
Accept: application/json, text/javascript, */*; q=0.01
Em resposta, o servidor pode 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}
Neste exemplo, o cliente solicitou JSON, Javascript ou "qualquer coisa" (*/*). O servidor respondeu com uma representação JSON do Product
objeto . Observe que o cabeçalho Content-Type na resposta está definido como "application/json".
Um controlador também pode retornar um objeto HttpResponseMessage . Para especificar um objeto CLR para o corpo da resposta, chame o método de extensão 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);
}
Essa opção oferece mais controle sobre os detalhes da resposta. Você pode definir o código status, adicionar cabeçalhos HTTP e assim por diante.
O objeto que serializa o recurso é chamado de formatador de mídia. Os formatadores de mídia derivam da classe MediaTypeFormatter . A API Web fornece formatadores de mídia para XML e JSON e você pode criar formatadores personalizados para dar suporte a outros tipos de mídia. Para obter informações sobre como escrever um formatador personalizado, consulte Formatadores de Mídia.
Como funciona a negociação de conteúdo
Primeiro, o pipeline obtém o serviço IContentNegotiator do objeto HttpConfiguration . Ele também obtém a lista de formatadores de mídia da coleção HttpConfiguration.Formatters .
Em seguida, o pipeline chama IContentNegotiator.Negotiate, passando:
- O tipo de objeto a ser serializado
- A coleção de formatadores de mídia
- A solicitação HTTP
O método Negotiate retorna duas informações:
- Qual formatador usar
- O tipo de mídia para a resposta
Se nenhum formatador for encontrado, o método Negotiate retornará nulo e o cliente receberá o erro HTTP 406 (Não Aceitável).
O código a seguir mostra como um controlador pode invocar diretamente a negociação de conteúdo:
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
)
};
}
Esse código é equivalente ao que o pipeline faz automaticamente.
Negociador de Conteúdo Padrão
A classe DefaultContentNegotiator fornece a implementação padrão de IContentNegotiator. Ele usa vários critérios para selecionar um formatador.
Primeiro, o formatador deve ser capaz de serializar o tipo. Isso é verificado chamando MediaTypeFormatter.CanWriteType.
Em seguida, o negociador de conteúdo examina cada formatador e avalia o quão bem ele corresponde à solicitação HTTP. Para avaliar a correspondência, o negociador de conteúdo analisa duas coisas no formatador:
- A coleção SupportedMediaTypes , que contém uma lista de tipos de mídia com suporte. O negociador de conteúdo tenta corresponder a essa lista com o cabeçalho de solicitação Aceitar. Observe que o cabeçalho Accept pode incluir intervalos. Por exemplo, "text/plain" é uma correspondência para texto/* ou */*.
- A coleção MediaTypeMappings , que contém uma lista de objetos MediaTypeMapping . A classe MediaTypeMapping fornece uma maneira genérica de corresponder solicitações HTTP com tipos de mídia. Por exemplo, ele pode mapear um cabeçalho HTTP personalizado para um tipo de mídia específico.
Se houver várias correspondências, a correspondência com o fator de maior qualidade vencerá. Por exemplo:
Accept: application/json, application/xml; q=0.9, */*; q=0.1
Neste exemplo, application/json tem um fator de qualidade implícito de 1.0, portanto, ele é preferencial em relação ao aplicativo/xml.
Se nenhuma correspondência for encontrada, o negociador de conteúdo tentará corresponder ao tipo de mídia do corpo da solicitação, se houver. Por exemplo, se a solicitação contiver dados JSON, o negociador de conteúdo procurará um formatador JSON.
Se ainda não houver correspondências, o negociador de conteúdo simplesmente escolherá o primeiro formatador que pode serializar o tipo.
Selecionando uma codificação de caracteres
Depois que um formatador é selecionado, o negociador de conteúdo escolhe a melhor codificação de caracteres examinando a propriedade SupportedEncodings no formatador e correspondendo-a ao cabeçalho Accept-Charset na solicitação (se houver).