Content negotiation

Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core 7

Последнее обновление: 15.03.2017

Когда метод контроллера возвращает ответ, то инфраструктура MVC определяет, в каком формате этот ответ лучше отправить клиенту. Формат контента зависит от ряда факторов: какой формат принимает клиент, какой формат может генерировать MVC, политика форматирования, возвращаемый методом тип.

Иногда метод контроллера возвращает результат в конкретном формате. Например, с помощью метода Content мы можем возвратить объект string, а с помощью метода Json() можно возвратить объект, сериализованый в формат json. Однако контроллеры в Web API для возвращения результат а используют самые различные методы, а не только Json и Content. Например:

[HttpGet]
public IEnumerable<User> Get()
{
    return db.Users.ToList();
}

[HttpGet("{id}")]
public IActionResult Get(int id)
{
    User user = db.Users.FirstOrDefault(x => x.Id == id);
    if (user == null)
        return NotFound();
    return new ObjectResult(user);
}

[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
    User user = db.Users.FirstOrDefault(x => x.Id == id);
    if(user==null)
    {
        return NotFound();
    }
    db.Users.Remove(user);
    db.SaveChanges();
    return Ok(user);
}

Первый метод Get возвращает коллекция элементов IEnumerable<User>. Однако здесь следует правило, если метод возвращает стандартный POCO-объект, то на самом деле неявно возвращается объект ObjectResult, в который оборачивается POCO-объект. То есть в случае с методом Get мы могли бы написать:

[HttpGet]
public IActionResult Get()
{
    return new ObjectResult(db.Users.ToList());
}

Когда же мы передаем в метод, который отправляет определенный статусный код, например, в метод Ok(), то он также возвращает объект класса ObjectResult, точнее один из наследников этого класса. Например, выражение return Ok(user); возвращает объект OkObjectResult.

Использование типа ObjectResult во всех случаях имеет важное значение, так как он реализует такую функциональность, как Content negotiation.

Content negotiation предполагает процесс согласования между сервером и клиентом по поводу типа контента, который отправляется клиенту.

Если метод возвращает ответ в виде строки, то есть объекта string, эта строка отправляется клиенту как есть, а для заголовка Content-Type устанавливается значение text/plain. Данные простейших типов, как int или DateTime, при оправке также форматируются в строку.

А для объектов классов отправляемые данные в ObjectResult по умолчанию форматируются в формат JSON, а для заголовка Content-Type устанавливается значение application/json.

Заголовок Accept

Большинство браузеров включают в запрос заголовок Accept, который указывает набор форматов, предпочтительных для получения ответа. В частности, браузер Google Chrome в запросе отправляет следующий заголовок Accept:

Accept: text/html,application/xhtml + xml,application/xml;q = 0.9,image/webp,*/*;q = 0.8

Этот заголовок указывает, что предпочтительными форматами для браузера являются HTML, XHTML, XML и WEBP (для изображений). Значение q, которое может указываться после каждого формата, обозначает вес данного формата. По умолчанию, если q не указан, то каждый формат имеет вес 1.0.А, к примеру, значение q = 0.9 для формата application/xml указывает, что браузер принимает данные в XML, но предпочитает форматы HTML и XHTML (для которых по умолчанию вес 1.0). Две звездочки в конце */* говорят о том, что браузер может принимать любой формат, но в соответствии с его весом q = 0.8 он будет менее предпочтительным по сравнению с ранее указанными форматами.

То есть данным заголовком Google Chrome говорит, что он предпочитает данные в формате HTML или XHTML, а изображения - в формате WEBP. Если форматы HTML и XHTML недоступны, то можно отправить данные в XML. Если же ни один из этих форматов недоступен для отправки ответа, тогда браузер может принять ответ в любом формате.

Форматирование ответа в xml

Добавим через NuGet в проект пакет Microsoft.AspNetCore.Mvc.Formatters.Xml:

