Маршрутизация к действиям контроллера в ASP.NET Core

  • Михаил
  • 12 мин. на прочтение
  • 175
  • 11 Nov 2022
  • 11 Nov 2022

Контроллеры ASP.NET Core используют ПО промежуточного слоя маршрутизации для сопоставления URL-адресов входящих запросов и сопоставления их с действиями . Шаблоны маршрутов:

  • Определяются при запуске в Program.csили в атрибутах.
  • Опишите, как пути URL сопоставляются с действиями .
  • Используются для генерации URL-адресов для ссылок. Сгенерированные ссылки обычно возвращаются в ответах.


Действия маршрутизируются либо по обычному маршруту , либо по атрибутам . Размещение маршрута на контроллере или в действии делает его маршрутизируемым по атрибутам. Дополнительные сведения см. в разделе Смешанная маршрутизация .

Этот документ:

  • Объясняет взаимодействие между MVC и маршрутизацией:
    • Как типичные приложения MVC используют функции маршрутизации.
    • Охватывает оба:
    • Обычная маршрутизация, обычно используемая с контроллерами и представлениями.
    • Маршрутизация атрибутов, используемая с REST API. Если вас в первую очередь интересует маршрутизация для REST API, перейдите к разделу Маршрутизация атрибутов для REST API .
    • Дополнительные сведения о маршрутизации см. в разделе Маршрутизация.
  • Относится к системе маршрутизации по умолчанию, называемой маршрутизацией конечной точки. Можно использовать контроллеры с предыдущей версией маршрутизации в целях совместимости. Инструкции см . в руководстве по миграции 2.2–3.0 .

Настроить обычный маршрут
Шаблон ASP.NET Core MVC создает обычный код маршрутизации, аналогичный следующему:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
   app.UseExceptionHandler("/Home/Error");
   app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
   name: "default",
   pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();


MapControllerRoute используется для создания одного маршрута. Единственный маршрут называется defaultмаршрутом. Большинство приложений с контроллерами и представлениями используют шаблон маршрута, аналогичный defaultмаршруту. API REST должны использовать маршрутизацию атрибутов .

 

app.MapControllerRoute(
   name: "default",
   pattern: "{controller=Home}/{action=Index}/{id?}");


Шаблон маршрута "{controller=Home}/{action=Index}/{id?}":

  • Соответствует URL-пути, например/Products/Details/5
  • Извлекает значения маршрута { controller = Products, action = Details, id = 5 }путем токенизации пути. 

Извлечение значений маршрута приводит к совпадению, если у приложения есть именованный контроллер ProductsControllerи Detailsдействие:

public class ProductsController : Controller
{
   public IActionResult Details(int id)
   {
       return ControllerContext.MyDisplayRouteInfo(id);
   }
}


MyDisplayRouteInfo предоставляется пакетом NuGet Rick.Docs.Samples.RouteInfo и отображает сведения о маршруте.

  • /Products/Details/5модель связывает значение, id = 5чтобы установить idпараметр равным 5. Дополнительные сведения см. в разделе Связывание модели .
  • {controller=Home}определяет Homeкак значение по умолчанию controller.
  • {action=Index}определяет Indexкак значение по умолчанию action.
  • Символ ?в {id?}определяет idкак необязательный.
  • Параметры маршрута по умолчанию и необязательные параметры не обязательно должны присутствовать в пути URL для соответствия. Подробное описание синтаксиса шаблона маршрута см. в Справочнике по шаблону маршрута.
    Соответствует URL-пути /.
  • Производит значения маршрута { controller = Home, action = Index }.

 

Значения для controllerи actionиспользуют значения по умолчанию. idне дает значения, так как в пути URL нет соответствующего сегмента. /соответствует только в том случае, если существует действие HomeControllerи Index:

public class HomeController : Controller
{
   public IActionResult Index() { ... }
}


Используя предыдущее определение контроллера и шаблон маршрута, HomeController.Indexдействие выполняется для следующих URL-адресов:

  • /Home/Index/17
  • /Home/Index
  • /Home
  • /


В URL-пути используются контроллеры и действие /шаблона маршрута по умолчанию . Путь URL -адреса использует действие по умолчанию шаблона маршрута .HomeIndex/HomeIndex

Удобный метод MapDefaultControllerRoute :

app.MapDefaultControllerRoute();


Заменяет:

app.MapControllerRoute(
   name: "default",
   pattern: "{controller=Home}/{action=Index}/{id?}");



Обычная маршрутизация
Обычная маршрутизация используется с контроллерами и представлениями. Маршрут default:

app.MapControllerRoute(
   name: "default",
   pattern: "{controller=Home}/{action=Index}/{id?}");


Предыдущее является примером обычного маршрута . Это называется обычной маршрутизацией , потому что устанавливает соглашение для URL-адресов:

  • Первый сегмент пути {controller=Home}соответствует имени контроллера.
  • Второй сегмент, {action=Index}, соответствует имени действия .
  • Третий сегмент {id?}используется для необязательного id. In делает его необязательным ?. используется для сопоставления с объектом модели.{id?}id


Используя этот defaultмаршрут, путь URL:

  • /Products/Listсопоставляется с ProductsController.Listдействием.
  • /Blog/Article/17сопоставляется BlogController.Articleи обычно модель привязывает idпараметр к 17.


Это сопоставление:

  • Основан только на имени контроллера и действия .
  • Не основан на пространствах имен, расположении исходных файлов или параметрах метода.


Использование обычной маршрутизации с маршрутом по умолчанию позволяет создать приложение без необходимости придумывать новый шаблон URL для каждого действия. Для приложения с действиями в стиле CRUD , имеющего согласованность URL-адресов между контроллерами:

  • Помогает упростить код.
  • Делает пользовательский интерфейс более предсказуемым.
     

Большинству приложений следует выбирать базовую и описательную схему маршрутизации, чтобы URL-адреса были удобочитаемыми и осмысленными. Обычный маршрут по умолчанию {controller=Home}/{action=Index}/{id?}:

  • Поддерживает базовую и описательную схему маршрутизации.
  • Является полезной отправной точкой для приложений на основе пользовательского интерфейса.
  • Единственный шаблон маршрута, необходимый для многих приложений веб-интерфейса. Для более крупных приложений с веб-интерфейсом зачастую достаточно другого маршрута с использованием областей .


MapControllerRoute и MapAreaRoute :

  • Автоматически назначать значение порядка их конечным точкам в зависимости от порядка их вызова.


Маршрутизация конечной точки в ASP.NET Core:

  • Не имеет понятия о маршрутах.
  • Не предоставляет гарантии порядка выполнения расширяемости, все конечные точки обрабатываются одновременно.


Включите ведение журнала , чтобы увидеть, как встроенные реализации маршрутизации, такие как Route , сопоставляют запросы.

Маршрутизация атрибутов объясняется далее в этом документе.


Несколько обычных маршрутов
Несколько обычных маршрутов можно настроить, добавив дополнительные вызовы MapControllerRoute и MapAreaControllerRoute . Это позволяет определить несколько соглашений или добавить обычные маршруты, предназначенные для определенного действия , например:

app.MapControllerRoute(name: "blog",
               pattern: "blog/{*article}",
               defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
              pattern: "{controller=Home}/{action=Index}/{id?}");


