Поделиться через


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

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

Примечание.

Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 8 этой статьи.

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

Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в статье о политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 8 этой статьи.

Внимание

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

В текущем выпуске см . версию .NET 8 этой статьи.

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

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

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

  • Соответствует URL-адресу, например /Products/Details/5

  • Извлекает значения { controller = Products, action = Details, id = 5 } маршрута путем маркеризации пути. Извлечение значений маршрута приводит к совпадению, если у приложения есть контроллер с именем ProductsController и действием Details :

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

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

  • /Products/Details/5 модель привязывает значение id = 5 для задания id параметра 5. Дополнительные сведения см. в статье "Привязка модели".

  • {controller=Home} определяется Home как значение по умолчанию controller.

  • {action=Index} определяется Index как значение по умолчанию action.

  • Символ?, который определяется id {id?} как необязательный.

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

  • Создает значения { controller = Home, action = Index }маршрута.

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

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

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

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

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

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

app.MapDefaultControllerRoute();

Заменяет:

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

Внимание

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

MapControllerRoute и MapAreaRoute :

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Например:

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

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

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

  • Путь URL-адреса /Products33/Edit/17
  • Маршрутизация данных { controller = Products33, action = Edit, id = 17 }.

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

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

Чтобы устранить правильный маршрут, выполните следующие действия.

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

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

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

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

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

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

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

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

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

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

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

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

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

RESTAPI должны использовать маршрутизацию атрибутов для моделирования функциональных возможностей приложения в виде набора ресурсов, в которых операции представлены 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 Not Found .
      [HttpGet("int/{id:int}")] // GET /api/test2/int/3
      public IActionResult GetIntProduct(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      
  • Действие GetInt2Product содержится {id} в шаблоне, но не ограничивает id значения, которые можно преобразовать в целое число. Запрос GET:/api/test2/int2/abc
    • Соответствует этому маршруту.
    • Привязка модели не может преобразовывать abc в целое число. Параметр id метода является целым числом.
    • Возвращает недопустимый запрос 400, так как привязка модели не удалось преобразовать abc в целое число.
      [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
      public IActionResult GetInt2Product(int id)
      {
          return ControllerContext.MyDisplayRouteInfo(id);
      }
      

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

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

    [HttpPost("/products3")]
    public IActionResult CreateProduct(MyProduct myProduct)
    {
        return ControllerContext.MyDisplayRouteInfo(myProduct.Name);
    }
}

Использование пути /products3URL-адреса:

  • Действие MyProductsController.ListProducts выполняется при выполнении GEThttp-команды.
  • Действие MyProductsController.CreateProduct выполняется при выполнении POSThttp-команды.

При создании 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] позволяет выполнять действие для ограничения поддерживаемых типов содержимого запросов. Дополнительные сведения см. в разделе "Определение поддерживаемых типов контента запроса" с помощью атрибута "Использование".

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

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

Имя маршрута

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Порядок маршрутов атрибутов

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

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

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

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

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

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

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

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

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

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

AmbiguousMatchException: The request matched multiple endpoints. Matches:

 WebMvcRouting.Controllers.HomeController.Index
 WebMvcRouting.Controllers.MyDemoController.MyIndex

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

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

В приведенном выше коде /home запускается конечная HomeController.Index точка. Чтобы добраться до MyDemoController.MyIndexзапроса, выполните запрос /home/MyIndex. Примечание.

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

См Razor . соглашения о маршрутах страниц и приложениях: порядок маршрутов для сведений о порядке маршрута с помощью Razor Pages.

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

Замена маркеров в шаблонах маршрутов [контроллер], [действие], [область]

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

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


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

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

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

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

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

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

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

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

[ApiController]
[Route("api/[controller]/[action]", Name = "[controller]_[action]")]
public abstract class MyBase2Controller : ControllerBase
{
}

public class Products11Controller : MyBase2Controller
{
    [HttpGet]                      // /api/products11/list
    public IActionResult List()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("{id}")]             //    /api/products11/edit/3
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Для сопоставления с литеральным разделителем замены токенов [ или ] его следует экранировать путем повтора символа ([[ или ]]).

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

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

using System.Text.RegularExpressions;

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

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

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

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

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

Зарегистрирован RouteTokenTransformerConvention в качестве параметра:

using Microsoft.AspNetCore.Mvc.ApplicationModels;

var builder = WebApplication.CreateBuilder(args);

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

var app = builder.Build();

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

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

app.UseRouting();

app.UseAuthorization();

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

app.Run();

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

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

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

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

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

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

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

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

Все ограничения маршрута http-команды реализуютсяIActionConstraint.

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

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

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

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

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

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

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

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

Настраиваемые атрибуты маршрута с помощью IRouteTemplateProvider

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

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

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

public class MyApiControllerAttribute : Attribute, IRouteTemplateProvider
{
    public string Template => "api/[controller]";
    public int? Order => 2;
    public string Name { get; set; } = string.Empty;
}

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

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

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

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

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

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

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

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

