# Маршрутизация ## Добавление маршрутизации Чтобы связать контроллеры MVC и их действия с запросами применяется система маршрутизации. Чтобы задействовать систему маршрутизации для контроллеров MVC, можно использовать различные способы. ```cs app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); ``` Параметр _name_ определяет название маршрута - оно произвольно и в данное случае имеет значение "default". А параметр _pattern_ определяет шаблон маршрута, с которым будут сопоставляться входящие маршруты. Шаблон маршрута использует три параметра. Параметр "controller" будет сопоставляться по имени с одним из контроллеров приложения, а параметр "action" - с действием этого контроллера. Например, при запросе `http://localhost:3456/Home/Index` система выберет для обработки запроса контроллер __Home__ - имя контроллера без префикса Controller и его действие __Index__. Данный тип маршрутизации еще называют __Convention-Based Routing__, то есть маршрутизация, основанная на условностях в определениях маршрутов. ### Добавление маршрутов Для добавления маршрутов в MVC мы можем применять следующие методы: - __MapControllerRoute()__ определяет произвольный маршрут и принимает следующие параметры: ``` MapControllerRoute( string name, string pattern, [object defaults = null], [object constraints = null], [object dataTokens = null]) ``` Его параметры: - _name_: название машрута - _pattern_: шаблон машрута - _defaults_: значения параметров маршрутов по умолчанию - _constraints_: ограничения маршрута - _dataTokens_: определения токенов маршрута Первые два параметра являются обязательными, остальные необязательные. - __MapDefaultControllerRoute()__ определяет стандартный маршрут, фактически эквивалентен вызову ```cs app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); ``` Поскольку это довольно часто используемое определение маршрута, то для него и был введен отдельный метод. - __MapAreaControllerRoute()__ определяет маршрут, который также учитывает область приложения. Имеет следующие параметры: ``` MapAreaControllerRoute( string name, string areaName, string pattern, [object defaults = null], [object constraints = null], [object dataTokens = null]) ``` Обязательный параметр _areaName_ позволяет определить область, с которой будет связан маршрут. - __MapControllers()__ сопоставляет действия контроллера с запросами, используя маршрутизацию на основе атрибутов. Про атрибуты маршрутизации будет сказано в последующих статьях. - __MapFallbackToController()__ определяет действие контроллера, которое будет обрабатывать запрос, если все остальые определенные маршруты не соответствуют запросу. Принимает имя контроллера и его метода: ``` MapFallbackToController(string action, string controller) ``` Определение шаблонов маршрутов, использование ограничений, необязательных параметров и параметров по умолчанию в MVC будет происходить также, как и уже было рассмотрено на примере ASP.NET Core в целом. Единственное существенное отличие состоит в том, что MVC добавляет поддержку атрибутов маршрутизации, которые мы далее рассмотрим. ### Получение параметров маршрутов в контроллере Мы можем получить в контроллере все параметры маршрута, используя объект __RouteData__: ```cs public class HomeController : Controller { public string Index() { var controller = RouteData.Values["controller"]; var action = RouteData.Values["action"]; return $"controller: {controller} | action: {action}"; } } ``` ### Дополнительные параметры маршрута Шаблон маршрута может содержать дополнительные параметры, которые можно получить через параметры метода. Например, определим следующие маршруты: ```cs // устанавливаем сопоставление маршрутов с контроллерами app.MapControllerRoute(name: "default", pattern: "{controller}/{action}/{id}"); app.MapControllerRoute(name: "name_age", pattern: "{controller}/{action}/{name}/{age}"); ``` Здесь первый маршрут - "default" принимает третий параметр - _id_, который располагается в третьем сегменте строки запроса. Второй маршрут - "name_age" принимает дополнительно два параметра - _name_ и _age_, который располагаются соответственно в третьем сегменте и четвертом сегментах строки запроса. Для тестирования маршрутов определим следующий контроллер: ```cs public class HomeController : Controller { public string Index(int id) => $"Index Page. Id: {id}"; public string About(string name, int age) => $"About Page. Name: {name} Age: {age}"; } ``` И в данном случае, если к приложению придет запрос типа `Home/Index/6`, система маршрутизации сможет сопоставить этот запрос с первым маршрутом - маршрутом "default", который также содержит из трех сегментов, и значение последнего сегмента - число `6` будет передано в метод _Index_ через параметр _id_ Если же к приложению придет запрос из четырех сегментов типа `Home/About/Tom/37`, то система маршрутизации сможет сопоставить этот запрос со вторым маршрутом - маршрутом "name_age". Тогда значение третьего сегмента будет передано в метод через параметр _name_, а значение четвертого сегмента - через параметр _age_: ### Статические сегменты Шаблоны маршрутов могут иметь статические сегменты, которые не связывются с параметрами маршрутов. Например, определим следующий маршрут: ```cs // устанавливаем сопоставление маршрутов с контроллерами app.MapControllerRoute( name: "default", pattern: "api/{controller}/{action}/{id}"); ``` В данном случае шаблон маршрута начинается со статического сегмента `api/`. Таким образом, этому маршруту будут соответствовать все маршруты, которые состоят из четырех сегментов, где первый сегмент равен `/api`, например, запрос `https://localhost:7288/api/Home/Index/6` ### Необязательные параметры маршрутов Параметры маршрута могут быть необязательными. Чтобы определить параметр как необязательный, после его названия указывается знак вопроса. Например, определим следующее приложение: ```cs app.MapControllerRoute( name: "default", pattern: "{controller}/{action}/{id?}"); ``` В этом шаблоне маршрута третий сегмент представляет параметр _id_, который помечен как необязательный. А это значит, что мы можем в запросе игнорировать значение для этого сегмента. Например, данный шаблон будет соответствовать двух- и трехсегментным запросам, например, двум следующим url: ``` /home/index /home/index/23 ``` Для тестирования определим следующий контроллер: ```cs public class HomeController : Controller { public string Index(int? id) { if(id is not null) return $"Product Id: {id}"; return "Product List"; } } ``` Здесь, если параметр _id_ определен, то метод _Index_ возвращает одну строку, если неопределен - то другую Необязательные параметры следует помещать в конце шаблона маршрута, как в случае с параметром _id_ в примере выше. То есть, к примеру, шаблон "{controller}/{action}/{id?}/{name}" не будет работать корректно, если мы для параметра _id_ не передадим значение. А вот шаблон "{controller}/{action}/{name}/{id?}" будет работать нормально. ### Значения параметров по умолчанию Параметрам маршрута также можно назначить значения по умолчанию на случай, если им не переданы значения: ```cs // устанавливаем сопоставление маршрутов с контроллерами app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); ``` Здесь определен шаблон маршрута, который состоит из трех параметров. Параметр "controller" имеет значение по умолчанию "Home". Параметр "action" имеет значение по умолчанию "Index". Параметр "id" определен как необязательный. В итоге при различных запросах у нас получатся следующие значения: Запрос | Параметры запроса -------|------------------ https://localhost:7256/ | controller=Home
action=Index https://localhost:7256/Book | controller=Book
action=Index https://localhost:7256/Book/Show | controller=Book
action=Show https://localhost:7256/Book/Show/2 | controller=Book
action=Show
id=2 Для установки значений по умолчанию также можно применять параметр _defaults_ метода _MapControllerRoute()_. Этот параметр представляет объект, свойства которого соответствуют параметрам маршрута. Например, определим следующий маршрут: ```cs app.MapControllerRoute( name: "default", pattern: "{action}", defaults: new { controller = "Home", action = "Index"}); ``` Здесь шаблон маршрута состоит из одного сегмента, который соответствует параметру "action", то есть представляет действие контроллера. А параметр _defaults_: ``` defaults: new { controller = "Home", action = "Index"} ``` Устанавливает, что в качестве контроллера по умолчанию будет использоваться контроллер Home, а в качестве действия - метод Index. Например, пусть у нас будет следующий HomeController: ```cs public class HomeController : Controller { public string Index() => "Index Page"; public string About() => "About Page"; } ``` В этом случае запрос типа `https://localhost:7288/` будет обрабатываться методом _Index_ контроллера __Home__, а запрос `https://localhost:7288/About` - методом _About_. При использовании значений по умолчанию мы можем вовсе не использовать в шаблоне параметры маршрута. Например: ```cs app.MapControllerRoute( name: "info", pattern: "contact/info", defaults: new { controller = "Home", action = "About"}); ``` В данном случае запрос `https://localhost:7288/contact/info` будет обрабатываться методом _About_ контроллера __Home__. ### Ограничения маршрутов Для параметров маршрута в MVC, также как и в общем в ASP.NET Core, можно устанавливать ограничения. Подробно ограничения были расписаны в лекции [API. REST API. Создание сервера ASP.NET Core.](./api_asp_net_core.md#ограничения-маршрута) Ограничения маршрутов в ASP.NET. В MVC применяется те же ограничения, что и в общем в ASP.NET Core, поэтому в данной статье я не буду их подробно описывать. Ограничения можно установить непосредственно в шаблоне маршрута: ```cs app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id:int?}"); ``` В данном случае указывается, что параметр _id_ может иметь либо значение типа __int__, либо значение `null` Второй способ установки ограничений представляет параметр _constraints_ метода _MapControllerRoute_: ```cs app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}", constraints: new {id= new IntRouteConstraint()}); // ограничения маршрутов ``` Параметр _constraints_ принимает объект, в котором свойства соответствуют по названиям параметрам маршрутов, а значения свойств - ограничения, применяемые к одноименным параметрам маршрутов. Так, в данном случае к параметру _id_ применяется ограничение __IntRouteConstraint__, которое указывает, что _id_ должно представлять значение типа __int__. ## Атрибуты маршрутизации Кроме маршрутизации на основе условностей или __Convention-Based Routing__, которая рассматривалась в прошлых темах, фреймворк MVC позволяет использовать в приложении маршрутизацию на основе атрибутов. Такой тип маршрутизации еще называется __Attribute-Based Routing__. Атрибуты предоставляют более гибкий способ определения маршрутов. Для определения маршрута непосредственно в контроллере, необходимо использовать атрибут `[Route]`: ```cs public class HomeController : Controller { [Route("Home/Index")] public IActionResult Index() { return Content("ASP.NET Core на metanit.com"); } [Route("About")] public IActionResult About() { return Content("About site"); } } ``` В данном случае метод _Index_ будет обрабатывать запросы по адресу "Home/Index", а метод _About_ по адресу "About". Если в проекте планируется использовать только маршрутизацию на основе атрибутов, то в файле ``Program.cs`` мы можем не определять никаких маршрутов. В этом случае для подключения в приложении возможностей маршрутизации достаточно применить метод _MapControllers_ без определения конкретных маршрутов: ```cs var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllersWithViews(); var app = builder.Build(); app.MapControllers(); ^^^^^^^^^^^^^^^^^^^^^ app.Run(); ``` Но также мы можем комбинировать способы маршрутизации. При чем здесь надо учитывать, что маршрутизация на основе атрибутов имеет больший приоритет. Например, определим следующее действие контроллера: ```cs public class HomeController : Controller { [Route("homepage")] public string Index() => "ASP.NET Core MVC on METANIT.COM"; } ``` В качестве параметра атрибут `Route` принимает шаблон URL, с которым будет сопоставляться запрошенный адрес. И даже если у нас определен стандартный маршрут в файле `Program.cs`: ```cs app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); ``` Запрос типа `http://localhost:xxxx/home/index` работать не будет, так как мы явно указали с помощью атрибута, что метод _Index_ контроллера __Home__ будет обрабатывать только запросы `http://localhost:xxxx/homepage` Определение маршрутов с помощью атрибутов подчиняется тем же правилам, что определение маршрутов с помощью метода _MapControllerRoute()_. Например, используем параметры и ограничения: ```cs [Route("{name:minlength(3)}/{age:int}")] public string Person(string name, int age) { return $"name={name} | age={age}"; } ``` И к такому методу мы сможем обратиться с запросом типа `http://localhost:xxxx/tom/37`. Или другой пример - применение необязательного параметра: ```cs public class HomeController : Controller { [Route("Home/Index/{id?}")] public string Test(int? id) { if(id is not null) return $"Параметр id={id}"; else return $"Параметр id неопределен"; } } ``` И к такому методу мы сможем обратиться с запросом типа `http://localhost:xxxx/home/index` или добавить параметр `http://localhost:xxxx/home/index/5`. ### Использование префиксов Допустим, у нас есть несколько методов, для которых определен свой маршрут, и мы хотим, чтобы эти маршруты начинались с одного определенного префикса. Например: ```cs public class HomeController : Controller { [Route("main/index/{name}")] public string Index(string name) => name; [Route("main/{id:int}/{name:maxlength(10)}")] public string Test(int id, string name) => $" id={id} | name={name}"; } ``` Вместо того, чтобы добавлять префикс "main" к каждому маршруту, мы могли бы определить его глобально для всего контроллера: ```cs [Route("main")] public class HomeController : Controller { [Route("index/{name}")] public string Index(string name) => name; [Route("{id:int}/{name:maxlength(10)}")] public string Test(int id, string name) => $" id={id} | name={name}"; } ``` ### Параметры controller и action От всех параметров шаблона маршрутов в атрибутах отличаются два параметра _controller_ и _action_, которые ссылаются соответственно на контроллер и его действие и которые также можно получить в методе контроллера: ```cs public class HomeController : Controller { [Route("{controller}/{action}")] public string Index() { var controller = RouteData.Values["controller"]; var action = RouteData.Values["action"]; return $"controller: {controller} | action: {action}"; } } ``` ```cs public class HomeController : Controller { [Route("{controller}/{action}")] public string Index(string controller, string action) { return $"controller: {controller} | action: {action}"; } } ``` ### Множественные маршруты С помощью атрибутов можно задать несколько маршрутов для одного метода. Например: ```cs [Route("{controller}")] public class HomeController : Controller { [Route("")] // сопоставляется с Home [Route("Index")] // сопоставляется с Home/Index public string Index() => "Index Page"; } ``` Также для контроллера можно задать сразу несколько маршрутов: ```cs [Route("Store")] [Route("{controller}")] public class HomeController : Controller { [Route("Main")] // сопоставляется с Home/Main, либо с Store/Main [Route("Index")] // сопоставляется с Home/Index, либо с Store/Index public string Index() => "Index Page"; } ``` ### Значения параметров по умолчанию Также параметры в атрибутах могут иметь значения по умолчанию: ```cs public class HomeController : Controller { [Route("{controller=Home}/{action=Index}")] public string Index(string controller, string action) { return $"controller: {controller} | action: {action}"; } } ``` В данном случае методом _Index_ будут сопоставляться такие запросы как `http://localhost:xxxx/home/index`, `http://localhost:xxxx/home/` и `http://localhost:xxxx/` Подобным образом можно задавать значения по умолчанию для других параметров, например: ```cs public class HomeController : Controller { [Route("{name=Tom}")] public string Index(string name) => $"Name: {name}"; } ``` В данном случае если параметру _name_ не передано значение, то он по умолчанию принимает в качестве значения строку "Tom" --- ## Задание на дом Сделать репозиторий - конспект, то есть создать приложение и в конспекте привести блоки кода и скриншоты для всех примеров из лекции.