Маршрут blogв предыдущем коде является выделенным обычным маршрутом . Он называется выделенным обычным маршрутом, потому что:

  • Он использует обычную маршрутизацию .
  • Он посвящен конкретному действию .


Потому что controllerи actionне отображаются в шаблоне маршрута "blog/{*article}"в качестве параметров:

  • Они могут иметь только значения по умолчанию { controller = "Blog", action = "Article" }.
  • Этот маршрут всегда соответствует действию BlogController.Article.


/Blog, /Blog/Articleи /Blog/{any-string}— единственные пути URL, соответствующие маршруту блога.

Предыдущий пример:

  • blogмаршрут имеет более высокий приоритет для совпадений, чем defaultмаршрут, потому что он добавляется первым.
  • Является примером маршрутизации в стиле Slug , где обычно название статьи является частью URL-адреса.
     

Обычный порядок маршрутизации
Обычная маршрутизация соответствует только комбинации действия и контроллера, которые определены приложением. Это предназначено для упрощения случаев, когда обычные маршруты перекрываются. Добавление маршрутов с помощью MapControllerRoute , MapDefaultControllerRoute и MapAreaControllerRoute автоматически присваивает значение порядка их конечным точкам в зависимости от порядка их вызова. Совпадения из маршрута, который появился раньше, имеют более высокий приоритет. Обычная маршрутизация зависит от заказа. Как правило, маршруты с областями следует размещать раньше, поскольку они более специфичны, чем маршруты без зон. Выделенные обычные маршруты с универсальными параметрами маршрута, например, {*article}могут сделать маршрут слишком жадным ., что означает, что он соответствует URL-адресам, которые вы намеревались сопоставить с другими маршрутами. Поместите жадные маршруты позже в таблицу маршрутов, чтобы предотвратить жадные совпадения.


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

  • Выберите лучшего кандидата.
  • Сбросить исключение.


Например:

public class Products33Controller : Controller
{
   public IActionResult Edit(int id)
   {
       return ControllerContext.MyDisplayRouteInfo(id);
   }
   [HttpPost]
   public IActionResult Edit(int id, Product product)
   {
       return ControllerContext.MyDisplayRouteInfo(id, product.name);
   }
}


Предыдущий контроллер определяет два совпадающих действия:

  • URL-адрес/Products33/Edit/17
  • Данные маршрута { controller = Products33, action = Edit, id = 17 }.


Это типичный шаблон для контроллеров MVC:

  • Edit(int)отображает форму для редактирования продукта.
  • Edit(int, Product)обрабатывает отправленную форму.


Чтобы решить правильный маршрут:

  • Edit(int, Product)выбирается, когда запрос является HTTP POST.
  • Edit(int)выбирается, когда HTTP-глагол является чем-то другим. Edit(int)обычно вызывается через GET.


HttpPostAttribute предоставляется маршрутизации , [HttpPost]чтобы она могла выбирать на основе HTTP-метода запроса. Лучшее HttpPostAttributeсовпадение Edit(int, Product), чем Edit(int).

Важно понимать роль таких атрибутов, как HttpPostAttribute. Аналогичные атрибуты определены для других HTTP-команд . В обычной маршрутизации действия обычно используют одно и то же имя действия, когда они являются частью рабочего процесса формы отображения, отправки формы. Например, см . раздел Изучение двух методов действия «Редактировать » .

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


Традиционные названия маршрутов
Строки "blog"и "default"в следующих примерах являются обычными именами маршрутов:

app.MapControllerRoute(name: "blog",
               pattern: "blog/{*article}",
               defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
              pattern: "{controller=Home}/{action=Index}/{id?}");

Имена маршрутов дают маршруту логическое имя. Именованный маршрут можно использовать для генерации URL. Использование именованного маршрута упрощает создание URL-адресов, когда порядок маршрутов может усложнить создание URL-адресов. Имена маршрутов должны быть уникальными для всего приложения.

Названия маршрутов:

  • Не влияет на сопоставление URL или обработку запросов.
  • Используются только для генерации URL.


Концепция имени маршрута представлена в маршрутизации как IEndpointNameMetadata . Термины имя маршрута и имя конечной точки :

  • взаимозаменяемы.
  • Какой из них используется в документации и коде, зависит от описываемого API.

Маршрутизация атрибутов для REST API
REST API должны использовать маршрутизацию атрибутов для моделирования функциональности приложения как набора ресурсов, где операции представлены глаголами HTTP .

Атрибутная маршрутизация использует набор атрибутов для непосредственного сопоставления действий с шаблонами маршрутов. Следующий код типичен для REST API и используется в следующем примере:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();


В предыдущем коде MapControllers вызывается для сопоставления маршрутизируемых контроллеров атрибутов.

В следующем примере:

  • HomeControllerсоответствует набору URL-адресов, аналогичных тому, что соответствует обычному маршруту по умолчанию {controller=Home}/{action=Index}/{id?}.
     
public class HomeController : Controller
{
   [Route("")]
   [Route("Home")]
   [Route("Home/Index")]
   [Route("Home/Index/{id?}")]
   public IActionResult Index(int? id)
   {
       return ControllerContext.MyDisplayRouteInfo(id);
   }
   [Route("Home/About")]
   [Route("Home/About/{id?}")]
   public IActionResult About(int? id)
   {
       return ControllerContext.MyDisplayRouteInfo(id);
   }
}


Действие HomeController.Indexвыполняется для любого из путей URL /, /Home, /Home/Indexили /Home/Index/3.

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

При маршрутизации атрибутов имена контроллера и действия не играют никакой роли в сопоставлении действия, если только не используется замена токена . Следующий пример соответствует тем же URL-адресам, что и предыдущий пример:

public class MyDemoController : Controller
{
   [Route("")]
   [Route("Home")]
   [Route("Home/Index")]
   [Route("Home/Index/{id?}")]
   public IActionResult MyIndex(int? id)
   {
       return ControllerContext.MyDisplayRouteInfo(id);
   }
   [Route("Home/About")]
   [Route("Home/About/{id?}")]
   public IActionResult MyAbout(int? id)
   {
       return ControllerContext.MyDisplayRouteInfo(id);
   }
}


В следующем коде используется замена токена для actionи controller:


public class HomeController : Controller
{
   [Route("")]
   [Route("Home")]
   [Route("[controller]/[action]")]
   public IActionResult Index()
   {
       return ControllerContext.MyDisplayRouteInfo();
   }
   [Route("[controller]/[action]")]
   public IActionResult About()
   {
       return ControllerContext.MyDisplayRouteInfo();
   }
}


Следующий код относится [Route("[controller]/[action]")]к контроллеру:

[Route("[controller]/[action]")]
public class HomeController : Controller
{
   [Route("~/")]
   [Route("/Home")]
   [Route("~/Home/Index")]
   public IActionResult Index()
   {
       return ControllerContext.MyDisplayRouteInfo();
   }
   public IActionResult About()
   {
       return ControllerContext.MyDisplayRouteInfo();
   }
}


В приведенном выше коде Indexшаблоны методов должны стоять перед шаблонами маршрутов /или перед ними. ~/Шаблоны маршрутов, применяемые к действию, которые начинаются с шаблонов маршрутов, применяемых к контроллеру, /или ~/не объединяются с ними.

Информацию о выборе шаблона маршрута см. в разделе Приоритет шаблона маршрута.

