Содержание
При настройке системы маршрутизации, мы можем задавать шаблоны маршрутов, содержащих параметры и различные ограничения эти параметров. Сегодня более подробно разберемся с шаблонами маршрутов в ASP.NET Core Web API.
Параметры маршрута
Общие сведения о параметрах маршрутов в ASP.NET Core
Параметры маршрута указываются в фигурных скобках. Например, в шаблоне маршрута /api/weather/{day}
сегмент {day}
является параметром маршрута, означающим, что на место {day}
должно подставляться любое значение (строка).
Параметры маршрутов активно используются при разработке различных Web API. Так, например, при проектировании API рекомендуется сосредотачиваться на сущностях с которыми мы работаем. При такой рекомендации маршруты к действиям контроллера могут соответствовать следующим шаблонам (на примере исполнителей и их задач) в зависимости от метода HTTP:
Ресурс API | GET | POST | PUT | DELETE |
/performers |
Получение списка всех исполнителей | Создание нового исполнителя | Массовое обновление всех исполнителей | Массовое удаление всех исполнителей |
/performers/1 |
Получение информации по исполнителю 1 | Ошибка | Обновление информации по исполнителю 1 | Удаление исполнителя 1 |
/performers/1/tasks |
Задачи исполнителя 1 | Добавление новой задачи для исполнителя 1 | Массовое обновление всех задач для исполнителя 1 | Массовое удаление задач для исполнителя 1 |
В данном случае, для составления шаблона маршрутов, включающих в себя идентификаторы пользователей логично использование параметра. Например, мы могли бы использовать следующий шаблон маршрута для получения всех задач конкретного исполнителя:
/performers/{id}/tasks
и, при вызове методов API, подставлять на место параметра конкретные идентификаторы пользователей:
/performers/1/tasks
или /performers/abcde/tasks
При этом, ASP.NET Core позволяет указывать в шаблонах маршрутов несколько параметров, например:
/tasks/{id}/{format}
здесь {id}
и {format}
являются параметрами маршрута на место которых при вызове метода API должны подставляться любые строковые значения. Если несколько параметров указываются в одном сегменте пути, то между ними должен находится разделитель. Например,
/tasks/{id}-{format}
здесь {id}
и {format}
указываются в одном сегменте пути и поэтому между ними используется разделитель — символ -
.
Если параметр является необязательным, то в конце имени указывается символ ?
, например
/tasks/{id}/{format?}
В данном случае, при вызове метода API мы можем не указывать параметр format
так как он является необязательным. Теперь рассмотрим несколько примеров использования параметров маршрутов в ASP.NET Core Web API
Использование параметров маршрутов в ASP.NET Core Web API
Рассмотрим применение параметров маршрутов на примере шаблона приложения ASP.NET Core Web API, но немного переделаем контроллер WeatherForecastController
:
[ApiController] [Route("[controller]")] //первый шаблон маршрута public class WeatherForecastController : ControllerBase { List<WeatherForecast> weathers; 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; weathers = Enumerable.Range(1, 10).Select(index => new WeatherForecast { Id = index, Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] }) .ToList(); } [HttpGet] public IEnumerable<WeatherForecast> Get() { return weathers; } }
Чтобы можно было запрашивать элементы типа WeatherForecast
по их Id
, коллекция этих элементов будет формироваться при создании контроллера:
public WeatherForecastController(ILogger<WeatherForecastController> logger) { _logger = logger; weathers = Enumerable.Range(1, 10).Select(index => new WeatherForecast { Id = index, Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), TemperatureC = Random.Shared.Next(-20, 55), Summary = Summaries[Random.Shared.Next(Summaries.Length)] }) .ToList(); }
а классу WeatherForecast
было добавлено новое свойство:
public class WeatherForecast { public int Id { get; set; } //здесь прочие свойства класса }
Теперь добавим еще один метод Get()
в котроллер:
[HttpGet] public IEnumerable<WeatherForecast> Get() { return weathers; } [HttpGet("{id}")] public WeatherForecast Get(int id) { return weathers[id-1]; }
Get()
не проводятся проверки параметра id
. Будем считать, что пользователь понятливый и будет передавать в параметре только числа от 1 до 10. В реальных приложениях Web API проверками значений параметров пренебрегать не стоит Чтобы получить значения параметра маршрута в качестве параметра метода мы объявили в методе Get()
параметр с тем же именем — id
. Также, мы можем указать для параметра метода атрибут FromRoute
, который указывает, что параметр метода должен быть получен из пути запроса:
[HttpGet("{id}")] public WeatherForecast Get([FromRoute]int id) { return weathers[id-1]; }
В результате, мы получим два метода API:
/WeatherForecast
— возвращает все элементы спискаList<WeatherForecast>
/WeatherForecast/{id}
— возвращает один элемент списка по егоId
Протестируем эти методы API, добавив в http-файл нашего проекта следующие запросы:
@WebApplication3_HostAddress = http://localhost:5225 GET {{WebApplication3_HostAddress}}/weatherforecast/ Accept: application/json ### GET {{WebApplication3_HostAddress}}/weatherforecast/1
Выполнение первого запроса:
Выполнение второго запроса:
Необязательные параметры маршрута
Технически, указав параметр id
необязательным, мы могли бы сделать один метод, например, так:
[HttpGet("{id?}")] public IEnumerable<WeatherForecast> Get([FromRoute]int id = -1) { if (id < 0) return weathers; return weathers.Where(w => w.Id == id); }
Но, при этом, стоит учитывать, что при использовании параметра id
в пути запроса пользователь ожидает получить конкретную сущность (объект типа WeatherForecast
), а получает коллекцию сущностей состоящую из одного элемента. Избежать такого поведения API можно, заменив тип возвращаемого результата на IActionResult
:
[HttpGet("{id?}")] public IActionResult Get([FromRoute]int id = -1) { if (id < 0) return Ok(weathers); return Ok(weathers.Where(w => w.Id == id).First()); }
Ограничения маршрутов
Ограничения маршрутов используются для разрешения неоднозначных маршрутов API. Чтобы понять в чём может заключаться неоднозначность маршрута, добавим в наш контроллер ещё одно действие:
//новое действие контроллера [HttpGet("{summaries}")] public IActionResult GetBySummaties(string summaries) { return Ok(weathers.Where(w => w.Summary.Equals(summaries)).First()); } [HttpGet("{id}")] public WeatherForecast Get(int id) { return weathers[id - 1]; }
Этот код не вызовет никаких ошибок ни при сборке проекта, ни при его запуске. Для нас тоже, вроде бы, всё логично — один метод принимает в качестве параметра строку, второй — целое число. Однако для ASP.NET Core эти два маршрута одинаковые так как на место обоих параметров мы можем подставлять любые значения. Попробуем выполнить любой из этих методов и получим ошибку следующего содержания:
Для разрешения такой неоднозначности и применяются ограничения маршрутов. Ограничение указывается через символ :
после параметра. Так, чтобы однозначно указать ASP.NET Core какой тип значения мы ожидаем получить в параметре маршрута мы можем изменить наши методы следующим образом:
[HttpGet("{summaries:alpha}")] public IActionResult GetBySummaties(string summaries) { return Ok(weathers.Where(w => w.Summary.Equals(summaries)).First()); } [HttpGet("{id:int}")] public WeatherForecast Get(int id) { return weathers[id - 1]; }
Так, для параметра summaries
мы указали ограничение alpha
, означающее, что значением параметра будет одна или несколько букв без учёта регистра. Для параметра id мы указали ограничение int
— только целые числа. В результате мы разрешили неоднозначность двух маршрутов и теперь об метода API будут работать без ошибок.
ASP.NET Core предоставляет достаточно обширный круг возможных ограничений маршрута, каждое из которых в ASP.NET Core соответствует определенному классу:
Ограничение | Пример | Примеры совпадений | Примечания. | Класс |
---|---|---|---|---|
int |
{id:int} |
123456789, -123456789 | Соответствует любому целому числу | IntRouteConstraint |
bool |
{active:bool} |
true, FALSE | Соответствует true или false . Без учета регистра |
BoolRouteConstraint |
datetime |
{dob:datetime} |
2016-12-31, 2016-12-31 7:32pm | Соответствует допустимому значению DateTime для инвариантного языка и региональных параметров. |
DateTimeRouteConstraint |
decimal |
{price:decimal} |
49.99, -1,000.01 | Соответствует допустимому значению decimal для инвариантного языка и региональных параметров. |
DecimalRouteConstraint |
double |
{weight:double} |
1.234, -1,001.01e8 | Соответствует допустимому значению double для инвариантного языка и региональных параметров. |
DoubleRouteConstraint |
float |
{weight:float} |
1.234, -1,001.01e8 | Соответствует допустимому значению float для инвариантного языка и региональных параметров. |
FloatRouteConstraint |
guid |
{id:guid} |
CD2C1638-1638-72D5-1638-DEADBEEF1638 | Соответствует допустимому значению Guid |
GuidRouteConstraint |
long |
{ticks:long} |
123456789, -123456789 | Соответствует допустимому значению long |
Long |
minlength(value) |
{username:minlength(4)} |
Rick | Строка должна содержать не менее 4 символов | MinLengthRouteConstraint |
maxlength(value) |
{filename:maxlength(8)} |
MyFile | Строка должна содержать не более 8 символов | MaxLengthRouteConstraint |
length(length) |
{filename:length(12)} |
somefile.txt | Длина строки должна составлять ровно 12 символов | LengthRouteConstraint |
length(min,max) |
{filename:length(8,16)} |
somefile.txt | Строка должна содержать от 8 до 16 символов | LengthRouteConstraint |
min(value) |
{age:min(18)} |
19 | Целочисленное значение не меньше 18 | MinRouteConstraint |
max(value) |
{age:max(120)} |
91 | Целочисленное значение не больше 120 | MaxRouteConstraint |
range(min,max) |
{age:range(18,120)} |
91 | Целочисленное значение от 18 до 120 | RangeRouteConstraint |
alpha |
{name:alpha} |
John | Строка должна состоять из одной буквы или нескольких (a —z ) без учета регистра. |
AlphaRouteConstraint |
regex(expression) |
{ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} |
123-45-6789 | Строка должна соответствовать регулярному выражению. См. советы по определению регулярного выражения. | RegexRouteConstraint |
required |
{name:required} |
John | Определяет обязательное наличие значения, не относящегося к параметру, во время формирования URL-адреса | RequiredRouteConstraint |
К одному и тому же параметру можно применять несколько ограничений. Для этого, все ограничения указываются через символ :
после параметра, например:
[HttpGet("{id:int:min(1):max(10)}")]
Здесь мы применили к параметру сразу три ограничения — по типу (int
), минимальное значение (min
) и максимальное значение (max
).
Следует также обратить внимание на рекомендации Microsoft относительно применения ограничений, а именно: не используйте ограничения для проверки входных данных. Эта рекомендация продиктована следующим моментом: при недопустимых значениях параметров сервер должен возвращать клиенту ошибку 400 BadRequest
в то время как использование ограничения для проверки входных данных вернет ошибку 404 NotFound
.
Проверить это достаточно просто, используя наш пример. Используем следующие ограничения параметр id
:
[HttpGet("{id:int:min(1):max(10)}")] public WeatherForecast Get(int id) { return weathers[id - 1]; }
и выполним запрос с id=11
непосредственно в браузере без использования интерфейса Swagger. В результате получим:Поэтому ограничения
min
и max
стоит убрать и каким-либо образом обработать возможный неверный ввод данных. Например, один из возможных вариантов обработки ввода неверных данных может выглядеть так:
[HttpGet("{id:int}")] public IActionResult Get(int id) { if ((id < 1) || (id > 10)) return BadRequest(); return Ok(weathers[id - 1]); }
Здесь мы используем вместо конкретного типа возвращаемого результата тип IActionResult
, проверяем значение id
и либо возвращаем результат, либо ошибку 400. В случае ошибки клиент API получит следующий объект:что соответствует рекомендации — клиенту возвращается ошибка с кодом 400 «Bad request».
Итого
При разработке системы маршрутизации в проекте мы можем использовать в шаблонах маршрутов параметры, которые включаются в шаблон маршрута в фигурных скобках. Для разрешения неоднозначных маршрутов в ASP.NET Core применяются ограничения маршрутов, которые указываются через двоеточие после имени параметра.