web_cs_03.md 25 KB

Маршрутизация

Добавление маршрутизации

Чтобы связать контроллеры 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])
    

    Его параметры:

    • name: название машрута
    • pattern: шаблон машрута
    • defaults: значения параметров маршрутов по умолчанию
    • constraints: ограничения маршрута
    • dataTokens: определения токенов маршрута

    Первые два параметра являются обязательными, остальные необязательные.

  • 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

От всех параметров шаблона маршрутов в атрибутах отличаются два параметра 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"