Часть изменений, которые сделаны в ASP.NET Core 2.1, касаются возможностей создания WebAPI приложений. Они включается в себя специальные соглашения для контроллеров, улучшенную обработку ввода и ошибок, а так же JSON. Рассмотрим все это по подробнее. |
Атрибут [ApiController]
ASP.NET Core 2.1 добавляет атрибут [ApiController], который упрощает разработку WebAPI контроллеров. Данный атрибут предназначен для класса контроллера и обеспечивает:
- Создание ответа с HTTP кодом 400 при ошибке валидирования модели
- Автоматическое добавление атрибутов для параметров действий контроллера:
- [FromBody] для комплексных типов;
- [FromRoute] где это возможно, исходя из настроек маршрутов;
- [FromQuery] в остальных случаях.
Для корректной работы [ApiController] требуется задание маршрутов с использованием атрибутов.
ActionResult<T>
Новый тип ActionResult<T> позволяет вернуть как обобщенный ответ (например с кодом 404), так и значение заданного типа. Например:
[HttpGet("{id}")]
public ActionResult<User> Get(int id)
{
if (!_repository.TryGetUser(id, out User user))
return NotFound();
return user;
}
В данном примере действие может вернуть экземпляр типа User или ответ 404, если данные не найдены.
Улучшенная обработка запроса
Еще одним нововведением в ASP.NET Core 2.1 стали улучшенные сообщения об ошибках обработки данных запроса. Например, если в запросе данные в формате JSON содержали значение неправильного типа, то раньше клиент получал в ответ следующее:
{
"id": [
"The input was not valid."
]
}
Теперь текст сообщения об ошибке стал более детальным. Для рассориваемого случая он будет выглядеть так:
{
"id": [
"Could not convert string to integer: abc. Path 'id', line 1, position 16."
]
}
Вот еще один пример ответа с пояснением ошибки:
{
"": [
"Unexpected end when reading JSON. Path '', line 1, position 1."
]
}
Валидация параметров запроса
Теперь можно добавлять атрибуты валидации непосредственно в сигнатуре Действия. Например:
public ActionResult Get(string testId, [Required]string name)
Поддержка Problem Details (RFC 7808)
В ASP.NET Core 2.1 появилась поддержка RFC 7808 – Problem Details for HTTP APIs – стандарта, который определяет формат возвращаемого сообщения об ошибках HTTP API. Для использования этой возможности необходимо добавить следующий код в метод ConfigureServices:
services.Configure<ApiBehaviorOptions>(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
var problemDetails = new ValidationProblemDetails(context.ModelState)
{
Instance = context.HttpContext.Request.Path,
Status = StatusCodes.Status400BadRequest,
Type = "https://asp.net/core",
Detail = "Please refer to the errors property for additional details."
};
return new BadRequestObjectResult(problemDetails)
{
ContentTypes = { "application/problem+json", "application/problem+xml" }
};
};
});
Другой вариант – вернуть результат вызова ValidationProblem() из самого действия в контроллере.
В любом случае ответ примет следующий вид:
{
"errors": {
"Text": [
"The Text field is required."
]
},
"type": "https://asp.net/core",
"title": "One or more validation errors occurred.",
"status": 400,
"detail": "Please refer to the errors property for additional details.",
"instance": "/api/values"
}
Улучшения для JSON Patch
JSON Patch определяет структуру JSON документа для поддержки операции HTTP PATCH (частичной модификации данных). Его поддержка уже существует в ASP.NET Core. В версии 2.1 добавлена возможность использовать операцию test для проверки на наличие заданных значений перед выполнением обновления. Например:
[HttpPatch("{id}")]
public ActionResult Patch(int id, JsonPatchDocument<Value> patch)
{
var value = new Value { ID = id, Text = "Do" };
patch.ApplyTo(value, ModelState);
if (!ModelState.IsValid)
return BadRequest(ModelState);
return value;
}
Следующий запрос успешно выполнит обновление данных.
[
{ "op": "test", "path": "/text", "value": "Do" },
{ "op": "add", "path": "/status/1", "value": "Done!" }
]
А обработка запроса вот с такими данными закончится ошибкой, т.к. значение свойства Text у переменной value не совпадает с заданным “Do not”:
[
{ "op": "test", "path": "/text", "value": "Do not" },
{ "op": "add", "path": "/status/1", "value": "Done!" }
]