public class NamespaceRoutingConvention : Attribute, IControllerModelConvention
{
    private readonly string _baseNamespace;

    public NamespaceRoutingConvention(string baseNamespace)
    {
        _baseNamespace = baseNamespace;
    }

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

        var namespc = controller.ControllerType.Namespace;
        if (namespc == null)
            return;
        var template = new StringBuilder();
        template.Append(namespc, _baseNamespace.Length + 1,
                        namespc.Length - _baseNamespace.Length - 1);
        template.Replace('.', '/');
        template.Append("/[controller]/[action]/{id?}");

        foreach (var selector in controller.Selectors)
        {
            selector.AttributeRouteModel = new AttributeRouteModel()
            {
                Template = template.ToString()
            };
        }
    }
}

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

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

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

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

    public IActionResult List(int? id)
    {
        var path = Request.Path.Value;
        return Content($"List- Path:{path}");
    }
}

Метод NamespaceRoutingConvention.Apply:

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

Его NamespaceRoutingConvention можно применить в Program.cs:

using My.Application.Controllers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews(options =>
{
    options.Conventions.Add(
     new NamespaceRoutingConvention(typeof(HomeController).Namespace!));
});

var app = builder.Build();

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

using Microsoft.AspNetCore.Mvc;

namespace My.Application.Admin.Controllers
{
    public class UsersController : Controller
    {
        // GET /admin/controllers/users/index
        public IActionResult Index()
        {
            var fullname = typeof(UsersController).FullName;
            var template = 
                ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
            var path = Request.Path.Value;

            return Content($"Path: {path} fullname: {fullname}  template:{template}");
        }

        public IActionResult List(int? id)
        {
            var path = Request.Path.Value;
            return Content($"Path: {path} ID:{id}");
        }
    }
}

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

  • База namespaceMy.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/Destinationпути URL-адреса. Этот путь 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-адресов.
  • Требуемые значения c и d должны быть указаны для создания URL-адреса.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

<h1>Test Links</h1>

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

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

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 маршрута для именованного area Blog:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

var app = builder.Build();

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

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

app.UseRouting();

app.UseAuthorization();

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

app.Run();

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

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

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

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

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

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

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace1
{
    [Area("Blog")]
    public class UsersController : Controller
    {
        // GET /manage/users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }        
    }
}

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

using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace1
{
    [Area("Blog")]
    public class UsersController : Controller
    {
        // GET /manage/users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }        
    }
}
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace2
{
    // Matches { area = Zebra, controller = Users, action = AddUser }
    [Area("Zebra")]
    public class UsersController : Controller
    {
        // GET /zebra/users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }        
    }
}
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Namespace3
{
    // Matches { area = string.Empty, controller = Users, action = AddUser }
    // Matches { area = null, controller = Users, action = AddUser }
    // Matches { controller = Users, action = AddUser }
    public class UsersController : Controller
    {
        // GET /users/adduser
        public IActionResult AddUser()
        {
            var area = ControllerContext.ActionDescriptor.RouteValues["area"];
            var actionName = ControllerContext.ActionDescriptor.ActionName;
            var controllerName = ControllerContext.ActionDescriptor.ControllerName;

            return Content($"area name:{area}" +
                $" controller:{controllerName}  action name: {actionName}");
        }
    }
}

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

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

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

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

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

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

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

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

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

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

  • Соответствует URL-адресу, например /Products/Details/5

  • Извлекает значения { controller = Products, action = Details, id = 5 } маршрута путем маркеризации пути. Извлечение значений маршрута приводит к совпадению, если у приложения есть контроллер с именем ProductsController и действием Details :

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

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

  • /Products/Details/5 модель привязывает значение id = 5 для задания id параметра 5. Дополнительные сведения см. в статье "Привязка модели".

  • {controller=Home} определяется Home как значение по умолчанию controller.

  • {action=Index} определяется Index как значение по умолчанию action.

  • Символ?, который определяется id {id?} как необязательный.

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

  • Соответствует URL-пути /.

  • Создает значения { controller = Home, action = Index }маршрута.

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

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

endpoints.MapDefaultControllerRoute();

Заменяет:

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

Внимание

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

MapControllerRoute и MapAreaRoute :

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Например:

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

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

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

  • Путь URL-адреса /Products33/Edit/17
  • Маршрутизация данных { controller = Products33, action = Edit, id = 17 }.

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

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

Чтобы устранить правильный маршрут, выполните следующие действия.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    app.UseHttpsRedirection();

    app.UseRouting();

    app.UseAuthorization();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • action
  • area
  • controller
  • handler
  • page

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

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

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

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

  • page
  • using
  • namespace
  • inject
  • section
  • inherits
  • model
  • addTagHelper
  • removeTagHelper

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

Шаблоны глаголов HTTP

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

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

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

Маршрутизация атрибутов с атрибутами http-команд

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

