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

Авторы: Райан Новак (Ryan Nowak), Кирк Ларкин (Kirk Larkin) и Рик Андерсон (Rick Anderson)

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

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

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

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

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

Настройка обычного маршрута

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

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 . REST API должны использовать маршрутизацию атрибутов.

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-адреса / использует контроллеры и Index действия шаблона маршрута по умолчаниюHome. Url-путь /Home использует действие шаблона маршрута по умолчанию Index .

Универсальный метод MapDefaultControllerRoute:

app.MapDefaultControllerRoute();

Заменяет:

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

Важно!

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

Приложениям обычно не требуется вызывать UseRouting или UseEndpoints. WebApplicationBuilder настраивает конвейер ПО промежуточного слоя, который создает программу-оболочку для ПО промежуточного слоя, добавленное в Program.cs с использованием UseRouting и UseEndpoints. Подробные сведения см. в статье Маршрутизация в ASP.NET Core.

Маршрутизация на основе соглашений

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

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

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

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

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

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

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

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

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

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

Предупреждение

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

  • id задается 0 привязкой модели.
  • Сущность не найдена в сопоставлении id == 0базы данных.

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

Для большинства приложений следует выбрать базовую описательную схему маршрутизации таким образом, чтобы 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-адреса.

Предупреждение

В ASP.NET Core маршрутизация не будет:

  • Определите концепцию, называемую маршрутом. UseRouting добавляет соответствие маршрута в конвейер ПО промежуточного слоя. ПО UseRouting промежуточного слоя проверяет набор конечных точек, определенных в приложении, и выбирает наилучшее соответствие конечной точки на основе запроса.
  • Предоставьте гарантии порядка выполнения расширяемости, например IRouteConstraint или IActionConstraint.

Сведения о маршрутизации см. в справочном материале по маршрутизации.

Обычный порядок маршрутизации

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

Предупреждение

Соответствие параметра catch-all маршрутам может быть неправильным из-за ошибки в маршрутизации. Приложения, на работу которых влияет эта ошибка, обладают следующими характеристиками:

  • Маршрут catch-all, например {**slug}".
  • Маршрут catch-all не соответствует необходимым запросам.
  • После удаления других маршрутов маршрут catch-all начинает функционировать должным образом.

Ознакомьтесь с примерами 18677 и 16579, в которых встречается эта ошибка, на сайте GitHub.

Опциональное исправление для этой ошибки содержится в пакете SDK для .NET Core начиная с версии 3.1.301. Следующий код задает внутренний переключатель, исправляющий эту ошибку:

public static void Main(string[] args)
{
   AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior", 
                         true);
   CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.

Разрешение неоднозначных действий

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

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

Например:

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 запроса. Edit(int, Product) Делает HttpPostAttribute лучшее совпадение, чем Edit(int).

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

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

Обычные имена маршрутов

Строки и "default" в следующих примерах "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-адреса. Использование именованного маршрута упрощает создание 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 страниц:

  • 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-команд:

Шаблоны маршрутов

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

Маршрутизация атрибутов с атрибутами 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 Не найдено .
      [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. Клиенты интерфейсов REST API должны знать, какие пути и 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] атрибуты в предыдущем коде:

