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

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

Примечание

Это не последняя версия этой статьи. Чтобы перейти на последнюю версию, используйте селектор версии ASP.NET Core в верхней части оглавлиния.
Выбор версии
Если селектор не отображается в узком окне браузера, расширьте окно или щелкните вертикальное многоточие (⋮) >Оглавление.
Селектор оглавлиния

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

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

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

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

  • Объясняет взаимодействие между MVC и маршрутизацией:
    • Как типичные приложения 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 route. Большинство приложений с контроллерами и представлениями используют шаблон маршрута, аналогичный маршруту 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 Model привязывает значение , чтобы присвоить 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, MapDefaultControllerRouteи MapAreaControllerRoute значения порядка автоматически назначаются их конечным точкам в зависимости от порядка, который они вызывают. Совпадения из маршрута, который отображается ранее, имеют более высокий приоритет. При маршрутизации на основе соглашений учитывается порядок. Как правило, маршруты с областями следует размещать раньше, так как они более специфичные, чем маршруты без области. Выделенные обычные маршруты с такими параметрами маршрута, как {*article} catch-all, могут сделать маршрут слишком жадным, а это означает, что он соответствует 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 запроса. Обеспечивает HttpPostAttributeEdit(int, Product) лучшее соответствие, чем Edit(int).

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

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

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

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

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

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

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

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

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

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

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

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

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

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

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

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

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

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult About(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

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

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

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

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

    [Route("Home/About")]
    [Route("Home/About/{id?}")]
    public IActionResult MyAbout(int? id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

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

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

    [Route("[controller]/[action]")]
    public IActionResult About()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

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

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

    public IActionResult About()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

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

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

Зарезервированные имена маршрутизации

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

  • 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. Клиенты API-интерфейсов REST должны знать, какие пути и HTTP-команды сопоставляются с конкретными логическими операциями.

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

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

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

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

  • Выполняется с URL-путем, например /products2/3
  • Не выполняется с URL-путем /products2.

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

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

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

Имя маршрута

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

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

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

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

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

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

Объединение маршрутов атрибутов

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

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

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

В предшествующем примере:

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

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

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

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

    [Route("About")]
    public IActionResult About()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

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

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 и преобразует значения параметров. Например, преобразователь пользовательских SlugifyParameterTransformerSubscriptionManagement параметров изменяет значение маршрута на subscription-management:

using System.Text.RegularExpressions;

public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
    public string? TransformOutbound(object? value)
    {
        if (value == null) { return null; }

        return Regex.Replace(value.ToString()!,
                             "([a-z])([A-Z])",
                             "$1-$2",
                             RegexOptions.CultureInvariant,
                             TimeSpan.FromMilliseconds(100)).ToLowerInvariant();
    }
}

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

  • Применяет преобразователь параметров ко всем маршрутам атрибута в приложении.
  • Настраивает значения токена маршрут атрибута при замене.
public class SubscriptionManagementController : Controller
{
    [HttpGet("[controller]/[action]")]
    public IActionResult ListAll()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

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

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

using Microsoft.AspNetCore.Mvc.ApplicationModels;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews(options =>
{
    options.Conventions.Add(new RouteTokenTransformerConvention(
                                 new SlugifyParameterTransformer()));
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

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

app.Run();

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

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

При использовании 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:

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

Можно 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.

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

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

Маршрутизация со специальными символами

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

[HttpGet("{id?}/name")]
public async Task<ActionResult<string>> GetName(string id)
{
    var todoItem = await _context.TodoItems.FindAsync(id);

    if (todoItem == null || todoItem.Name == null)
    {
        return NotFound();
    }

    return todoItem.Name;
}

Если string id содержит следующие закодированные значения, могут возникнуть непредвиденные результаты:

ASCII Encoded
/ %2F
+

Параметры маршрута не всегда декодируются по URL-адресу. Эта проблема может быть решена в будущем. Дополнительные сведения см. в этой проблеме на GitHub.

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

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

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

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

    [HttpGet("custom/url/to/destination")]
    public IActionResult Destination()
    {
       return ControllerContext.MyDisplayRouteInfo();
    }
}

Действие Source в приведенном выше коде custom/url/to/destinationсоздает .

LinkGeneratorдобавлен в ASP.NET Core 3.0 в качестве альтернативы IUrlHelper. LinkGenerator предлагает аналогичные, но более гибкие функции. Каждый метод в 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 .

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

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

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

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

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

Приведенный выше код создает /Products/Buy/17?color=red.

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

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

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

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

Создание URL-адресов по маршруту

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

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

    [HttpGet("custom/url/to/destination2", Name = "Destination_Route")]
    public IActionResult Destination()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

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

<h1>Test Links</h1>

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

Создание URL-адресов в HTML и Razor

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?}");
});

Внутри вызова UseEndpointsиспользуется MapControllerRoute для создания одного маршрута. Один маршрут называется default route. Большинство приложений с контроллерами и представлениями используют шаблон маршрута, аналогичный маршруту 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 Model привязывает значение 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:

endpoints.MapDefaultControllerRoute();

Заменяет:

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

Важно!

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

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

Обычная маршрутизация используется с контроллерами и представлениями. Маршрут 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, MapDefaultControllerRouteи MapAreaControllerRoute значения порядка автоматически назначаются их конечным точкам в зависимости от порядка, который они вызывают. Совпадения из маршрута, который отображается ранее, имеют более высокий приоритет. При маршрутизации на основе соглашений учитывается порядок. Как правило, маршруты с областями следует размещать раньше, так как они более специфичные, чем маршруты без области. Выделенные обычные маршруты с такими параметрами маршрута, как {*article} catch-all, могут сделать маршрут слишком жадным, а это означает, что он соответствует 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 запроса. Обеспечивает HttpPostAttributeEdit(int, Product) лучшее соответствие, чем Edit(int).

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

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

В приведенном выше коде вызывается внутри UseEndpoints для 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 метода — integer.
    • Возвращает ошибку 400 Bad Request , так как привязка модели не смогла преобразоваться 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 и преобразует значения параметров. Например, преобразователь пользовательских SlugifyParameterTransformerSubscriptionManagement параметров изменяет значение маршрута на 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 по Slug .

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

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

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

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

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

    [HttpGet("custom/url/to/destination")]
    public IActionResult Destination()
    {
       return ControllerContext.MyDisplayRouteInfo();
    }
}

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

LinkGeneratorдобавлен в ASP.NET Core 3.0 в качестве альтернативы IUrlHelper. LinkGenerator предлагает аналогичные, но более гибкие функции. Каждый метод в 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 .

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

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

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

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

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

Приведенный выше код создает /Products/Buy/17?color=red.

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

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

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

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

Создание URL-адресов по маршруту

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

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

    [HttpGet("custom/url/to/destination2", Name = "Destination_Route")]
    public IActionResult Destination()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

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

<h1>Test Links</h1>

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

Создание URL-адресов в HTML и Razor

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"
    }
  }
}