Зарезервированные имена маршрутизации
Следующие ключевые слова являются зарезервированными именами параметров маршрута при использовании контроллеров или Razor Pages:

  • action
  • area
  • controller
  • handler
  • page


Использование pageв качестве параметра маршрута с атрибутивной маршрутизацией является распространенной ошибкой. Это приводит к непоследовательному и запутанному поведению при создании URL.

 

public class MyDemo2Controller : Controller
{
   [Route("/articles/{page}")]
   public IActionResult ListArticles(int page)
   {
       return ControllerContext.MyDisplayRouteInfo(page);
   }
}


Имена специальных параметров используются при создании URL-адресов, чтобы определить, относится ли операция создания URL-адресов к странице Razor или к контроллеру.

  • Следующие ключевые слова зарезервированы в контексте представления Razor или страницы Razor:
  • page
  • using
  • namespace
  • inject
  • section
  • inherits
  • model
  • addTagHelper
  • removeTagHelper

Эти ключевые слова не следует использовать для генерации ссылок, связанных с моделью параметров или свойств верхнего уровня.


Шаблоны глаголов HTTP
В ASP.NET Core есть следующие шаблоны глаголов HTTP:

  • [HttpGet]
  • [HttpPost]
  • [HttpPut]
  • [HttpDelete]
  • [HttpHead]
  • [HttpPatch]

Шаблоны маршрутов
ASP.NET Core имеет следующие шаблоны маршрутов:

  • Все шаблоны глаголов HTTP являются шаблонами маршрутов.
  • [Маршрут]

Маршрутизация атрибутов с помощью атрибутов команды Http
Рассмотрим следующий контроллер:

[Route("api/[controller]")]
[ApiController]
public class Test2Controller : ControllerBase
{
   [HttpGet]   // GET /api/test2
   public IActionResult ListProducts()
   {
       return ControllerContext.MyDisplayRouteInfo();
   }
   [HttpGet("{id}")]   // GET /api/test2/xyz
   public IActionResult GetProduct(string id)
   {
      return ControllerContext.MyDisplayRouteInfo(id);
   }
   [HttpGet("int/{id:int}")] // GET /api/test2/int/3
   public IActionResult GetIntProduct(int id)
   {
       return ControllerContext.MyDisplayRouteInfo(id);
   }
   [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
   public IActionResult GetInt2Product(int id)
   {
       return ControllerContext.MyDisplayRouteInfo(id);
   }
}


В предыдущем коде:

  • Каждое действие содержит [HttpGet]атрибут, ограничивающий соответствие только HTTP-запросам GET.
  • Действие GetProductвключает "{id}"шаблон, поэтому idдобавляется к "api/[controller]"шаблону на контроллере. Шаблон методов "api/[controller]/"{id}"". Поэтому это действие соответствует только запросам GET для формы /api/test2/xyz, /api/test2/123, /api/test2/{any string}и т. д.
[HttpGet("{id}")]   // GET /api/test2/xyz
public IActionResult GetProduct(string id)
{
  return ControllerContext.MyDisplayRouteInfo(id);
}
  • Действие GetIntProductсодержит "int/{id:int}")шаблон. Часть :intшаблона ограничивает idзначения маршрута строками, которые можно преобразовать в целое число. GET-запрос к /api/test2/int/abc:
  • Не соответствует этому действию.
  • Возвращает ошибку 404 Not Found .
[HttpGet("int/{id:int}")] // GET /api/test2/int/3
public IActionResult GetIntProduct(int id)
{
   return ControllerContext.MyDisplayRouteInfo(id);
}
  • Действие GetInt2Productсодержится {id}в шаблоне, но не ограничивается idзначениями, которые можно преобразовать в целое число. GET-запрос к /api/test2/int2/abc:
  • Соответствует этому маршруту.
  • Привязку модели не удается преобразовать abcв целое число. Параметр idметода является целым числом.
  • Возвращает неверный запрос 400 , поскольку привязку модели не удалось преобразовать abcв целое число.
     
[HttpGet("int2/{id}")]  // GET /api/test2/int2/3
public IActionResult GetInt2Product(int id)
{
   return ControllerContext.MyDisplayRouteInfo(id);
}


Маршрутизация атрибутов может использовать атрибуты HttpMethodAttribute , такие как HttpPostAttribute , HttpPutAttribute и HttpDeleteAttribute . Все атрибуты команды HTTP принимают шаблон маршрута. В следующем примере показаны два действия, соответствующие одному и тому же шаблону маршрута:

[ApiController]
public class MyProductsController : ControllerBase
{
   [HttpGet("/products3")]
   public IActionResult ListProducts()
   {
       return ControllerContext.MyDisplayRouteInfo();
   }
   [HttpPost("/products3")]
   public IActionResult CreateProduct(MyProduct myProduct)
   {
       return ControllerContext.MyDisplayRouteInfo(myProduct.Name);
   }
}


Используя путь URL /products3:

  • Действие MyProductsController.ListProductsвыполняется, когда глагол HTTP имеет значение GET.
  • Действие MyProductsController.CreateProductвыполняется, когда глагол HTTP имеет значение POST.


При создании REST API вам редко придется использовать [Route(...)]метод действия, потому что действие принимает все методы HTTP. Лучше использовать более конкретный атрибут HTTP-глагола , чтобы быть точным в отношении того, что поддерживает ваш API. Ожидается, что клиенты API REST будут знать, какие пути и HTTP-команды сопоставляются с конкретными логическими операциями.

REST API должны использовать маршрутизацию атрибутов для моделирования функциональности приложения как набора ресурсов, где операции представлены глаголами HTTP. Это означает, что многие операции, например, GET и POST для одного и того же логического ресурса, используют один и тот же URL-адрес. Маршрутизация атрибутов обеспечивает уровень контроля, необходимый для тщательного проектирования макета общедоступной конечной точки API.

Поскольку атрибут маршрута применяется к конкретному действию, легко сделать параметры обязательными как часть определения шаблона маршрута. В следующем примере idтребуется как часть URL-адреса:


[ApiController]
public class Products2ApiController : ControllerBase
{
   [HttpGet("/products2/{id}", Name = "Products_List")]
   public IActionResult GetProduct(int id)
   {
       return ControllerContext.MyDisplayRouteInfo(id);
   }
}


Действие Products2ApiController.GetProduct(int):

  • Запускается с URL-адресом, например/products2/3
  • Не запускается с URL-путем /products2.


Атрибут [Consumes] позволяет действию ограничивать поддерживаемые типы содержимого запросов. Дополнительные сведения см. в разделе Определение поддерживаемых типов содержимого запросов с помощью атрибута Consumes .

Полное описание шаблонов маршрутов и связанных параметров см. в разделе Маршрутизация .

Дополнительные сведения об атрибуте ApiController[ApiController] см . в разделе .

Название маршрута
Следующий код определяет имя маршрута Products_List:

[ApiController]
public class Products2ApiController : ControllerBase
{
   [HttpGet("/products2/{id}", Name = "Products_List")]
   public IActionResult GetProduct(int id)
   {
       return ControllerContext.MyDisplayRouteInfo(id);
   }
}


Имена маршрутов можно использовать для создания URL-адреса на основе определенного маршрута. Названия маршрутов:

  • Не влияет на поведение маршрутизации при сопоставлении URL-адресов.
  • Используются только для генерации URL.