attribute Объединение с [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 Страниц.

В некоторых случаях возвращается ошибка 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 преобразователь параметров изменяет значение маршрута наsubscription-managementSubscriptionManagement:

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();

Сведения об определении Slug см. в веб-документации по MDN .

Предупреждение

При использовании System.Text.RegularExpressions для обработки ненадежных входных данных передайте время ожидания. Злонамеренный пользователь может предоставить входные данные для RegularExpressions, что делает возможными атаки типа "отказ в обслуживании". API платформы ASP.NET Core, использующие RegularExpressions, передают время ожидания.

Несколько маршрутов атрибутов

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

[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 переменной является строкой /UrlGeneration/DestinationURL-пути. Этот 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-адресов работает аналогично с маршрутизацией атрибутов, хотя основные понятия отличаются. При обычной маршрутизации:

  • Значения маршрута используются для расширения шаблона.
  • Значения маршрута и controlleraction обычно отображаются в этом шаблоне. Это работает, так как 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 предлагает аналогичные, но более гибкие функции. У каждого метода IUrlHelper есть также соответствующее семейство методов 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-адреса и не удается создать URL-адрес.
  • Требуемые значения c и d должны быть указаны для создания URL-адреса.

Возможно, вы столкнулись с этой проблемой с маршрутом {controller}/{action}/{id?}по умолчанию. Эта проблема редко возникает controller на практике, так как Url.Action всегда явно указывает и 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

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

Для формирования URL-адресов используются вспомогательные функции тегов form и <a>. Обе они реализуются с помощью интерфейса 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 }. После этого система маршрутизации выполнит попытку использовать маршрут default, которая завершится успешно.

Области

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

  • Маршрутизация пространства имен для действий контроллера.
  • Структура папок для представлений.

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

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

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 не предоставлено системой маршрутизации.

В плане сопоставления отсутствующих значений отсутствие значения area равносильно тому, как если значением area было бы NULL или пустая строка.

При выполнении действия внутри области значение 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 , являются действиями.

Пример кода

Отладка диагностики

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

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

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

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

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

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

  • Описывает взаимодействие между MVC и маршрутизацией:
    • Как типичные приложения MVC используют функции маршрутизации.
    • Охватывает оба варианта:
    • Дополнительные сведения о маршрутизации см. в разделе "Маршрутизация ".
  • Ссылается на систему маршрутизации по умолчанию, добавленную в ASP.NET Core 3.0, называемую маршрутизацией конечных точек. Для обеспечения совместимости можно использовать контроллеры с предыдущей версией маршрутизации. Инструкции см. в руководстве по миграции 2.2-3.0 . Справочные материалы по устаревшей системе маршрутизации см. в версии 2.2 этого документа .

Настройка обычного маршрута

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

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

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

Шаблон "{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 }маршрута.

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

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

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

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

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

Универсальный метод MapDefaultControllerRoute:

endpoints.MapDefaultControllerRoute();

Заменяет:

endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");

Важно!

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

Маршрутизация на основе соглашений

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

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

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

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

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

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

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

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

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

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

Предупреждение

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

  • id задается 0 привязкой модели.
  • Сущность не найдена в сопоставлении id == 0базы данных.

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

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

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

MapControllerRoute и MapAreaRoute :

  • Автоматическое назначение значения заказа конечным точкам в зависимости от того, в чем они вызываются.

Маршрутизация конечных точек в ASP.NET Core 3.0 и более поздних версий

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

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

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

Несколько обычных маршрутов

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

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(name: "blog",
                pattern: "blog/{*article}",
                defaults: new { controller = "Blog", action = "Article" });
    endpoints.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-адреса.

Предупреждение

В ASP.NET Core 3.0 и более поздних версий маршрутизация не поддерживает:

  • Определите концепцию, называемую маршрутом. UseRouting добавляет соответствие маршрута в конвейер ПО промежуточного слоя. UseRouting ПО промежуточного слоя проверяет набор конечных точек, определенных в приложении, и выбирает наилучшее соответствие конечной точки на основе запроса.
  • Предоставьте гарантии порядка выполнения расширяемости, как IRouteConstraint или IActionConstraint.

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

Обычный порядок маршрутизации

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

Предупреждение

Соответствие параметра catch-all маршрутам может быть неправильным из-за ошибки в маршрутизации. Приложения, на работу которых влияет эта ошибка, обладают следующими характеристиками:

  • Маршрут catch-all, например {**slug}".
  • Маршрут catch-all не соответствует необходимым запросам.
  • После удаления других маршрутов маршрут catch-all начинает функционировать должным образом.

Ознакомьтесь с примерами 18677 и 16579, в которых встречается эта ошибка, на сайте GitHub.

Опциональное исправление для этой ошибки содержится в пакете SDK для .NET Core начиная с версии 3.1.301. Следующий код задает внутренний переключатель, исправляющий эту ошибку:

public static void Main(string[] args)
{
   AppContext.SetSwitch("Microsoft.AspNetCore.Routing.UseCorrectCatchAllBehavior", 
                         true);
   CreateHostBuilder(args).Build().Run();
}
// Remaining code removed for brevity.

Разрешение неоднозначных действий

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

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

Например:

    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 запроса. Edit(int, Product) Делает HttpPostAttribute лучшее совпадение, чем Edit(int).

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

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

Обычные имена маршрутов

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

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

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

Имена маршрутов:

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

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

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

Маршрутизация атрибутов для REST API

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

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

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseHttpsRedirection();

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

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

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

  • 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 страниц:

  • 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-команд:

Шаблоны маршрутов

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

Маршрутизация атрибутов с атрибутами 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 Не найдено .
      [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. Клиенты интерфейсов REST API должны знать, какие пути и 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] атрибуты в предыдущем коде:

attribute Объединение с [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:

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 регистрируется в качестве параметра в ConfigureServices.

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
    {
        options.Conventions.Add(new RouteTokenTransformerConvention(
                                     new SlugifyParameterTransformer()));
    });
}

