Condividi tramite


Negoziazione del contenuto nell'API Web di ASP.NET

Questo articolo descrive come ASP.NET'API Web implementa la negoziazione del contenuto per ASP.NET 4.x.

La specifica HTTP (RFC 2616) definisce la negoziazione del contenuto come "il processo di selezione della migliore rappresentazione per una determinata risposta quando sono disponibili più rappresentazioni". Il meccanismo principale per la negoziazione del contenuto in HTTP sono le intestazioni di richiesta seguenti:

  • Accettare: Quali tipi di supporti sono accettabili per la risposta, ad esempio "application/json", "application/xml" o un tipo di supporto personalizzato, ad esempio "application/vnd.example+xml"
  • Accept-Charset: Quali set di caratteri sono accettabili, ad esempio UTF-8 o ISO 8859-1.
  • Accept-Encoding: Quali codifiche di contenuto sono accettabili, ad esempio gzip.
  • Accept-Language: Linguaggio naturale preferito, ad esempio "en-us".

Il server può anche esaminare altre parti della richiesta HTTP. Ad esempio, se la richiesta contiene un'intestazione X-Requested-With, che indica una richiesta AJAX, il server potrebbe impostare come predefinito JSON se non è presente alcuna intestazione Accept.

In questo articolo verrà illustrato come l'API Web usa le intestazioni Accept e Accept-Charset. Attualmente non è disponibile alcun supporto predefinito per Accept-Encoding o Accept-Language.

Serializzazione

Se un controller API Web restituisce una risorsa come tipo CLR, la pipeline serializza il valore restituito e lo scrive nel corpo della risposta HTTP.

Si consideri ad esempio l'azione controller seguente:

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 potrebbe inviare questa richiesta HTTP:

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

In risposta, il server potrebbe inviare:

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 questo esempio il client ha richiesto JSON, Javascript o "anything" (*/*). Il server ha risposto con una rappresentazione JSON dell'oggetto Product . Si noti che l'intestazione Content-Type nella risposta è impostata su "application/json".

Un controller può anche restituire un oggetto HttpResponseMessage . Per specificare un oggetto CLR per il corpo della risposta, chiamare il metodo di estensione 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);
}

Questa opzione offre un maggiore controllo sui dettagli della risposta. È possibile impostare il codice di stato, aggiungere intestazioni HTTP e così via.

L'oggetto che serializza la risorsa è denominato formattatore multimediale. I formattatori multimediali derivano dalla classe MediaTypeFormatter . L'API Web fornisce formattatori multimediali per XML e JSON ed è possibile creare formattatori personalizzati per supportare altri tipi di supporti. Per informazioni sulla scrittura di un formattatore personalizzato, vedere Formattatori multimediali.

Funzionamento della negoziazione del contenuto

Prima di tutto, la pipeline ottiene il servizio IContentNegotiator dall'oggetto HttpConfiguration . Ottiene anche l'elenco dei formattatori multimediali dall'insieme HttpConfiguration.Formatters .

Successivamente, la pipeline chiama IContentNegotiator.Negotiate, passando:

  • Tipo di oggetto da serializzare
  • Raccolta di formattatori multimediali
  • Richiesta HTTP

Il metodo Negotiate restituisce due informazioni:

  • Quale formattatore usare
  • Tipo di media per la risposta

Se non viene trovato alcun formattatore, il metodo Negotiate restituisce null e il client riceve l'errore HTTP 406 (Non accettabile).

Il codice seguente illustra come un controller può richiamare direttamente la negoziazione del contenuto:

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

Questo codice equivale a ciò che la pipeline esegue automaticamente.

Negoziazione di contenuti predefinito

La classe DefaultContentNegotiator fornisce l'implementazione predefinita di IContentNegotiator. Usa diversi criteri per selezionare un formattatore.

Innanzitutto, il formattatore deve poter serializzare il tipo. Questa operazione viene verificata chiamando MediaTypeFormatter.CanWriteType.

Successivamente, il negoziatore di contenuto esamina ogni formattatore e valuta il livello di corrispondenza con la richiesta HTTP. Per valutare la corrispondenza, il negoziatore di contenuto esamina due elementi nel formattatore:

  • Raccolta SupportedMediaTypes , che contiene un elenco di tipi di supporti supportati. Il negoziatore dei contenuti tenta di confrontare questo elenco con l'intestazione 'Accept' della richiesta. Si noti che l'intestazione Accept può includere intervalli. Ad esempio, "text/plain" è una corrispondenza per text/* o */*.
  • La raccolta MediaTypeMappings, che contiene un elenco di oggetti MediaTypeMapping. La classe MediaTypeMapping offre un modo generico per trovare una corrispondenza con le richieste HTTP con i tipi di supporti. Ad esempio, potrebbe eseguire il mapping di un'intestazione HTTP personalizzata a un particolare tipo di media.

Se ci sono più corrispondenze, vince quella con il fattore di qualità più alto. Per esempio:

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

In questo esempio application/json ha un fattore di qualità implicito pari a 1.0, quindi è preferibile rispetto a application/xml.

Se non vengono trovate corrispondenze, il negoziatore di contenuto tenta di abbinare il tipo di media del contenuto della richiesta, se presente. Ad esempio, se la richiesta contiene dati JSON, il negoziatore di contenuto cerca un formattatore JSON.

Se ancora non ci sono corrispondenze, il negoziatore di contenuti seleziona semplicemente il primo formattatore in grado di serializzare il tipo.

Selezione di una codifica di caratteri

Dopo aver selezionato un formattatore, il negoziatore di contenuto sceglie la codifica dei caratteri migliore esaminando la proprietà SupportedEncodings nel formattatore e associandola all'intestazione Accept-Charset nella richiesta (se presente).