Содержание
На сегодняшний день, практически форматом по умолчанию для отправки ответа клиенту сервером Web API считается JSON. Это действительно довольно популярный, лаконичный и простой формат обмена данными, который используется практически повсеместно. Когда мы создаем приложение ASP.NET Core Web API, то, по умолчанию, сервер также пытается сериализовать ответ в JSON. При этом, ASP.NET Core позволяет настраивать формат данных ответа и использовать, в том числе, для обмена данными XML. В этой части мы рассмотрим некоторые вопросы форматирования данных ответа в приложениях ASP.NET Core Web API.
Согласование содержимого в приложениях ASP.NET Core Web API
ASP.NET Core, по умолчанию, поддерживает отправку ответов клиенту со следующими mime-типами (в порядке приоритета от наиболее предпочтительного до наименее предпочтительного):
application/jsontext/jsontext/plain
Согласование содержимого начинается в том случае, если клиент отправляет в запросе заголовок Accept. В этом случае, приложение ASP.NET Core будет действовать следующим образом:
- пытается найти форматировщик, который сможет создать ответ в необходимом формате.
- если требуемый форматировщик не найден, то
- пытается сформировать ответ, используя первый форматировщик
- возвращает код 406, если сервер API настроен на отправку такого ответа.
Чтобы продемонстрировать такое поведение приложения, нам потребуется сервис тестирования Web API, поддерживающий отправку заголовков на сервер. Например, воспользуемся сервисом https://reqbin.com/. Создадим новое приложение ASP.NET Core Web API на основе контроллеров, запустим его, перейдем в ReqBin и создадим такой запрос:
Так как наше приложение пока не поддерживает формат XML, а возврат кода 406 сервером не настроен, то сервер попытается отправить ответ, используя для сериализации JSON. То есть ответ сервера будет таким:
при этом, в заголовках ответа будет следующий заголовок:
content-type: application/json; charset=utf-8
Попытаемся реализовать вариант, при котором сервер ответит кодом 406, если в заголовке Accept не будет содержаться поддерживаемый формат. Для этого, перейдем в файл Program.cs и добавим настройку:
builder.Services.AddControllers(
options=>options.ReturnHttpNotAcceptable = true //сервер вернет код 406, если требуемый формат не поддерживается
);
Теперь попробуем снова выполнить точно такой же запрос, как выше и получим ответ сервера:
HTTP/1.1 406 content-length: 0 server: Kestrel
Теперь добавим в наше приложение поддержку формата XML.
Добавление поддержки XML
В файле Program.cs добавим следующую строку:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(options=>options.ReturnHttpNotAcceptable = true)
.AddXmlSerializerFormatters();//добавление поддержки формата XML
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Теперь убедимся, что приложение может отправить нам ответ в формате XML. Перейдем снова в ReqBin и повторно отправим запрос с заголовком Accept: application/xml. В результате, мы получим ответ в формате XML:
<ArrayOfWeatherForecast xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<WeatherForecast>
<Date />
<TemperatureC>27</TemperatureC>
<Summary>Chilly</Summary>
</WeatherForecast>
<WeatherForecast>
<Date />
<TemperatureC>13</TemperatureC>
<Summary>Warm</Summary>
</WeatherForecast>
<WeatherForecast>
<Date />
<TemperatureC>19</TemperatureC>
<Summary>Hot</Summary>
</WeatherForecast>
<WeatherForecast>
<Date />
<TemperatureC>27</TemperatureC>
<Summary>Chilly</Summary>
</WeatherForecast>
<WeatherForecast>
<Date />
<TemperatureC>-7</TemperatureC>
<Summary>Hot</Summary>
</WeatherForecast>
</ArrayOfWeatherForecast>
Можно попытаться изменить заголовок Accept на какой-нибудь другой, например, на text/html и убедиться, что сервер, согласно нашим настройкам ответит кодом 406.
Согласование содержимого при работе в браузере
ASP.NET Core действует иначе, если запрос на сервер приходит непосредственно из браузера. Так как браузеры отправляют заголовок Accept, содержащий множество различных mime-типов, то, по умолчанию, ASP.NET Core игнорирует заголовок Accept и возвращает данные в формате JSON. Убедиться в этом можно, отправив запрос, например, из браузера Edge. Результат ответа и данные запроса показаны на рисунке ниже:
Как можно видеть, наше приложение могло бы отправить данные в формате XML, однако, так как запрос пришел из браузера, то заголовок Accept был проигнорирован и результат вернулся в формате JSON. Чтобы приложение не игнорировало заголовок Accept от браузера, необходимо добавить следующую настройку:
builder.Services.AddControllers(
options =>
{
options.ReturnHttpNotAcceptable = true;
options.RespectBrowserAcceptHeader = true; //не игнорировать заголовок Accept от браузера
}
).AddXmlSerializerFormatters();
Теперь запустим приложение и повторим запрос из браузера:
Получение формата ответа из URI
Клиенты могут запрашивать определенный формат данных, используя URI запроса. Так, формат может быть запрошен в части пути или в строке запроса. Для этого нам необходимо применить к контроллеру атрибут фильтра [FormatFilter] и указать в маршруте параметр, например, format, который может быть необязательным:
[ApiController]
[Route("[controller]")]
[FormatFilter] //атрибут для фильтра Format
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet("{format?}")] //указываем в маршруте необязательный формат
public IActionResult Get()
{
return Ok( Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray());
}
}
Теперь запустим приложение и проверим результат. Вначале, запросим ответ в формате JSON, используя строку формата в качестве части пути, то есть вот так:
localhost:7136/WeatherForecast/json
тот же самый ответ мы получим, если включим данные о формате в строку запроса: localhost:7136/WeatherForecast?format=json
Аналогичным образом можно запросить и формат XML:
Принудительное указание формата ответа
Если требуется, чтобы определенное действие контроллера или весь контроллер целиком формировал ответ в определенном формате, то можно воспользоваться атрибутом [Produces]. Например, укажем, что наш контроллер должен формировать ответ только в формате JSON:
[ApiController]
[Route("[controller]")]
[Produces("application/json")] //данные возвращаются только в JSON
public class WeatherForecastController : ControllerBase
{
//тут код контроллера
}
Теперь все действия в контроллере будут формировать ответ только в формате JSON, игнорируя заголовок Accept.
Настройка форматировщика
В ASP.NET Core можно настроить вывод ответа. Например, изменим настройки форматировщика JSON, используемого по умолчанию. Для этого необходимо вызвать следующий метод расширения AddJsonOptions()и указать необходимые настройки форматирования:
builder.Services.AddControllers(
options =>
{
options.ReturnHttpNotAcceptable = true;
options.RespectBrowserAcceptHeader = true; //не игнорировать заголовок Accept от браузера
})
.AddXmlSerializerFormatters()
.AddJsonOptions(config => //настройка форматировщика JSON
{
config.JsonSerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase,
});
Теперь вывод ответа в формате JSON будет выглядеть следующим образом:
Итого
По умолчанию, ASP.NET Core поддерживает формирование ответа клиенту, используя три mime-типа: application/json, text/json и text/plain. При необходимости, мы можем включать поддержку других форматов данных, например, XML и управлять согласованием содержимого с клиентом, например, отправлять клиенту ответ с кодом 406, если требуемый формат не поддерживается, принимать заголовок Accept от браузера или же принудительно указывать формат данных в котором будут отправляться ответы клиенту от всего контроллера или отдельных его действий.