Имена маршрутов должны быть уникальными для всего приложения.

Сравните предыдущий код с обычным маршрутом по умолчанию, который определяет idпараметр как необязательный ( {id?}). Возможность точного указания API имеет преимущества, такие как разрешение /productsи /products/5возможность отправки к различным действиям.


Объединение атрибутивных маршрутов
Чтобы сделать маршрутизацию атрибутов менее повторяющейся, атрибуты маршрута на контроллере объединяются с атрибутами маршрута в отдельных действиях. Любые шаблоны маршрутов, определенные в контроллере, добавляются перед шаблонами маршрутов в действиях. Размещение атрибута маршрута в контроллере приводит к тому, что все действия в контроллере используют маршрутизацию атрибутов.

[ApiController]
[Route("products")]
public class ProductsApiController : ControllerBase
{
   [HttpGet]
   public IActionResult ListProducts()
   {
       return ControllerContext.MyDisplayRouteInfo();
   }
   [HttpGet("{id}")]
   public IActionResult GetProduct(int id)
   {
       return ControllerContext.MyDisplayRouteInfo(id);
   }
}


В предыдущем примере:

  • Путь URL /productsможет совпадатьProductsApi.ListProducts
  • Путь URL /products/5может совпадать с ProductsApi.GetProduct(int).


Оба этих действия соответствуют только HTTP GET, потому что они помечены [HttpGet]атрибутом.

Шаблоны маршрутов, применяемые к действию, которые начинаются с шаблонов маршрутов, применяемых к контроллеру, /или ~/не объединяются с ними. В следующем примере набор URL-адресов соответствует маршруту по умолчанию.

[Route("Home")]
public class HomeController : Controller
{
   [Route("")]
   [Route("Index")]
   [Route("/")]
   public IActionResult Index()
   {
       return ControllerContext.MyDisplayRouteInfo();
   }
   [Route("About")]
   public IActionResult About()
   {
       return ControllerContext.MyDisplayRouteInfo();
   }
}


В следующей таблице объясняются [Route]атрибуты в предыдущем коде:

Атрибут Сочетается с[Route("Home")] Определяет шаблон маршрута
[Route("")] Да "Home"
[Route("Index")] Да "Home/Index"
[Route("/")] Нет ""
[Route("About")] Да "Home/About"

Порядок маршрута атрибутов
Маршрутизация строит дерево и сопоставляет все конечные точки одновременно:

  • Записи маршрутов ведут себя так, как если бы они были размещены в идеальном порядке.
  • Наиболее специфичные маршруты имеют шанс выполниться раньше более общих маршрутов.


Например, маршрут с атрибутом, например blog/search/{topic}, более специфичен, чем маршрут с атрибутом, например blog/{*article}. Маршрут blog/search/{topic}имеет более высокий приоритет по умолчанию, потому что он более специфичен. Используя обычную маршрутизацию , разработчик отвечает за размещение маршрутов в желаемом порядке.

Маршруты атрибутов могут настраивать порядок с помощью свойства Order . Все атрибуты маршрута , предоставляемые платформой, включают Order. Маршруты обрабатываются в порядке возрастания Orderсвойства. Порядок по умолчанию такой 0. Установка маршрута с использованием Order = -1пробегов перед маршрутами, которые не устанавливают порядок. Установка маршрута с использованием Order = 1трасс после порядка маршрутов по умолчанию.

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

Рассмотрим следующие два контроллера, оба из которых определяют сопоставление маршрутов /home:

public class HomeController : Controller
{
   [Route("")]
   [Route("Home")]
   [Route("Home/Index")]
   [Route("Home/Index/{id?}")]
   public IActionResult Index(int? id)
   {
       return ControllerContext.MyDisplayRouteInfo(id);
   }
   [Route("Home/About")]
   [Route("Home/About/{id?}")]
   public IActionResult About(int? id)
   {
       return ControllerContext.MyDisplayRouteInfo(id);
   }
}
public class MyDemoController : Controller
{
   [Route("")]
   [Route("Home")]
   [Route("Home/Index")]
   [Route("Home/Index/{id?}")]
   public IActionResult MyIndex(int? id)
   {
       return ControllerContext.MyDisplayRouteInfo(id);
   }
   [Route("Home/About")]
   [Route("Home/About/{id?}")]
   public IActionResult MyAbout(int? id)
   {
       return ControllerContext.MyDisplayRouteInfo(id);
   }
}


Запрос /homeс помощью предыдущего кода вызывает исключение, подобное следующему:

AmbiguousMatchException: The request matched multiple endpoints. Matches:
WebMvcRouting.Controllers.HomeController.Index
WebMvcRouting.Controllers.MyDemoController.MyIndex


Добавление Orderк одному из атрибутов маршрута устраняет неоднозначность:

[Route("")]
[Route("Home", Order = 2)]
[Route("Home/MyIndex")]
public IActionResult MyIndex()
{
   return ControllerContext.MyDisplayRouteInfo();
}

С помощью предыдущего кода /homeзапускает HomeController.Indexконечную точку. Чтобы добраться до MyDemoController.MyIndex, запросите /home/MyIndex. Примечание :

  • Предыдущий код является примером плохой схемы маршрутизации. Он был использован для иллюстрации Orderсвойства.
  • Свойство Orderтолько разрешает неоднозначность, этот шаблон не может быть сопоставлен. [Route("Home")]Шаблон лучше убрать .


См. раздел Соглашения о маршрутах и приложениях Razor Pages: порядок маршрутов для получения информации о порядке маршрутов с Razor Pages.

В некоторых случаях возвращается ошибка HTTP 500 с неоднозначными маршрутами. Используйте ведение журнала , чтобы увидеть, какие конечные точки вызвали AmbiguousMatchException.


Замена токена в шаблонах маршрутов [контроллер], [действие], [область]
Для удобства маршруты атрибутов поддерживают замену токена , заключая токен в квадратные скобки ( [, ]). Маркеры [action], [area]и [controller]заменяются значениями имени действия, имени области и имени контроллера из действия, в котором определен маршрут:

[Route("[controller]/[action]")]
public class Products0Controller : Controller
{
   [HttpGet]
   public IActionResult List()
   {
       return ControllerContext.MyDisplayRouteInfo();
   }

   [HttpGet("{id}")]
   public IActionResult Edit(int id)
   {
       return ControllerContext.MyDisplayRouteInfo(id);
   }
}


В предыдущем коде:

[HttpGet]
public IActionResult List()
{
   return ControllerContext.MyDisplayRouteInfo();
}
  • Спички/Products0/List
     

[HttpGet("{id}")]
public IActionResult Edit(int id)
{
   return ControllerContext.MyDisplayRouteInfo(id);
}
  • Спички/Products0/Edit/{id}


Замена токена происходит на последнем этапе построения маршрутов атрибутов. Предыдущий пример ведет себя так же, как следующий код:


public class Products20Controller : Controller
{
   [HttpGet("[controller]/[action]")]  // Matches '/Products20/List'
   public IActionResult List()
   {
       return ControllerContext.MyDisplayRouteInfo();
   }
   [HttpGet("[controller]/[action]/{id}")]   // Matches '/Products20/Edit/{id}'
   public IActionResult Edit(int id)
   {
       return ControllerContext.MyDisplayRouteInfo(id);
   }
}


