Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core 7
Когда метод контроллера возвращает ответ, то инфраструктура 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
, который указывает набор форматов, предпочтительных для получения ответа.
В частности, браузер 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. Если же ни один из этих форматов недоступен для отправки ответа, тогда браузер может принять ответ в любом формате.
Добавим через NuGet в проект пакет 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":
Если бы мы не указали заголовок Accept, то данные по прежнему передавались бы в формате json.
Мы можем переопределить систему согласования типа контента с помощью атрибута Produces. Этот атрибут выступает своего рода фильтром, который изменяет тип контента для объекта ObjectResult. В качестве значения в этот атрибут передается тип содержимого:
[HttpGet] [Produces("application/json")] public IEnumerable<User> Get() { return db.Users.ToList(); }
В данном случае, даже если приложение использует сериализацию ответа в формат XML, а клиент в запросе указывает заголовок "Accept: application/xml", данные все равно будут отправляться в формате json.
Заголовок 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
:
Атрибут FormatFilter
может применяться как к отдельным методам, так и ко всему контроллеру.
При этом серверу уже не важно, какой формат контента передается в запросе в заголовке Accept - он все равно не будет учитываться.