Сведения об определении Slug см. в веб-документации ПО MDN .

Предупреждение

При использовании System.Text.RegularExpressions для обработки ненадежных входных данных передайте время ожидания. Злонамеренный пользователь может предоставить входные данные для RegularExpressions, что делает возможными атаки типа "отказ в обслуживании". API платформы ASP.NET Core, использующие RegularExpressions, передают время ожидания.

Несколько маршрутов атрибутов

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

[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; }
}

[MyApiController]
[ApiController]
public class MyTestApiController : ControllerBase
{
    // GET /api/MyTestApi
    [HttpGet]
    public IActionResult Get()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

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

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

Модель приложения:

  • Объектная модель, созданная при запуске.
  • Содержит все метаданные, используемые 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 применяться в следующих функциях Startup.ConfigureServices:

namespace My.Application
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews(options =>
            {
                options.Conventions.Add(
                    new NamespaceRoutingConvention(typeof(Startup).Namespace));
            });
        }
        // Remaining code ommitted for brevity.

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

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 переменной является строкой /UrlGeneration/DestinationURL-пути. Этот 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-адресов работает аналогично с маршрутизацией атрибутов, хотя основные понятия отличаются. При обычной маршрутизации:

  • Значения маршрута используются для расширения шаблона.
  • Значения маршрута и controlleraction обычно отображаются в этом шаблоне. Это работает, так как 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 предлагает аналогичные, но более гибкие функции. У каждого метода IUrlHelper есть также соответствующее семейство методов 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-адреса и не удается создать URL-адрес.
  • Требуемые значения c и d должны быть указаны для создания URL-адреса.

Возможно, вы столкнулись с этой проблемой с маршрутом {controller}/{action}/{id?}по умолчанию. Эта проблема редко возникает controller на практике, так как Url.Action всегда явно указывает и 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

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

Для формирования URL-адресов используются вспомогательные функции тегов form и <a>. Обе они реализуются с помощью интерфейса 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.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(name: "blog",
                pattern: "blog/{*article}",
                defaults: new { controller = "Blog", action = "Article" });
    endpoints.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 }. После этого система маршрутизации выполнит попытку использовать маршрут default, которая завершится успешно.

Области

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

  • Пространство имен маршрутизации для действий контроллера.
  • Структура папок для представлений.

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

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

app.UseEndpoints(endpoints =>
{
    endpoints.MapAreaControllerRoute("blog_route", "Blog",
        "Manage/{controller}/{action}/{id?}");
    endpoints.MapControllerRoute("default_route", "{controller}/{action}/{id?}");
});

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

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

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute("blog_route", "Manage/{controller}/{action}/{id?}",
        defaults: new { area = "Blog" }, constraints: new { area = "Blog" });
    endpoints.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 не предоставлено системой маршрутизации.

В плане сопоставления отсутствующих значений отсутствие значения area равносильно тому, как если значением area было бы NULL или пустая строка.

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

app.UseEndpoints(endpoints =>
{
    endpoints.MapAreaControllerRoute(name: "duck_route", 
                                     areaName: "Duck",
                                     pattern: "Manage/{controller}/{action}/{id?}");
    endpoints.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 , являются действиями.

Пример кода

Отладка диагностики

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

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