Microsoft.AspNetCore.Mvc.Formatters.Xml

Добавим форматировщик XmlSerializerFormatters к сервисам MVC в классе Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().AddXmlDataContractSerializerFormatters();

    // остальной код метода
}

В качестве альтернативы можно также использовать другой способ:

services.AddMvc(options =>
{
  options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
});

Вне зависимости от того, какой из этих двух способов будет применяться, для сериализации ответа будет использоваться класс System.Runtime.Serialization.DataContractSerializer.

В качестве альтернативы для сериализации данных в xml можно использовать класс System.Xml.Serialization.XmlSerializer:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().AddXmlSerializerFormatters();

	// альтернативный способ
	// services.AddMvc(options =>
	// {
	//		options.OutputFormatters.Add(new XmlSerializerOutputFormatter());
	// });
	
    // остальной код метода
}

В данном случае разница между XmlDataContractSerializerOutputFormatter и XmlSerializerOutputFormatter будет небольшая за тем исключением, что XmlSerializerOutputFormatter на клиентские приложения на .NET, которые используют старые версии фреймворка при работе с XML.

В этом случае, если клиент будет отправлять в запросе, например, к методу:

[HttpGet]
public IEnumerable<User> Get()
{
    return db.Users.ToList();
}

следующий заголовок

Accept: application/xml

То сервер будет отправлять данные в формате xml.

Например, обратимся из Fiddlera к методу Get, отправив в запросе заголовок "Accept: application/xml":

Content Negotiation в ASP.NET Core Web API

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

Атрибут Produces

Мы можем переопределить систему согласования типа контента с помощью атрибута Produces. Этот атрибут выступает своего рода фильтром, который изменяет тип контента для объекта ObjectResult. В качестве значения в этот атрибут передается тип содержимого:

[HttpGet]
[Produces("application/json")]
public IEnumerable<User> Get()
{
    return db.Users.ToList();
}

В данном случае, даже если приложение использует сериализацию ответа в формат XML, а клиент в запросе указывает заголовок "Accept: application/xml", данные все равно будут отправляться в формате json.

Атрибут Produce в ASP.NET Core Web API

Получение типа контента из строки запроса

Заголовок Accept - не единственный способ указать серверу, в каком формате надо отправлять данные клиенту. Еще один способ представляет использование строки запроса. Так, изменим в классе Startup добавление сервисов MVC:

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore;
using HelloWebApi.Models;
using Microsoft.Net.Http.Headers;

namespace HelloWebApi
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            string con = "Server=(localdb)\\mssqllocaldb;Database=usersdbstore;Trusted_Connection=True;MultipleActiveResultSets=true";
            services.AddDbContext<UsersContext>(options => options.UseSqlServer(con));

            services.AddMvc()
                .AddXmlDataContractSerializerFormatters()
                .AddMvcOptions(opts => {
                    opts.FormatterMappings.SetMediaTypeMappingForFormat("xml", new MediaTypeHeaderValue("application/xml"));
             });
        }
        
        public void Configure(IApplicationBuilder app)
        {
            app.UseDefaultFiles();
            app.UseStaticFiles();
            app.UseMvc();
        }
    }
}

В методе AddMvcOptions() устанавливается сопоставление формата из строки запроса с определенным медиа-типом:

opts.FormatterMappings.SetMediaTypeMappingForFormat("xml", new MediaTypeHeaderValue("application/xml"));

Теперь к методу контроллера надо применить атрибут FormatFilter:

[HttpGet]
[FormatFilter]
public IEnumerable<User> Get()
{
    return db.Users.ToList();
}

При запросе мы можем указать формат xml, передав в строке запроса параметр format=xml:

Формат xml в ASP.NET Core Web API

Атрибут FormatFilter может применяться как к отдельным методам, так и ко всему контроллеру.

При этом серверу уже не важно, какой формат контента передается в запросе в заголовке Accept - он все равно не будет учитываться.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850