Если вы читаете это на языке, отличном от английского, сообщите нам в этом выпуске обсуждения GitHub , если вы хотите увидеть комментарии к коду на своем родном языке.

Атрибутные маршруты также можно комбинировать с наследованием. Это мощно в сочетании с заменой токена. Замена токена также применяется к именам маршрутов, определенным атрибутивными маршрутами. [Route("[controller]/[action]", Name="[controller]_[action]")]генерирует уникальное имя маршрута для каждого действия:

[ApiController]
[Route("api/[controller]/[action]", Name = "[controller]_[action]")]
public abstract class MyBase2Controller : ControllerBase
{
}
public class Products11Controller : MyBase2Controller
{
   [HttpGet]                      // /api/products11/list
   public IActionResult List()
   {
       return ControllerContext.MyDisplayRouteInfo();
   }
   [HttpGet("{id}")]             //    /api/products11/edit/3
   public IActionResult Edit(int id)
   {
       return ControllerContext.MyDisplayRouteInfo(id);
   }
}


Чтобы соответствовать буквальному разделителю замены токена [или ], избегайте его, повторяя символ ( [[или ]]).


Используйте преобразователь параметров для настройки замены токена
Замена токена может быть настроена с помощью преобразователя параметров. Преобразователь параметров реализует IOutboundParameterTransformer и преобразует значения параметров. Например, SlugifyParameterTransformerпреобразователь настраиваемого параметра изменяет SubscriptionManagementзначение маршрута на subscription-management:


using System.Text.RegularExpressions;
public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
   public string? TransformOutbound(object? value)
   {
       if (value == null) { return null; }
       return Regex.Replace(value.ToString()!,
                            "([a-z])([A-Z])",
                            "$1-$2",
                            RegexOptions.CultureInvariant,
                            TimeSpan.FromMilliseconds(100)).ToLowerInvariant();
   }
}


RouteTokenTransformerConvention — это соглашение модели приложения, которое:

  • Применяет преобразователь параметров ко всем маршрутам атрибутов в приложении.
  • Настраивает значения маркера маршрута атрибута по мере их замены.
     

public class SubscriptionManagementController : Controller
{
   [HttpGet("[controller]/[action]")]
   public IActionResult ListAll()
   {
       return ControllerContext.MyDisplayRouteInfo();
   }
}


Предыдущий ListAllметод соответствует /subscription-management/list-all.

Регистрируется RouteTokenTransformerConventionкак опция:

