Andriy Shyrokoryadov

.Net developer, data scientist

№9 Маршрутизация в Asp.Net Core [#60].

Текст к видео "Маршрутизация в Asp.Net Core" на канале YouTube

Привет всем! Сегодня мы обсудим тему маршрутизации, или как некоторые могут сказать, тему роутинга в приложениях ASP.NET Core. Мне кажется, что это видео будет интересным, с большим числом примеров кода. Предыдущие темы на канале были немного абстрактные, так как программисты не задумываются о вопросах среды приложения или сервера приложения в повседневной работе. Конечно, знать эти темы нужно, особенно перед собеседованием, а на практике этим вопросам уделяется меньшее внимания. Тема маршрутизации с этой точки зрения является более интересной темой. Почему? Потому что в своей повседневной работе вы будете довольно часто добавлять контроллеры и действия в контроллерах попутно задумываясь над тем, как привязать конкретное действие к контролеру к определенному адресу. Но это не единственный вопрос, который у вас может возникнуть. Вы узнаете через несколько минут что добавить действие и привязать его к определенному адресу / пути в строке браузера это тривиальное задание. А как можно передать какие-то параметры вместе с запросом? На этот вопрос вы также получите ответ. Я понимаю, что меня могут смотреть также и опытные программисты, поэтому я сразу предупрежу, что сегодня мы будем рассматривать некоторые базовые понятия, например рассмотрим вкратце некоторые методы HTTP запросов, такие как GET, POST, PUT, DELETE. Сегодня в примерах будет много атрибутов, что также должно быть интересно моим зрителям.

Если речь идет о маршрутизации, то это определенная логика, которая определяет на основании адреса, под который был выслан запрос, а также на основании метода запроса (о них мы поговорим позже), каким контроллером и каким действием должен быть обслужен данный запрос. То есть перейти от адреса запроса к конкретному коду в нашем приложении. Логика работы маршрутизации в приложениях ASP.NET Core довольно сложная и вряд ли удастся объяснить все нюансы в течении 20 минутного видео. Сегодня мы познакомимся как пользоваться данной логикой и взаимодействовать с ней, в основном при помощи атрибутов.

Если вас спросят на собеседовании какие виды маршрутизации в приложении ASP.NET Core вы знаете, то можно смело ответить после просмотра данного видео, что вы знаете 2 метода маршрутизации: первый метод – это так называемый обычный метод, на основании маппинга маршрутов контроллеров, и второй метод на основании атрибутов. Сложно сказать, как из этих методов лучше или хуже. Результат работы этих методов одинаков, однако способ настройки маршрутизации отличается. Скорей всего это дело привычки. Я заметил, что если программист хочет сделать что-то быстро, то он использует маршрутизацию на основании атрибутов. Возможно, обычная маршрутизации требует больше времени для понимания как она работает, однако в ней также ничего сложно нет. В любом случае сегодня будут представлены оба метода маршрутизации.

Если речь идет о практическом примере, то я подготовил небольшое приложение с двумя контроллерами. Каждый из контроллеров имеет маршрутизацию настроенную либо на основании атрибутов, либо на основании декларирования маршрутов. К сожалению или, к счастью, нельзя использовать одновременно 2 вида маршрутизации, однако, с другой стороны, я могу представить себе ситуацию, когда контроллер настроен для использования обоих типов маршрутизации, а какой конкретно тип будет использоваться определяется при старте приложения.

Давайте посмотри на контроллер ValuesController. Сразу бросается в глаза что каждое действие контроллера имеет атрибут с названием метода запроса. Конкретно у нас есть 4 метода: GET, POST, PUT, DELETE. Каждый из методов имеет своё семантическое значение. Если мы вспомним шаблон проектирования «Репозиторий», то можно сказать, что каждый из этих методов, который передаётся вместе с запросом, соответствует операциям «Получить», «Добавить», «Обновить», «Удалить», то есть:

  • метод GET – операция «Получить», метод Get в шаблоне «Репозиторий»;
  • метод POST – операция «Добавить», метод Add в шаблоне «Репозиторий»;
  • метод PUT – операция «Обновить», метод Update в шаблоне «Репозиторий»;
  • метод DELETE – операция «Удалить», метод Remove в шаблоне «Репозиторий»;

Каждый запрос с одним из этих методов в случае безошибочного выполнения может вернуть статус 200, то есть «ОК». Однако, более профессионально будет разделение успешных статусов на отдельные статусы в зависимости от метода запроса. Что имеется в виду:

  • метод GET – здесь мы получаем некоторый объект и статус кода может остаться без изменений, то есть 200 «ОК»;
  • метод POST – добавляем объект, в такой ситуации более подходит код 201 «Created», т. е. «Создано»;
  • метод PUT – актуализация объекта, если она прошла успешно, то можно вернуть код 202 «Accepted», т. е. «Принято»;
  • метод DELETE – операция удаления объекта может возвратить код 204 «No Content», т. е. «Нет содержимого».

Это не ошибка, если все операции будут возвращать код статуса 200. И это также не правило, что, например все запросы POST должны возвращать статус 201. Надо смотреть на каждую ситуации в отдельности и выбирать соответствующий код, который будет наиболее подходящим в данном контексте. Например, с кодами ошибок клиента ситуация другая – всегда возвращается код, который наиболее точно характеризует ошибку. Вот несколько примеров:

  • 400 Bad Request – «Плохой запрос» - в данных вашего запроса есть ошибка или они неполные, возможно запрос отбрасывается сервером при обработке в одном из связующих программных компонентов. Как правило вместе с данным кодом возвращается сообщение об ошибке, которое содержит информацию о том, почему ваш запрос не был обслужен.
  • 401 Unauthorized – «Не авторизирован» - для выполнения данного запроса необходимо предоставить данные для авторизации, например логин и пароль.
  • 403 Forbidden – «Запрещено» - вы авторизированы, но у вас нет прав, чтобы выполнить данный запрос.
  • 404 NotFound – «Не найдено» - адрес, на который вы отправили запрос, не существует. Это наверно самый известный код статуса с ошибкой.
  • 405 MethodNotAllowed – «Метод не разрешен» - вы отправили запрос с методом HTTP (например, с GET) под адрес, который ожидает другой метод (например действие контроллера обслуживает только POST)
  • 415 UnsupportedMediaType – «Неподдерживаемый тип медиа данных» — это означает что формат данных которые вы отправили вместе с запросом на сервер не обслуживается сервером. Чтобы исправить ошибку необходимо проверить какой формат ожидает сервер и указать этот формат с помощью заголовка запрос «Content-Type»

Возвращаясь к нашему коду, каждый из этих атрибутов определяет с каким методом может использоваться данное действие контроллера. По умолчанию название действия определяется по названию метода контроллера. Это не всегда удобно, так как названия методов могут быть длинными и будет неудобно их вписывать в адрес запроса. Используя атрибут ActionName с аргументом типа string, который определяет, как будет называться действие, связанное с данным методом.

Теперь вернемся к классу StartUp. В этом классе задекларирован метод ConfigureRoute. В этом методе определены все адреса, которые используются нашим приложением и как эти адреса связаны с контроллерами и действиями. Сразу стоит оговорится, что адреса могут декларироваться как константы. То есть адресу соответствует определенный контроллер и действие. Например, давайте посмотрим на декларацию маршрута, которая называется «UserExample1». Здесь контроллеру Values и действию «UserFromBody» будет соответствовать один единственный адрес: адрес сервера + строка «UserJson».

Также адрес может быть задекларирован так, что шаблон адреса будет подходить большому количеству адресов. Давайте посмотрим на первую декларацию: если мы укажем только адрес сервера без указания контроллера и действия, то получим контроллер и действие по умолчанию, то есть „Values” и „Index”. А если укажем только контроллер любой, то будет попытка вызова действия „Index” – если такое действие есть, будет выполнена его логика. Если такого действия нет, то будет возвращена ошибка «404 - Страница не найдена». Если же кроме адреса сервера мы укажем и название контроллера, и название действия, то будет попытка вызова этого контроллера с указанным действием. Результатом этой операции будет либо выполнение логики, либо ошибка 404.

Все декларации в методе ConfigureRoute, кроме первой, относятся к конкретным адресам для конкретных методов и действий. Так иногда бывает, что в строке запрос, в адресе, мы хотели бы передать определенный параметр. Это возможно. Более того мы можем указать параметр какого типа мы ожидаем. Обратите внимание в некоторых паттернах деклараций после двоеточия указан конкретный тип – это и есть ограничение на тип передаваемого параметра. Также возле некоторых параметров можно увидеть знак вопроса. Он означает, что данный параметр необязательный.

Вернемся в контроллер ValuesController. Обратите внимание, что некоторые действия принимают аргумент сложного типа User и возле каждого аргумента есть атрибут. Таких атрибутов 3: FromBody, FromQuery, FromForm. Они определяют как этот сложный тип будет передан в запросе в наш контроллер. Более подробно мы рассмотрим это на конкретном примере.

Также часто бывают ситуации, когда в контроллер необходимо передать файл вместе с запросом. Чтобы это сделать необходимо задекларировать действие, которое принимает аргумент типа IFormFile. Как это работает мы узнаем после запуска приложения.

Если речь идёт о маршрутизации на основании атрибутов, то давайте посмотрим на контроллер ValuesTwoController, который полностью повторяет функциональность контроллера ValuesController. Однако, маршруты, которые определены в данном контроллере немного отличаются, чтобы было понятно, что мы работает с другим контроллером, который не связан с первым. В этом контроллере также, как и в предыдущем, задекларированы атрибуты методов запросов. Наблюдательные зрители заметят, что некоторые из атрибутов принимают аргумент типа string, например действие Index. Это первый способ декларации маршрута при помощи атрибутов. Действие Index будет доступно по адресу ValuesTwo/Primary. Способ декларирования маршрутов при помощи атрибутов несколько. Рассмотрим их на примерах:

  • декларирование в атрибутах методов – уже нам знакомо.
  • атрибут Route – в качестве аргумента принимает маршрут, по которому будет доступно данное действие. Например, действие Get будет доступно по адресу ValuesTwo/Retrieve. Стоит отметить, что аргументом атрибута Route может быть любая строка с любым количеством слэшей. Например, как это сделано в перегрузках метода Index. Естественно, в маршруте можно указать параметры с ограничениями, так же как это было сделано в случае первого контроллера.
  • атрибут Route с аргументом константой. Удобно выписать все маршруты в виде констант в отдельном классе и использовать их в различных местах нашего приложения.
  • декларирование без маршрутов – в данном случае приложение ASP.NET Core будет ориентироваться на название контроллера и метод запроса. Как это работает мы рассмотрим на примере дальше.

Мне кажется, сегодня мне удалось показать большее количество примером как можно реализовать маршрутизацию в приложении ASP.NET Core, а кроме того, показать реальный практический пример использования атрибутов. Пора заканчивать видео. Напоминаю, что в описании к данному видео вы найдете ссылку на репозиторий с кодом, который был использован в примере, ссылку на текст к данному видео и ссылку на коллекции запросов Postman для обычного роутинга и роутинга по атрибутам. Спасибо за внимание. Если видео было вам хоть чуточку полезным, буду благодарен за лайк, комментарий и подписку, если вы не подписаны. До новых встреч.

Пример кода из видео на GitHub

Для открытия файла проекта необходимо Visual Studio 2019.