Согласование содержимого в веб-API ASP.NET

В этой статье описывается, как веб-API ASP.NET реализует согласование содержимого для ASP.NET 4.x.

Спецификация HTTP (RFC 2616) определяет согласование содержимого как "процесс выбора оптимального представления для заданного ответа при наличии нескольких доступных представлений". Основным механизмом согласования содержимого в HTTP являются следующие заголовки запросов:

  • Принять: Какие типы носителей допустимы для ответа, например application/json, application/xml или настраиваемый тип носителя, например application/vnd.example+xml.
  • Accept-Charset: Какие наборы символов допустимы, например UTF-8 или ISO 8859-1.
  • Accept-Encoding: Какие кодировки содержимого допустимы, например gzip.
  • Accept-Language: Предпочтительный естественный язык, например "en-us".

Сервер также может просматривать другие части HTTP-запроса. Например, если запрос содержит заголовок X-Requested-With, указывающий запрос AJAX, сервер может по умолчанию использовать JSON, если заголовок Accept отсутствует.

В этой статье мы рассмотрим, как веб-API использует заголовки Accept и Accept-Charset. (В настоящее время встроенная поддержка Accept-Encoding или Accept-Language отсутствует.)

Сериализация

Если контроллер веб-API возвращает ресурс в виде типа CLR, конвейер сериализует возвращаемое значение и записывает его в тело HTTP-ответа.

Например, рассмотрим следующее действие контроллера:

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

Клиент может отправить этот HTTP-запрос:

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

В ответ сервер может отправить следующее:

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}

В этом примере клиент запросил json, Javascript или "что-нибудь" (*/*). Сервер ответил представлением Product объекта в формате JSON. Обратите внимание, что заголовок Content-Type в ответе имеет значение application/json.

Контроллер также может возвращать объект HttpResponseMessage . Чтобы указать объект CLR для текста ответа, вызовите метод расширения 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);
}

Этот параметр обеспечивает более полный контроль над подробными сведениями ответа. Вы можете задать код состояния, добавить заголовки HTTP и т. д.

Объект, сериализующий ресурс, называется формататором мультимедиа. Форматировщики мультимедиа, производные от класса MediaTypeFormatter . Веб-API предоставляет средства форматирования мультимедиа для XML и JSON, а также можно создавать пользовательские средства форматирования для поддержки других типов мультимедиа. Сведения о написании настраиваемого средства форматирования см. в разделе "Формататоры мультимедиа".

Как работает согласование содержимого

Во-первых, конвейер получает службу IContentNegotiator из объекта HttpConfiguration . Он также получает список средств форматирования мультимедиа из коллекции HttpConfiguration.Formatters .

Затем конвейер вызывает IContentNegotiator.Negotiate, передавая:

  • Тип объекта для сериализации
  • Коллекция средств форматирования мультимедиа
  • HTTP-запрос

Метод "Согласование " возвращает две части информации:

  • Какой форматировщик следует использовать
  • Тип носителя для ответа

Если средство форматирования не найдено, метод Negotiate возвращает значение NULL, и клиент получает ошибку HTTP 406 (не допускается).

В следующем коде показано, как контроллер может напрямую вызывать согласование содержимого:

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

Этот код эквивалентен тому, что конвейер делает автоматически.

Механизм согласования содержимого по умолчанию

Класс DefaultContentNegotiator предоставляет реализацию IContentNegotiator по умолчанию. В нем используется несколько критериев для выбора модуля форматирования.

Во-первых, модуль форматирования должен иметь возможность сериализовать тип. Это проверяется путем вызова MediaTypeFormatter.CanWriteType.

Затем переговорщик содержимого смотрит на каждый формататор и оценивает, насколько хорошо он соответствует HTTP-запросу. Чтобы оценить совпадение, контентный переговорщик рассматривает два аспекта на форматере.

  • Коллекция SupportedMediaTypes , содержащая список поддерживаемых типов носителей. Согласующий содержимого пытается сопоставить этот список с заголовком Accept запроса. Обратите внимание, что заголовок Accept может включать диапазоны. Например, "text/plain" — это совпадение для text/* или */*.
  • Коллекция MediaTypeMappings , содержащая список объектов MediaTypeMapping . Класс MediaTypeMapping предоставляет универсальный способ сопоставления HTTP-запросов с типами мультимедиа. Например, он может сопоставить пользовательский заголовок HTTP с определенным типом носителя.

При наличии нескольких матчей матч с самым высоким коэффициентом качества выигрывает. Рассмотрим пример.

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

В этом примере application/json имеет подразумеваемый коэффициент качества 1.0, поэтому он предпочтителен для application/xml.

Если совпадения не найдены, переговорщик содержимого пытается сопоставить тип носителя текста запроса, если таковой существует. Например, если запрос содержит данные JSON, переговорщик содержимого ищет формататор JSON.

Если совпадений по-прежнему нет, переговорщик содержимого просто выбирает первый формататор, который может сериализовать тип.

Выбор кодировки символов

После выбора средства форматирования переговорщик контента выбирает лучшую кодировку символов, просматривая SupportedEncodings на форматировщике и сопоставляя её с заголовком Accept-Charset в запросе (если таковой имеется).