using Microsoft.AspNetCore.Mvc.ApplicationModels;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews(options =>
{
   options.Conventions.Add(new RouteTokenTransformerConvention(
                                new SlugifyParameterTransformer()));
});
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
   app.UseExceptionHandler("/Home/Error");
   app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(name: "default",
              pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();

Маршруты с несколькими атрибутами
Маршрутизация атрибутов поддерживает определение нескольких маршрутов, ведущих к одному и тому же действию. Чаще всего это используется для имитации поведения обычного маршрута по умолчанию, как показано в следующем примере:

[Route("[controller]")]
public class Products13Controller : Controller
{
   [Route("")]     // Matches 'Products13'
   [Route("Index")] // Matches 'Products13/Index'
   public IActionResult Index()
   {
       return ControllerContext.MyDisplayRouteInfo();
   }


Размещение нескольких атрибутов маршрута в контроллере означает, что каждый из них сочетается с каждым из атрибутов маршрута в методах действия:


[Route("Store")]
[Route("[controller]")]
public class Products6Controller : Controller
{
   [HttpPost("Buy")]       // Matches 'Products6/Buy' and 'Store/Buy'
   [HttpPost("Checkout")]  // Matches 'Products6/Checkout' and 'Store/Checkout'
   public IActionResult Buy()
   {
       return ControllerContext.MyDisplayRouteInfo();
   }
}


Все ограничения маршрута глагола HTTP реализуются IActionConstraint.

Когда несколько атрибутов маршрута, реализующих IActionConstraint , помещаются в действие:

  • Каждое ограничение действия сочетается с шаблоном маршрута, примененным к контроллеру.
     
[Route("api/[controller]")]
public class Products7Controller : ControllerBase
{
   [HttpPut("Buy")]        // Matches PUT 'api/Products7/Buy'
   [HttpPost("Checkout")]  // Matches POST 'api/Products7/Checkout'
   public IActionResult Buy()
   {
       return ControllerContext.MyDisplayRouteInfo();
   }
}


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


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

public class Products14Controller : Controller
{
   [HttpPost("product14/{id:int}")]
   public IActionResult ShowProduct(int id)
   {
       return ControllerContext.MyDisplayRouteInfo(id);
   }
}


В предыдущем коде [HttpPost("product14/{id:int}")]применяет ограничение маршрута. Действие Products14Controller.ShowProductсоответствует только путям URL, таким как /product14/3. Часть шаблона маршрута {id:int}ограничивает этот сегмент только целыми числами.

Подробное описание синтаксиса шаблона маршрута см. в Справочнике по шаблону маршрута.


Пользовательские атрибуты маршрута с использованием IRouteTemplateProvider
Все атрибуты маршрута реализуют IRouteTemplateProvider . Среда выполнения ASP.NET Core:

  • Ищет атрибуты в классах контроллеров и методах действий при запуске приложения.
  • Использует реализуемые атрибуты IRouteTemplateProviderдля построения начального набора маршрутов.


Реализуйте IRouteTemplateProvider, чтобы определить настраиваемые атрибуты маршрута. Каждый из них IRouteTemplateProviderпозволяет вам определить один маршрут с собственным шаблоном маршрута, порядком и именем:

public class MyApiControllerAttribute : Attribute, IRouteTemplateProvider
{
   public string Template => "api/[controller]";
   public int? Order => 2;
   public string Name { get; set; } = string.Empty;
}
[MyApiController]
[ApiController]
public class MyTestApiController : ControllerBase
{
   // GET /api/MyTestApi
   [HttpGet]
   public IActionResult Get()
   {
       return ControllerContext.MyDisplayRouteInfo();
   }
}


Предыдущий Getметод возвращает Order = 2, Template = api/MyTestApi.


Используйте модель приложения для настройки маршрутов атрибутов
Модель приложения:

  • Создается ли объектная модель при запуске в Program.cs.
  • Содержит все метаданные, используемые ASP.NET Core для маршрутизации и выполнения действий в приложении.


Модель приложения включает в себя все данные, собранные из атрибутов маршрута. Данные из атрибутов маршрута предоставляются IRouteTemplateProviderреализацией. Соглашения:

  • Может быть написан для изменения модели приложения, чтобы настроить поведение маршрутизации.
  • Читаются при запуске приложения.


В этом разделе показан базовый пример настройки маршрутизации с использованием модели приложения. Следующий код заставляет маршруты примерно соответствовать структуре папок проекта.

public class NamespaceRoutingConvention : Attribute, IControllerModelConvention
{
   private readonly string _baseNamespace;
   public NamespaceRoutingConvention(string baseNamespace)
   {
       _baseNamespace = baseNamespace;
   }
   public void Apply(ControllerModel controller)
   {
       var hasRouteAttributes = controller.Selectors.Any(selector =>
                                               selector.AttributeRouteModel != null);
       if (hasRouteAttributes)
       {
           return;
       }
       var namespc = controller.ControllerType.Namespace;
       if (namespc == null)
           return;
       var template = new StringBuilder();
       template.Append(namespc, _baseNamespace.Length + 1,
                       namespc.Length - _baseNamespace.Length - 1);
       template.Replace('.', '/');
       template.Append("/[controller]/[action]/{id?}");
       foreach (var selector in controller.Selectors)
       {
           selector.AttributeRouteModel = new AttributeRouteModel()
           {
               Template = template.ToString()
           };
       }
   }
}


Следующий код предотвращает применение namespaceсоглашения к контроллерам с маршрутизацией по атрибутам:

public void Apply(ControllerModel controller)
{
   var hasRouteAttributes = controller.Selectors.Any(selector =>
                                           selector.AttributeRouteModel != null);
   if (hasRouteAttributes)
   {
       return;
   }


Например, следующий контроллер не использует NamespaceRoutingConvention:

[Route("[controller]/[action]/{id?}")]
public class ManagersController : Controller
{
   // /managers/index
   public IActionResult Index()
   {
       var template = ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
       return Content($"Index- template:{template}");
   }
   public IActionResult List(int? id)
   {
       var path = Request.Path.Value;
       return Content($"List- Path:{path}");
   }
}


Метод NamespaceRoutingConvention.Apply:

  • Ничего не делает, если контроллер маршрутизируется по атрибутам.
  • Устанавливает шаблон контроллеров на основе , с удаленной namespaceбазой .namespace


NamespaceRoutingConventionМожет применяться в Program.cs:

using My.Application.Controllers;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews(options =>
{
   options.Conventions.Add(
    new NamespaceRoutingConvention(typeof(HomeController).Namespace!));
});
var app = builder.Build();


Например, рассмотрим следующий контроллер:

using Microsoft.AspNetCore.Mvc;
namespace My.Application.Admin.Controllers
{
   public class UsersController : Controller
   {
       // GET /admin/controllers/users/index
       public IActionResult Index()
       {
           var fullname = typeof(UsersController).FullName;
           var template = 
               ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
           var path = Request.Path.Value;
           return Content($"Path: {path} fullname: {fullname}  template:{template}");
       }
       public IActionResult List(int? id)
       {
           var path = Request.Path.Value;
           return Content($"Path: {path} ID:{id}");
       }
   }
}


В предыдущем коде:

  • База namespaceесть My.Application.
  • Полное имя предыдущего контроллера — My.Application.Admin.Controllers.UsersController.
  • Устанавливает NamespaceRoutingConventionшаблон контроллера в Admin/Controllers/Users/[action]/{id?.


Также NamespaceRoutingConventionможет применяться как атрибут контроллера:

[NamespaceRoutingConvention("My.Application")]
public class TestController : Controller
{
   // /admin/controllers/test/index
   public IActionResult Index()
   {
       var template = ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
       var actionname = ControllerContext.ActionDescriptor.ActionName;
       return Content($"Action- {actionname} template:{template}");
   }
   public IActionResult List(int? id)
   {
       var path = Request.Path.Value;
       return Content($"List- Path:{path}");
   }
}

Смешанная маршрутизация: маршрутизация по атрибутам и обычная маршрутизация
Приложения ASP.NET Core могут сочетать использование обычной маршрутизации и маршрутизации по атрибутам. Обычно используются обычные маршруты для контроллеров, обслуживающих HTML-страницы для браузеров, и маршрутизация по атрибутам для контроллеров, обслуживающих REST API.

Действия либо маршрутизируются обычным образом, либо маршрутизируются по атрибутам. Размещение маршрута на контроллере или действие делает его маршрутизируемым атрибутом. Действия, которые определяют маршруты атрибутов, не могут быть достигнуты через обычные маршруты и наоборот. Любой атрибут маршрута в контроллере делает маршрутизируемыми все действия в атрибуте контроллера.

Атрибутная маршрутизация и обычная маршрутизация используют один и тот же механизм маршрутизации.


Генерация URL и внешние значения
Приложения могут использовать функции генерации URL-адресов маршрутизации для создания URL-ссылок на действия. Генерация URL-адресов устраняет жестко закодированные URL- адреса, делая код более надежным и удобным в сопровождении. В этом разделе основное внимание уделяется функциям генерации URL-адресов, предоставляемым MVC, и рассматриваются только основы того, как работает генерация URL-адресов. Подробное описание создания URL-адресов см. в разделе Маршрутизация .

Интерфейс IUrlHelper является базовым элементом инфраструктуры между MVC и маршрутизацией для генерации URL. Экземпляр IUrlHelperдоступен через Urlсвойство в контроллерах, представлениях и компонентах представления.

В следующем примере IUrlHelperинтерфейс используется через Controller.Urlсвойство для создания URL-адреса другого действия.

public class UrlGenerationController : Controller
{
   public IActionResult Source()
   {
       // Generates /UrlGeneration/Destination
       var url = Url.Action("Destination");
       return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
   }
   public IActionResult Destination()
   {
       return ControllerContext.MyDisplayRouteInfo();
   }
}


Если приложение использует обычный маршрут по умолчанию, значением urlпеременной является строка пути URL-адреса /UrlGeneration/Destination. Этот URL-путь создается путем маршрутизации путем объединения:

  • Значения маршрута из текущего запроса, которые называются окружающими значениями .
  • Значения, переданные Url.Actionи заменяющие эти значения в шаблоне маршрута:
     
ambient values: { controller = "UrlGeneration", action = "Source" }
values passed to Url.Action: { controller = "UrlGeneration", action = "Destination" }
route template: {controller}/{action}/{id?}
result: /UrlGeneration/Destination


Значение каждого параметра маршрута в шаблоне маршрута заменено именами, соответствующими значениям и значениям окружения. Параметр маршрута, не имеющий значения, может:

  • Используйте значение по умолчанию, если оно есть.
  • Пропустить, если это необязательно. Например, idиз шаблона маршрута {controller}/{action}/{id?}.


Генерация URL завершается ошибкой, если какой-либо требуемый параметр маршрута не имеет соответствующего значения. Если создание URL-адреса для маршрута завершается неудачно, пробуется следующий маршрут до тех пор, пока не будут опробованы все маршруты или пока не будет найдено совпадение.

В предыдущем примере Url.Actionпредполагается обычная маршрутизация . Генерация URL-адресов работает аналогично маршрутизации атрибутов , хотя концепции разные. При обычной маршрутизации:

  • Значения маршрута используются для расширения шаблона.
  • Значения маршрута для controllerи actionобычно появляются в этом шаблоне. Это работает, потому что URL-адреса, соответствующие маршрутизации, соответствуют соглашению.


В следующем примере используется маршрутизация атрибутов:

public class UrlGenerationAttrController : Controller
{
   [HttpGet("custom")]
   public IActionResult Source()
   {
       var url = Url.Action("Destination");
       return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
   }
   [HttpGet("custom/url/to/destination")]
   public IActionResult Destination()
   {
      return ControllerContext.MyDisplayRouteInfo();
   }
}


Действие Sourceв предыдущем коде генерирует custom/url/to/destination.

LinkGenerator был добавлен в ASP.NET Core 3.0 в качестве альтернативы IUrlHelper. LinkGeneratorпредлагает аналогичную, но более гибкую функциональность. Каждый метод on IUrlHelperтакже имеет соответствующее семейство методов on LinkGenerator.

Создание URL-адресов по имени действия
Url.Action , LinkGenerator.GetPathByAction и все связанные перегрузки предназначены для создания целевой конечной точки путем указания имени контроллера и имени действия.

При использовании Url.Actionтекущие значения маршрута для controllerи actionпредоставляются средой выполнения:

  • Значение controllerи actionявляются частью как окружающих значений, так и значений. Метод Url.Actionвсегда использует текущие значения actionи controllerи создает путь URL-адреса, который ведет к текущему действию.


Маршрутизация пытается использовать значения во внешних значениях для заполнения информации, которая не была предоставлена при создании URL-адреса. Рассмотрим маршрут, например, {a}/{b}/{c}/{d}с параметрами окружения { a = Alice, b = Bob, c = Carol, d = David }:

  • Маршрутизация имеет достаточно информации для создания URL-адреса без каких-либо дополнительных значений.
  • Маршрутизация имеет достаточно информации, потому что все параметры маршрута имеют значение.


Если { d = Donovan }добавлено значение:

  • Значение { d = David }игнорируется.
  • Сгенерированный URL-адрес: Alice/Bob/Carol/Donovan.


Предупреждение . URL-адреса являются иерархическими. В предыдущем примере, если значение { c = Cheryl }добавляется:

  • Оба значения { c = Carol, d = David }игнорируются.
  • Больше нет значения для dи создание URL завершается сбоем.
  • Требуемые значения cи dдолжны быть указаны для создания URL-адреса.


Вы можете ожидать решения этой проблемы с маршрутом по умолчанию {controller}/{action}/{id?}. На практике эта проблема возникает редко, поскольку Url.Actionвсегда явно указывает значение controllerи action.

Несколько перегрузок Url.Action используют объект значений маршрута для предоставления значений для параметров маршрута, отличных от controllerи action. Объект значений маршрута часто используется с id. Например, Url.Action("Buy", "Products", new { id = 17 }). Объект значений маршрута:

  • По соглашению обычно это объект анонимного типа.
  • Может быть IDictionary<>или POCO ).

Любые дополнительные значения маршрута, которые не соответствуют параметрам маршрута, помещаются в строку запроса.

public IActionResult Index()
{
   var url = Url.Action("Buy", "Products", new { id = 17, color = "red" });
   return Content(url!);
}


Предыдущий код генерирует /Products/Buy/17?color=red.

Следующий код генерирует абсолютный URL:

public IActionResult Index2()
{
   var url = Url.Action("Buy", "Products", new { id = 17 }, protocol: Request.Scheme);
   // Returns https://localhost:5001/Products/Buy/17
   return Content(url!);
}


Чтобы создать абсолютный URL-адрес, используйте один из следующих способов:

  • Перегрузка, которая принимает файл protocol. Например, предыдущий код.
  • LinkGenerator.GetUriByAction , который по умолчанию создает абсолютные URI.

Создание URL-адресов по маршруту
В предыдущем коде показано создание URL-адреса путем передачи имени контроллера и действия. IUrlHelperтакже предоставляет семейство методов Url.RouteUrl . Эти методы аналогичны Url.Action , но они не копируют текущие значения actionи controllerв значения маршрута. Наиболее распространенное использование Url.RouteUrl:

  • Указывает имя маршрута для создания URL-адреса.
  • Обычно не указывает контроллер или имя действия.
public class UrlGeneration2Controller : Controller
{
   [HttpGet("")]
   public IActionResult Source()
   {
       var url = Url.RouteUrl("Destination_Route");
       return ControllerContext.MyDisplayRouteInfo("", $" URL = {url}");
   }
   [HttpGet("custom/url/to/destination2", Name = "Destination_Route")]
   public IActionResult Destination()
   {
       return ControllerContext.MyDisplayRouteInfo();
   }


Следующий файл Razor создает HTML-ссылку на Destination_Route:

<h1>Test Links</h1>
<ul>
   <li><a href="@Url.RouteUrl("Destination_Route")">Test Destination_Route</a></li>
</ul>

Создание URL-адресов в HTML и Razor
IHtmlHelper предоставляет методы HtmlHelper Html.BeginForm и Html.ActionLink для создания элементов <form>и <a>соответственно. Эти методы используют метод Url.Action для создания URL-адреса и принимают аналогичные аргументы. Компаньоны Url.RouteUrlдля HtmlHelperесть Html.BeginRouteFormи Html.RouteLinkкоторые имеют аналогичный функционал.

TagHelpers генерирует URL-адреса через formTagHelper и <a>TagHelper. Оба они используются IUrlHelperдля их реализации. Дополнительные сведения см. в разделе Вспомогательные функции тегов в формах .

Внутренние представления IUrlHelperдоступны через Urlсвойство для любого специального создания URL-адресов, не охваченного выше.


Генерация URL в результатах действий
В предыдущих примерах показано использование IUrlHelperв контроллере. Наиболее распространенное использование в контроллере — создание URL-адреса как части результата действия.

Базовые классы ControllerBase и Controller предоставляют удобные методы для результатов действий, которые ссылаются на другое действие. Одним из типичных вариантов использования является перенаправление после принятия пользовательского ввода:

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int id, Customer customer)
{
   if (ModelState.IsValid)
   {
       // Update DB with new details.
       ViewData["Message"] = $"Successful edit of customer {id}";
       return RedirectToAction("Index");
   }
   return View(customer);
}


Методы фабрики результатов действий, такие как RedirectToAction и CreatedAtAction , следуют тому же шаблону, что и методы в IUrlHelper.


Особый случай для выделенных обычных маршрутов
Обычная маршрутизация может использовать специальный тип определения маршрута, называемый выделенным обычным маршрутом . В следующем примере названный маршрут blogявляется выделенным обычным маршрутом:

app.MapControllerRoute(name: "blog",
               pattern: "blog/{*article}",
               defaults: new { controller = "Blog", action = "Article" });
app.MapControllerRoute(name: "default",
              pattern: "{controller=Home}/{action=Index}/{id?}");

Используя предыдущие определения маршрута, Url.Action("Index", "Home")генерирует URL-путь /с использованием defaultмаршрута, но почему? Вы можете предположить, что значений маршрута { controller = Home, action = Index }будет достаточно для создания URL-адреса с использованием blog, и результатом будет /blog?action=Index&controller=Home.

Выделенные обычные маршруты основаны на особом поведении значений по умолчанию, которые не имеют соответствующего параметра маршрута, что предотвращает слишком жадный маршрут при создании URL-адресов. В этом случае значения по умолчанию равны { controller = Blog, action = Article }, и ни один из них controllerне actionотображается в качестве параметра маршрута. Когда маршрутизация выполняет генерацию URL, предоставленные значения должны соответствовать значениям по умолчанию. Генерация URL с помощью blogзавершается неудачно, поскольку значения { controller = Home, action = Index }не совпадают { controller = Blog, action = Article }. Затем маршрутизация возвращается к try default, что завершается успешно.


Области
Области — это функция MVC, используемая для организации связанных функций в группу как отдельную:

Пространство имен маршрутизации для действий контроллера.
Структура папок для представлений.
Использование областей позволяет приложению иметь несколько контроллеров с одинаковыми именами, если они имеют разные области. Использование областей создает иерархию для целей маршрутизации, добавляя еще один параметр маршрута, areaк controllerи action. В этом разделе обсуждается, как маршрутизация взаимодействует с областями. Подробную информацию о том, как области используются с представлениями, см. в разделе Области .

В следующем примере MVC настраивается для использования обычного маршрута по умолчанию и areaмаршрута для areanamed Blog:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{    
   app.UseExceptionHandler("/Home/Error");
   app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapAreaControllerRoute("blog_route", "Blog",
       "Manage/{controller}/{action}/{id?}");
app.MapControllerRoute("default_route", "{controller}/{action}/{id?}");
app.Run();


В предыдущем коде MapAreaControllerRoute вызывается для создания файла "blog_route". Второй параметр "Blog"— это имя области.

При сопоставлении URL-пути, например /Manage/Users/AddUser, "blog_route"маршрут генерирует значения маршрута { area = Blog, controller = Users, action = AddUser }. Значение areaмаршрута создается значением по умолчанию для area. Маршрут, созданный MapAreaControllerRouteэквивалентен следующему:

 

app.MapControllerRoute("blog_route", "Manage/{controller}/{action}/{id?}",
       defaults: new { area = "Blog" }, constraints: new { area = "Blog" });
app.MapControllerRoute("default_route", "{controller}/{action}/{id?}");


MapAreaControllerRouteсоздает маршрут, используя как значение по умолчанию, так и ограничение для areaиспользования предоставленного имени области, в данном случае Blog. Значение по умолчанию гарантирует, что маршрут всегда создает { area = Blog, ... }, ограничение требует значение { area = Blog, ... }для создания URL-адреса.

Обычная маршрутизация зависит от заказа. Как правило, маршруты с областями следует размещать раньше, поскольку они более специфичны, чем маршруты без зон.

В предыдущем примере значения маршрута { area = Blog, controller = Users, action = AddUser }соответствуют следующему действию:

using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace1
{
   [Area("Blog")]
   public class UsersController : Controller
   {
       // GET /manage/users/adduser
       public IActionResult AddUser()
       {
           var area = ControllerContext.ActionDescriptor.RouteValues["area"];
           var actionName = ControllerContext.ActionDescriptor.ActionName;
           var controllerName = ControllerContext.ActionDescriptor.ControllerName;
           return Content($"area name:{area}" +
               $" controller:{controllerName}  action name: {actionName}");
       }        
   }
}


Атрибут [Area] определяет контроллер как часть области. Этот контроллер находится в этом Blogрайоне. Контроллеры без [Area]атрибута не являются членами какой-либо области и не совпадают, когда areaзначение маршрута предоставляется маршрутизацией. В следующем примере только первый указанный контроллер может соответствовать значениям маршрута { area = Blog, controller = Users, action = AddUser }.

using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace1
{
   [Area("Blog")]
   public class UsersController : Controller
   {
       // GET /manage/users/adduser
       public IActionResult AddUser()
       {
           var area = ControllerContext.ActionDescriptor.RouteValues["area"];
           var actionName = ControllerContext.ActionDescriptor.ActionName;
           var controllerName = ControllerContext.ActionDescriptor.ControllerName;
           return Content($"area name:{area}" +
               $" controller:{controllerName}  action name: {actionName}");
       }        
   }
}
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace2
{
   // Matches { area = Zebra, controller = Users, action = AddUser }
   [Area("Zebra")]
   public class UsersController : Controller
   {
       // GET /zebra/users/adduser
       public IActionResult AddUser()
       {
           var area = ControllerContext.ActionDescriptor.RouteValues["area"];
           var actionName = ControllerContext.ActionDescriptor.ActionName;
           var controllerName = ControllerContext.ActionDescriptor.ControllerName;
           return Content($"area name:{area}" +
               $" controller:{controllerName}  action name: {actionName}");
       }        
   }
}
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace3
{
   // Matches { area = string.Empty, controller = Users, action = AddUser }
   // Matches { area = null, controller = Users, action = AddUser }
   // Matches { controller = Users, action = AddUser }
   public class UsersController : Controller
   {
       // GET /users/adduser
       public IActionResult AddUser()
       {
           var area = ControllerContext.ActionDescriptor.RouteValues["area"];
           var actionName = ControllerContext.ActionDescriptor.ActionName;
           var controllerName = ControllerContext.ActionDescriptor.ControllerName;
           return Content($"area name:{area}" +
               $" controller:{controllerName}  action name: {actionName}");
       }
   }
}


Для полноты здесь показано пространство имен каждого контроллера. Если бы предыдущие контроллеры использовали одно и то же пространство имен, возникла бы ошибка компилятора. Пространства имен классов не влияют на маршрутизацию MVC.

Первые два контроллера являются членами областей и совпадают только тогда, когда их соответствующее имя области предоставляется areaзначением маршрута. Третий контроллер не является членом какой-либо области и может соответствовать только тогда, когда areaмаршрутизация не предоставляет никакого значения.


С точки зрения соответствия no value отсутствие areaзначения равносильно тому, как если бы значение for areaбыло нулевым или пустой строкой.

При выполнении действия внутри области значение маршрута для areaдоступно в качестве внешнего значения для маршрутизации, используемой для создания URL-адреса. Это означает, что по умолчанию области действуют как фиксированные для создания URL-адресов, как показано в следующем примере.

app.MapAreaControllerRoute(name: "duck_route",
                                    areaName: "Duck",
                                    pattern: "Manage/{controller}/{action}/{id?}");
app.MapControllerRoute(name: "default",
                            pattern: "Manage/{controller=Home}/{action=Index}/{id?}");
using Microsoft.AspNetCore.Mvc;
namespace MyApp.Namespace4
{
   [Area("Duck")]
   public class UsersController : Controller
   {
       // GET /Manage/users/GenerateURLInArea
       public IActionResult GenerateURLInArea()
       {
           // Uses the 'ambient' value of area.
           var url = Url.Action("Index", "Home");
           // Returns /Manage/Home/Index
           return Content(url);
       }
       // GET /Manage/users/GenerateURLOutsideOfArea
       public IActionResult GenerateURLOutsideOfArea()
       {
           // Uses the empty value for area.
           var url = Url.Action("Index", "Home", new { area = "" });
           // Returns /Manage
           return Content(url);
       }
   }
}


Следующий код генерирует URL-адрес для /Zebra/Users/AddUser:

public class HomeController : Controller
{
   public IActionResult About()
   {
       var url = Url.Action("AddUser", "Users", new { Area = "Zebra" });
       return Content($"URL: {url}");
   }

Определение действия
Публичные методы контроллера, за исключением тех, у которых есть атрибут NonAction , являются действиями.

Образец кода

  • MyDisplayRouteInfo предоставляется пакетом NuGet Rick.Docs.Samples.RouteInfo и отображает сведения о маршруте.
  • Посмотреть или загрузить образец кода ( как скачать )

Диагностика отладки
Для подробного диагностического вывода маршрутизации установите Logging:LogLevel:Microsoftзначение Debug. В среде разработки установите уровень журнала в appsettings.Development.json:

{
 "Logging": {
   "LogLevel": {
     "Default": "Information",
     "Microsoft": "Debug",
     "Microsoft.Hosting.Lifetime": "Information"
   }
 }
}