Чтобы связать контроллеры MVC и их действия с запросами применяется система маршрутизации. Чтобы задействовать систему маршрутизации для контроллеров MVC, можно использовать различные способы.
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])
Его параметры:
Первые два параметра являются обязательными, остальные необязательные.
MapDefaultControllerRoute() определяет стандартный маршрут, фактически эквивалентен вызову
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:
public class HomeController : Controller
{
public string Index()
{
var controller = RouteData.Values["controller"];
var action = RouteData.Values["action"];
return $"controller: {controller} | action: {action}";
}
}
Шаблон маршрута может содержать дополнительные параметры, которые можно получить через параметры метода. Например, определим следующие маршруты:
// устанавливаем сопоставление маршрутов с контроллерами
app.MapControllerRoute(name: "default", pattern: "{controller}/{action}/{id}");
app.MapControllerRoute(name: "name_age", pattern: "{controller}/{action}/{name}/{age}");
Здесь первый маршрут - "default" принимает третий параметр - id, который располагается в третьем сегменте строки запроса.
Второй маршрут - "name_age" принимает дополнительно два параметра - name и age, который располагаются соответственно в третьем сегменте и четвертом сегментах строки запроса.
Для тестирования маршрутов определим следующий контроллер:
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:
Шаблоны маршрутов могут иметь статические сегменты, которые не связывются с параметрами маршрутов. Например, определим следующий маршрут:
// устанавливаем сопоставление маршрутов с контроллерами
app.MapControllerRoute(
name: "default",
pattern: "api/{controller}/{action}/{id}");
В данном случае шаблон маршрута начинается со статического сегмента api/. Таким образом, этому маршруту будут соответствовать все маршруты, которые состоят из четырех сегментов, где первый сегмент равен /api, например, запрос https://localhost:7288/api/Home/Index/6
Параметры маршрута могут быть необязательными. Чтобы определить параметр как необязательный, после его названия указывается знак вопроса. Например, определим следующее приложение:
app.MapControllerRoute(
name: "default",
pattern: "{controller}/{action}/{id?}");
В этом шаблоне маршрута третий сегмент представляет параметр id, который помечен как необязательный. А это значит, что мы можем в запросе игнорировать значение для этого сегмента. Например, данный шаблон будет соответствовать двух- и трехсегментным запросам, например, двум следующим url:
/home/index
/home/index/23
Для тестирования определим следующий контроллер:
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?}" будет работать нормально.
Параметрам маршрута также можно назначить значения по умолчанию на случай, если им не переданы значения:
// устанавливаем сопоставление маршрутов с контроллерами
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(). Этот параметр представляет объект, свойства которого соответствуют параметрам маршрута. Например, определим следующий маршрут:
app.MapControllerRoute(
name: "default",
pattern: "{action}",
defaults: new { controller = "Home", action = "Index"});
Здесь шаблон маршрута состоит из одного сегмента, который соответствует параметру "action", то есть представляет действие контроллера. А параметр defaults:
defaults: new { controller = "Home", action = "Index"}
Устанавливает, что в качестве контроллера по умолчанию будет использоваться контроллер Home, а в качестве действия - метод Index.
Например, пусть у нас будет следующий HomeController:
public class HomeController : Controller
{
public string Index() => "Index Page";
public string About() => "About Page";
}
В этом случае запрос типа https://localhost:7288/ будет обрабатываться методом Index контроллера Home, а запрос https://localhost:7288/About - методом About.
При использовании значений по умолчанию мы можем вовсе не использовать в шаблоне параметры маршрута. Например:
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. Ограничения маршрутов в ASP.NET. В MVC применяется те же ограничения, что и в общем в ASP.NET Core, поэтому в данной статье я не буду их подробно описывать.
Ограничения можно установить непосредственно в шаблоне маршрута:
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id:int?}");
В данном случае указывается, что параметр id может иметь либо значение типа int, либо значение null
Второй способ установки ограничений представляет параметр constraints метода MapControllerRoute:
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]:
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 без определения конкретных маршрутов:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
var app = builder.Build();
app.MapControllers();
^^^^^^^^^^^^^^^^^^^^^
app.Run();
Но также мы можем комбинировать способы маршрутизации. При чем здесь надо учитывать, что маршрутизация на основе атрибутов имеет больший приоритет. Например, определим следующее действие контроллера:
public class HomeController : Controller
{
[Route("homepage")]
public string Index() => "ASP.NET Core MVC on METANIT.COM";
}
В качестве параметра атрибут Route принимает шаблон URL, с которым будет сопоставляться запрошенный адрес. И даже если у нас определен стандартный маршрут в файле Program.cs:
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
Запрос типа http://localhost:xxxx/home/index работать не будет, так как мы явно указали с помощью атрибута, что метод Index контроллера Home будет обрабатывать только запросы http://localhost:xxxx/homepage
Определение маршрутов с помощью атрибутов подчиняется тем же правилам, что определение маршрутов с помощью метода MapControllerRoute(). Например, используем параметры и ограничения:
[Route("{name:minlength(3)}/{age:int}")]
public string Person(string name, int age)
{
return $"name={name} | age={age}";
}
И к такому методу мы сможем обратиться с запросом типа http://localhost:xxxx/tom/37.
Или другой пример - применение необязательного параметра:
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.
Допустим, у нас есть несколько методов, для которых определен свой маршрут, и мы хотим, чтобы эти маршруты начинались с одного определенного префикса. Например:
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" к каждому маршруту, мы могли бы определить его глобально для всего контроллера:
[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, которые ссылаются соответственно на контроллер и его действие и которые также можно получить в методе контроллера:
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}";
}
}
public class HomeController : Controller
{
[Route("{controller}/{action}")]
public string Index(string controller, string action)
{
return $"controller: {controller} | action: {action}";
}
}
С помощью атрибутов можно задать несколько маршрутов для одного метода. Например:
[Route("{controller}")]
public class HomeController : Controller
{
[Route("")] // сопоставляется с Home
[Route("Index")] // сопоставляется с Home/Index
public string Index() => "Index Page";
}
Также для контроллера можно задать сразу несколько маршрутов:
[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";
}
Также параметры в атрибутах могут иметь значения по умолчанию:
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/
Подобным образом можно задавать значения по умолчанию для других параметров, например:
public class HomeController : Controller
{
[Route("{name=Tom}")]
public string Index(string name) => $"Name: {name}";
}
В данном случае если параметру name не передано значение, то он по умолчанию принимает в качестве значения строку "Tom"