[Route("api/[controller]")]
[ApiController]
public class Test2Controller : ControllerBase
{
    [HttpGet]   // GET /api/test2
    public IActionResult ListProducts()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("{id}")]   // GET /api/test2/xyz
    public IActionResult GetProduct(string id)
    {
       return ControllerContext.MyDisplayRouteInfo(id);
    }

    [HttpGet("int/{id:int}")] // GET /api/test2/int/3
    public IActionResult GetIntProduct(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    [HttpGet("int2/{id}")]  // GET /api/test2/int2/3
    public IActionResult GetInt2Product(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

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

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

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

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

    [HttpPost("/products3")]
    public IActionResult CreateProduct(MyProduct myProduct)
    {
        return ControllerContext.MyDisplayRouteInfo(myProduct.Name);
    }
}

Использование пути /products3URL-адреса:

  • Действие MyProductsController.ListProducts выполняется при выполнении GEThttp-команды.
  • Действие MyProductsController.CreateProduct выполняется при выполнении POSThttp-команды.

При создании 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] позволяет выполнять действие для ограничения поддерживаемых типов содержимого запросов. Дополнительные сведения см. в разделе "Определение поддерживаемых типов контента запроса" с помощью атрибута "Использование".

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

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

Имя маршрута

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Порядок маршрутов атрибутов

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

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

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

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

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

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

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

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

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

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

AmbiguousMatchException: The request matched multiple endpoints. Matches:

 WebMvcRouting.Controllers.HomeController.Index
 WebMvcRouting.Controllers.MyDemoController.MyIndex

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

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

В приведенном выше коде /home запускается конечная HomeController.Index точка. Чтобы добраться до MyDemoController.MyIndexзапроса, выполните запрос /home/MyIndex. Примечание.

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

См Razor . соглашения о маршрутах страниц и приложениях: порядок маршрутов для сведений о порядке маршрута с помощью Razor Pages.

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

Замена маркеров в шаблонах маршрутов [контроллер], [действие], [область]

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

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


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

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

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

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

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

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

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

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

[ApiController]
[Route("api/[controller]/[action]", Name = "[controller]_[action]")]
public abstract class MyBase2Controller : ControllerBase
{
}

public class Products11Controller : MyBase2Controller
{
    [HttpGet]                      // /api/products11/list
    public IActionResult List()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [HttpGet("{id}")]             //    /api/products11/edit/3
    public IActionResult Edit(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

Для сопоставления с литеральным разделителем замены токенов [ или ] его следует экранировать путем повтора символа ([[ или ]]).

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

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

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

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

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

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

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

RouteTokenTransformerConvention регистрируется в качестве параметра в ConfigureServices.

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

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

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

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

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

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

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

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

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

Все ограничения маршрута http-команды реализуютсяIActionConstraint.

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

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

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

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

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

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

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

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

Настраиваемые атрибуты маршрута с помощью IRouteTemplateProvider

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

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

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

public class MyApiControllerAttribute : Attribute, IRouteTemplateProvider
{
    public string Template => "api/[controller]";
    public int? Order => 2;
    public string Name { get; set; }
}

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

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

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

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

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

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

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

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

public class NamespaceRoutingConvention : Attribute, IControllerModelConvention
{
    private readonly string _baseNamespace;

    public NamespaceRoutingConvention(string baseNamespace)
    {
        _baseNamespace = baseNamespace;
    }

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

        var namespc = controller.ControllerType.Namespace;
        if (namespc == null)
            return;
        var template = new StringBuilder();
        template.Append(namespc, _baseNamespace.Length + 1,
                        namespc.Length - _baseNamespace.Length - 1);
        template.Replace('.', '/');
        template.Append("/[controller]/[action]/{id?}");

        foreach (var selector in controller.Selectors)
        {
            selector.AttributeRouteModel = new AttributeRouteModel()
            {
                Template = template.ToString()
            };
        }
    }
}

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

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

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

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

    public IActionResult List(int? id)
    {
        var path = Request.Path.Value;
        return Content($"List- Path:{path}");
    }
}

Метод NamespaceRoutingConvention.Apply:

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

Его NamespaceRoutingConvention можно применить в Startup.ConfigureServices:

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

        public IConfiguration Configuration { get; }

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

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

using Microsoft.AspNetCore.Mvc;

namespace My.Application.Admin.Controllers
{
    public class UsersController : Controller
    {
        // GET /admin/controllers/users/index
        public IActionResult Index()
        {
            var fullname = typeof(UsersController).FullName;
            var template = 
                ControllerContext.ActionDescriptor.AttributeRouteInfo?.Template;
            var path = Request.Path.Value;

            return Content($"Path: {path} fullname: {fullname}  template:{template}");
        }

        public IActionResult List(int? id)
        {
            var path = Request.Path.Value;
            return Content($"Path: {path} ID:{id}");
        }
    }
}

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

  • База namespaceMy.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/Destinationпути URL-адреса. Этот путь 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-адресов.
  • Требуемые значения c и d должны быть указаны для создания URL-адреса.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

<h1>Test Links</h1>

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

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

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 маршрута для именованного area Blog:

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