Согласование содержимого в веб-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.
  • Язык принятия: Предпочтительный естественный язык, например "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 или "anything" (*/*). Сервер ответил представлением объекта в 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 возвращает два элемента информации:

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

Если модуль форматирования не найден, метод 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 в запросе (если таковой имеется).