Маршрутизация в ASP.NET Core

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

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

Приложения могут настраивать маршрутизацию с помощью следующего.

В этом документе представлены сведения о низкоуровневой маршрутизации ASP.NET Core. Дополнительные сведения о настройке маршрутизации

Система маршрутизации конечных точек, описанная в этом документе, применима к ASP.NET Core 3.0 и более поздних версий. Чтобы получить сведения о предыдущей системе маршрутизации на основе IRouter, выберите версию ASP.NET Core 2.1, используя один из следующих методов.

Просмотреть или скачать образец кода (как скачивать)

Примеры этого документа для загрузки включены в определенном классе Startup. Чтобы выполнить конкретный пример, измените Program.cs для вызова нужного класса Startup.

Основы маршрутизации

Все шаблоны ASP.NET Core включают маршрутизацию в созданном коде. Маршрутизация регистрируется в конвейере ПО промежуточного слоя в Startup.Configure.

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

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

    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    });
}

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

  • UseRouting добавляет соответствие маршрута в конвейер ПО промежуточного слоя. Это ПО промежуточного слоя обращается к набору конечных точек, определенных в приложении, и выбирает наиболее подходящее на основе запроса.
  • UseEndpoints добавляет выполнение конечной точки в конвейер ПО промежуточного слоя. Он запускает делегат, связанный с выбранной конечной точкой.

В предыдущем примере имеется один маршрут к конечной точке кода с помощью метода MapGet.

  • При отправке HTTP-запроса GET на корневой URL-адрес /:
    • Выполняется показанный делегат запроса.
    • В ответ HTTP записывается Hello World!. Корневой URL-адрес / по умолчанию — https://localhost:5001/.
  • Если метод запроса не является GET или если корневой URL-адрес не /, сопоставление маршрута не выполняется и возвращается сообщение об ошибке HTTP 404.

Конечная точка

Для определения конечной точки используется метод MapGet. Конечная точка — это то, что можно:

  • выбрать путем сопоставления URL-адреса и метода HTTP;
  • выполнить путем запуска делегата.

Конечные точки, которые могут быть сопоставлены и выполнены приложением, настраиваются в UseEndpoints. Например, MapGet, MapPost и аналогичные методы подключают делегаты запросов к системе маршрутизации. Для подключения функций платформы ASP.NET Core к системе маршрутизации можно использовать дополнительные методы.

Ниже представлен пример маршрутизации с более сложным шаблоном маршрута.

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("/hello/{name:alpha}", async context =>
    {
        var name = context.Request.RouteValues["name"];
        await context.Response.WriteAsync($"Hello {name}!");
    });
});

Строка /hello/{name:alpha} является шаблоном маршрута. Она используется для настройки способа сопоставления конечной точки. В этом случае шаблон соответствует следующим условиям.

  • URL-адрес, подобный /hello/Ryan
  • Любой URL-путь, начинающийся с /hello/,после которого следует набор буквенных символов. :alpha применяет ограничение маршрута, которое соответствует только буквенным символам. Ограничения маршрута описаны далее в этом документе.

Второй сегмент URL-пути, {name:alpha}:

  • привязан к параметру name;
  • записывается и сохраняется в HttpRequest.RouteValues.

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

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

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

    // Matches request to an endpoint.
    app.UseRouting();

    // Endpoint aware middleware. 
    // Middleware can use metadata from the matched endpoint.
    app.UseAuthentication();
    app.UseAuthorization();

    // Execute the matched endpoint.
    app.UseEndpoints(endpoints =>
    {
        // Configure the Health Check endpoint and require an authorized user.
        endpoints.MapHealthChecks("/healthz").RequireAuthorization();

        // Configure another endpoint, no authorization requirements.
        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    });
}

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

В предыдущем примере, показано то, как:

  • ПО промежуточного слоя для авторизации можно использовать с маршрутизацией;
  • можно использовать конечные точки для настройки режима авторизации.

При вызове MapHealthChecks добавляется конечная точка проверки работоспособности. Связывание RequireAuthorization с этим вызовом прикрепляет политику авторизации к конечной точке.

При вызове UseAuthentication и UseAuthorization добавляется ПО промежуточного слоя для проверки подлинности и авторизации. Это ПО промежуточного слоя размещается между методами UseRouting и UseEndpoints, чтобы оно могло:

  • просматривать, какая конечная точка выбрана методом UseRouting;
  • применять политику авторизации до отправки UseEndpoints на конечную точку.

Метаданные конечной точки

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

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

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

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

Определение конечной точки ASP.NET Core

Конечная точка ASP.NET Core

В следующем примере кода показано, как получить и проверить конечную точку, соответствующую текущему запросу.

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

    app.UseRouting();

    app.Use(next => context =>
    {
        var endpoint = context.GetEndpoint();
        if (endpoint is null)
        {
            return Task.CompletedTask;
        }
        
        Console.WriteLine($"Endpoint: {endpoint.DisplayName}");

        if (endpoint is RouteEndpoint routeEndpoint)
        {
            Console.WriteLine("Endpoint has route pattern: " +
                routeEndpoint.RoutePattern.RawText);
        }

        foreach (var metadata in endpoint.Metadata)
        {
            Console.WriteLine($"Endpoint has metadata: {metadata}");
        }

        return Task.CompletedTask;
    });

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    });
}

Конечную точку, если она выбрана, можно получить из HttpContext. Ее свойства можно проверить. Объекты конечных точек являются неизменяемыми, и их невозможно изменить после создания. Наиболее распространенным типом конечной точки является RouteEndpoint. RouteEndpoint содержит сведения, позволяющие системе маршрутизации выбрать эту конечную точку.

В приведенном выше коде app.Use настраивает встроенное ПО промежуточного слоя.

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

// Location 1: before routing runs, endpoint is always null here
app.Use(next => context =>
{
    Console.WriteLine($"1. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    return next(context);
});

app.UseRouting();

// Location 2: after routing runs, endpoint will be non-null if routing found a match
app.Use(next => context =>
{
    Console.WriteLine($"2. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    return next(context);
});

app.UseEndpoints(endpoints =>
{
    // Location 3: runs when this endpoint matches
    endpoints.MapGet("/", context =>
    {
        Console.WriteLine(
            $"3. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
        return Task.CompletedTask;
    }).WithDisplayName("Hello");
});

// Location 4: runs after UseEndpoints - will only run if there was no match
app.Use(next => context =>
{
    Console.WriteLine($"4. Endpoint: {context.GetEndpoint()?.DisplayName ?? "(null)"}");
    return next(context);
});

В предыдущем примере добавляются инструкции Console.WriteLine, которые показывают, выбрана ли конечная точка. Для ясности в примере указанной конечной точке / назначается отображаемое имя.

При выполнении этого кода с URL-адресом / отображается следующее.

1. Endpoint: (null)
2. Endpoint: Hello
3. Endpoint: Hello

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

1. Endpoint: (null)
2. Endpoint: (null)
4. Endpoint: (null)

В этом выводе показано следующее.

  • Перед вызовом UseRouting конечная точка всегда имеет значение NULL.
  • Если найдено совпадение, конечная точка не имеет значение NULL между методами UseRouting и UseEndpoints.
  • ПО промежуточного слоя UseEndpoints является терминальным при обнаружении соответствия. Определение терминального ПО промежуточного слоя приведено далее в этом документе.
  • ПО промежуточного слоя после метода UseEndpoints выполняется, только если совпадения не найдены.

ПО промежуточного слоя UseRouting использует метод SetEndpoint, чтобы присоединить конечную точку к текущему контексту. ПО промежуточного слоя UseRouting можно заменить на настраиваемую логику и по-прежнему использовать конечные точки. Конечные точки — это низкоуровневые примитивы, такие как ПО промежуточного слоя, которые не связаны с реализацией маршрутизации. В большинстве приложений метод UseRouting не требуется заменять настраиваемой логикой.

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

В следующем примере кода показано, как ПО промежуточного слоя может влиять на маршрутизацию или реагировать на нее.

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

        // Location 1: Before routing runs. Can influence request before routing runs.
        app.UseHttpMethodOverride();

        app.UseRouting();

        // Location 2: After routing runs. Middleware can match based on metadata.
        app.Use(next => context =>
        {
            var endpoint = context.GetEndpoint();
            if (endpoint?.Metadata.GetMetadata<AuditPolicyAttribute>()?.NeedsAudit
                                                                            == true)
            {
                Console.WriteLine($"ACCESS TO SENSITIVE DATA AT: {DateTime.UtcNow}");
            }

            return next(context);
        });

        app.UseEndpoints(endpoints =>
        {         
            endpoints.MapGet("/", async context =>
            {
                await context.Response.WriteAsync("Hello world!");
            });

            // Using metadata to configure the audit policy.
            endpoints.MapGet("/sensitive", async context =>
            {
                await context.Response.WriteAsync("sensitive data");
            })
            .WithMetadata(new AuditPolicyAttribute(needsAudit: true));
        });

    } 
}

public class AuditPolicyAttribute : Attribute
{
    public AuditPolicyAttribute(bool needsAudit)
    {
        NeedsAudit = needsAudit;
    }

    public bool NeedsAudit { get; }
}

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

  • ПО промежуточного слоя может выполняться до UseRouting для изменения данных, с которыми взаимодействует маршрутизация.
    • Обычно ПО промежуточного слоя, отображаемое перед маршрутизацией, изменяет некоторое свойство запроса, например UseRewriter, UseHttpMethodOverride или UsePathBase.
  • ПО промежуточного слоя может выполняться между UseRouting и UseEndpoints для обработки результатов маршрутизации до выполнения конечной точки.
    • ПО промежуточного слоя, которое выполняется между UseRouting и UseEndpoints:
      • Обычно проверяет метаданные для получения представления о конечных точках.
      • Зачастую принимает решения по обеспечению безопасности, как это делается методами UseAuthorization и UseCors.
    • Сочетание ПО промежуточного слоя и метаданных позволяет настраивать политики для каждой конечной точки.

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

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

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

Метаданные политики аудита AuditPolicyAttribute определены как Attribute, чтобы их было проще использовать в платформах на основе классов, таких как контроллеры и SignalR. При использовании маршрута к коду:

  • Метаданные присоединяются к API-интерфейсу построителя.
  • При создании конечных точек платформы на основе классов включают все атрибуты в соответствующем методе и классе.

Типы метаданных рекомендуется определить либо как интерфейсы, либо как атрибуты. Интерфейсы и атрибуты допускают повторное использование кода. Система метаданных является гибкой и не накладывает никаких ограничений.

Сравнение терминального ПО промежуточного слоя и маршрутизации

В следующем примере кода использование ПО промежуточного слоя сравнивается с маршрутизацией.

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

    // Approach 1: Writing a terminal middleware.
    app.Use(next => async context =>
    {
        if (context.Request.Path == "/")
        {
            await context.Response.WriteAsync("Hello terminal middleware!");
            return;
        }

        await next(context);
    });

    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        // Approach 2: Using routing.
        endpoints.MapGet("/Movie", async context =>
        {
            await context.Response.WriteAsync("Hello routing!");
        });
    });
}

Стиль ПО промежуточного слоя, который показан в разделе Approach 1:, — терминальное ПО промежуточного слоя. ПО промежуточного слоя называется терминальным, поскольку выполняет операцию сопоставления.

  • Операция сопоставления в предыдущем примере — Path == "/" для ПО промежуточного слоя и Path == "/Movie" для маршрутизации.
  • Если сопоставление выполнено успешно, оно выполняет некоторые функции и возвращает результат, а не вызывает ПО промежуточного слоя next.

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

Сравнение терминального ПО промежуточного слоя и маршрутизации

  • Оба подхода позволяют завершать конвейер обработки:
    • ПО промежуточного слоя завершает конвейер, возвращая вместо вызова next.
    • Конечные точки всегда являются терминальными.
  • Терминальное ПО промежуточного слоя позволяет размещать ПО промежуточного слоя в произвольном месте конвейера.
    • Конечные точки выполняются в позиции UseEndpoints.
  • Терминальное ПО промежуточного слоя позволяет произвольному коду проверять соответствие ПО промежуточного слоя.
    • Настраиваемый код сопоставления маршрутов может быть подробным и сложным для корректной записи.
    • Маршрутизация обеспечивает простые решения для обычных приложений. Большинству приложений не требуется настраиваемый код сопоставления маршрутов.
  • Интерфейс конечных точек с ПО промежуточного слоя, например UseAuthorization и UseCors.
    • Использование терминального ПО промежуточного слоя с UseAuthorization или UseCors требует взаимодействия вручную с системой авторизации.

Конечная точка определяет и то, и другое:

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

Терминальное ПО промежуточного слоя может быть эффективным средством, однако может потребоваться:

  • значительный объем кода и тестирования;
  • интеграция вручную с другими системами для достижения желаемого уровня гибкости.

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

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

  • Напишите метод расширения в IEndpointRouteBuilder.
  • Создайте вложенный конвейер ПО промежуточного слоя с помощью CreateApplicationBuilder.
  • Присоедините ПО промежуточного слоя к новому конвейеру. В этом случае — UseHealthChecks.
  • Build конвейер ПО промежуточного слоя в RequestDelegate.
  • Вызовите Map и укажите новый конвейер ПО промежуточного слоя.
  • Верните объект построителя, предоставленного Map, из метода расширения.

В следующем коде показано использование MapHealthChecks.

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

    // Matches request to an endpoint.
    app.UseRouting();

    // Endpoint aware middleware. 
    // Middleware can use metadata from the matched endpoint.
    app.UseAuthentication();
    app.UseAuthorization();

    // Execute the matched endpoint.
    app.UseEndpoints(endpoints =>
    {
        // Configure the Health Check endpoint and require an authorized user.
        endpoints.MapHealthChecks("/healthz").RequireAuthorization();

        // Configure another endpoint, no authorization requirements.
        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    });
}

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

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

Соответствие URL-адресов

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

При выполнении ПО промежуточного слоя маршрутизации оно задает конечную точку (Endpoint) и значения маршрута для функции запроса в HttpContext из текущего запроса.

  • Вызов HttpContext.GetEndpoint получает конечную точку.
  • HttpRequest.RouteValues получает коллекцию значений маршрута.

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

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

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

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

Для обеспечения обратной совместимости, когда выполняются контроллер или делегат конечной точки Razor Pages, свойствам RouteContext.RouteData присваиваются значения с учетом уже выполненной на текущий момент обработки.

В следующем выпуске тип RouteContext будет помечен как устаревший.

  • Перенесите RouteData.Values в HttpRequest.RouteValues.
  • Перенесите RouteData.DataTokens, чтобы получить IDataTokensMetadata из метаданных конечной точки.

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

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

Список конечных точек определяется по приоритету в соответствии со следующим.

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

Приоритет маршрута вычисляется на основе более определенного шаблона маршрута, которому назначается более высокий приоритет. Например, рассмотрим шаблоны /hello и /{message}.

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

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

Ввиду различных типов расширяемости, предоставляемых службой маршрутизации, система маршрутизации не может заранее вычислить неоднозначные маршруты. Рассмотрим в качестве примера шаблоны маршрутов /{message:alpha} и /{message:int}.

  • Ограничение alpha соответствует только буквенным символам.
  • Ограничение int соответствует только числам.
  • Эти шаблоны имеют одинаковый приоритет маршрута, однако не существует одного URL-адреса, по которому они совпадают.
  • Если система маршрутизации сообщила об ошибке неоднозначности при запуске, это означает, что она заблокировала этот допустимый вариант использования.

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

Порядок операций в UseEndpoints не влияет на поведение маршрутизации, за одним исключением. MapControllerRoute и MapAreaRoute автоматически присваивают значение порядка своим конечным точкам в соответствии с порядком их вызова. Это имитирует поведение контроллеров без системы маршрутизации в долгосрочной перспективе, предоставляя те же гарантии, что и в старых реализациях маршрутизации.

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

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

Приоритет шаблонов маршрутов и порядок выбора конечных точек

Приоритет шаблонов маршрутов — это система, которая назначает каждому шаблону маршрута значение в зависимости от того, насколько он является конкретным. Приоритет шаблона маршрута

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

Например, рассмотрим шаблоны /Products/List и /Products/{id}. Разумно предположить, что для URL-пути /Products/List /Products/List является лучшим соответствием, чем /Products/{id}. Литеральный сегмент /List считается более приоритетным, чем сегмент параметров /{id}.

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

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

Справка по точным значениям приведена в исходном коде в GitHub.

Основные понятия формирования URL-адресов

Формирование URL-адреса

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

Маршрутизация конечных точек включает в себя API генератора ссылок (LinkGenerator). LinkGenerator — это одноэлементная служба, доступная в DI. API LinkGenerator можно использовать вне контекста выполнения запроса. Mvc.IUrlHelper и сценарии, которые зависят от IUrlHelper, такие как вспомогательные функции тегов, вспомогательные методы HTML и результаты действий, используют API LinkGenerator для предоставления возможностей создания ссылок.

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

Генератор ссылок может установить связь с контроллерами и Razor Pages с помощью следующих методов расширения.

Перегрузка этих методов принимает аргументы, которые включают HttpContext. Эти методы являются функциональными эквивалентами Url.Action и Url.Page, но предлагают дополнительную гибкость и параметры.

Методы GetPath* наиболее схожи с Url.Action и Url.Page в том, что создают URI, содержащий абсолютный путь. Методы GetUri* всегда создают абсолютный URI, содержащий схему и узел. Методы, которые принимают HttpContext, создают URI в контексте выполнения запроса. Используются значения окружения маршрута, базовый URL-адрес, схема и узел из выполняющегося запроса, если не указано иное.

LinkGenerator вызывается с адресом. Создание URI происходит в два этапа:

  1. Адрес привязан к списку конечных точек, соответствующих адресу.
  2. RoutePattern конечной точки вычисляется, пока не будет найден шаблон маршрута, который соответствует предоставленным значениям. Полученный результат объединяется с другими частями URI, предоставленными генератору ссылок и возвращенными.

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

Метод расширения Описание
GetPathByAddress Создает URI с абсолютным путем на основе предоставленных значений.
GetUriByAddress Создает абсолютный URI на основе предоставленных значений.

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

Обратите внимание на следующие последствия вызова методов LinkGenerator:

  • Используйте методы расширения GetUri* с осторожностью в конфигурации приложения, которая не проверяет заголовок входящих запросов Host. Если не проверить заголовок входящих запросов Host, входные данные в запросе без доверия могут отправляться обратно клиенту в URI в представлении или на странице. Рекомендуется, чтобы все рабочие приложения настраивали свой сервер на проверку заголовка Host относительно известных допустимых значений.

  • Используйте LinkGenerator с осторожностью в ПО промежуточного слоя в сочетании с Map или MapWhen. Map* изменяет базовый путь выполняющегося запроса, что влияет на выходные данные создания ссылки. Все API LinkGenerator разрешают указание базового пути. Укажите пустой базовый путь для отмены влияния Map* на создание ссылок.

Пример ПО промежуточного слоя

В следующем примере ПО промежуточного слоя использует API LinkGenerator, чтобы создать ссылку на метод действия, который перечисляет хранимые продукты. Использование генератора ссылок путем его внедрения в класс и вызова GenerateLink доступно для любого класса в приложении.

public class ProductsLinkMiddleware
{
    private readonly LinkGenerator _linkGenerator;

    public ProductsLinkMiddleware(RequestDelegate next, LinkGenerator linkGenerator)
    {
        _linkGenerator = linkGenerator;
    }

    public async Task InvokeAsync(HttpContext httpContext)
    {
        var url = _linkGenerator.GetPathByAction("ListProducts", "Store");

        httpContext.Response.ContentType = "text/plain";

        await httpContext.Response.WriteAsync($"Go to {url} to see our products.");
    }
}

Справочник по шаблону маршрута

Токены в фигурных скобках ({}) определяют параметры маршрута, которые будут привязаны при совпадении маршрута. В сегменте маршрута можно определить несколько параметров маршрута, однако они должны разделяться литеральным значением. Например, {controller=Home}{action=Index} будет недопустимым маршрутом, так как между {controller} и {action} нет литерального значения. Параметрам маршрута должны быть присвоены имена, и для них могут быть определены дополнительные атрибуты.

Весь текст, кроме параметров маршрута (например, {id}) и разделителя пути /, должен соответствовать тексту в URL-адресе. Сопоставление текста производится без учета регистра на основе декодированного представления пути URL-адреса. Для сопоставления с литеральным разделителем параметров маршрута ({ или }) разделитель следует экранировать путем повтора символа. Например, {{ или }}.

Звездочка * или двойная звездочка **:

  • Можно использовать в качестве префикса параметра маршрута для привязки к остальной части URI.
  • Такие параметры называются универсальными. Например, blog/{**slug}:
    • Соответствует любому URI, который начинается с /blog и имеет любое значение после него.
    • Значение после /blog присваивается значению динамического маршрута.

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

Соответствие параметра 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.

Универсальные параметры также могут соответствовать пустой строке.

Универсальный параметр экранирует соответствующие символы, если маршрут использует для формирования URL-адрес, включая символы разделителей пути (/). Например, маршрут foo/{*path} со значениями маршрутов { path = "my/path" } формирует foo/my%2Fpath. Обратите внимание на экранированный знак косой черты. В качестве символов разделителя кругового пути используйте префикс параметра маршрута **. Маршрут foo/{**path} с { path = "my/path" } формирует foo/my/path.

Шаблоны URL-адресов, которые пытаются получить имя файла с необязательным расширением, имеют свои особенности. Например, рассмотрим шаблон files/{filename}.{ext?}. Когда значения для filename и ext существуют, заполняются оба значения. Если в URL-адресе есть только значение для filename, маршрут совпадает, так как точка в конце (.) является необязательной. Следующие URL-адреса соответствуют этому маршруту:

  • /files/myFile.txt
  • /files/myFile

Параметры маршрута могут иметь значения по умолчанию. Они указываются после имени параметра и знака равенства (=). Например, {controller=Home} определяет Home в качестве значения по умолчанию для controller. Значение по умолчанию используется, если для параметра нет значения в URL-адресе. Параметры маршрута могут быть необязательными, для этого необходимо добавить вопросительный знак (?) в конец имени параметра. Например, id?. Разница между необязательными значениями и параметрами маршрута по умолчанию

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

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

Имя и аргументы ограничения передаются в службу IInlineConstraintResolver для создания экземпляра интерфейса IRouteConstraint, который будет использоваться при обработке URL-адреса. Например, в шаблоне маршрута blog/{article:minlength(10)} определяется ограничение minlength с аргументом 10. Более подробное описание ограничений маршрутов и список ограничений, предоставляемых платформой, см. в разделе Справочник по ограничениям маршрутов.

Параметры маршрута также могут иметь преобразователи параметров, которые преобразуют значение параметра при создании ссылок и сопоставлении действий и страниц с URL-адресами. Как и ограничения, преобразователи параметров можно включать в параметр маршрута, добавив двоеточие (:) и имя преобразователя после имени параметра маршрута. Например, шаблон маршрута blog/{article:slugify} задает преобразователь slugify. Дополнительные сведения о преобразователях параметров см. в разделе Справочник по преобразователям параметров.

В приведенной ниже таблице показаны некоторые примеры шаблонов маршрутов и их поведение.

Шаблон маршрута Пример соответствующего URI URI запроса…
hello /hello Соответствует только одному пути /hello.
{Page=Home} / Соответствует и задает для параметра Page значение Home.
{Page=Home} /Contact Соответствует и задает для параметра Page значение Contact.
{controller}/{action}/{id?} /Products/List Сопоставляется с контроллером Products и действием List.
{controller}/{action}/{id?} /Products/Details/123 Сопоставляется с контроллером Products и действием Details (id имеет значение 123).
{controller=Home}/{action=Index}/{id?} / Сопоставляется с контроллером Home и методом Index. id не учитывается.
{controller=Home}/{action=Index}/{id?} /Products Сопоставляется с контроллером Products и методом Index. id не учитывается.

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

Сложные сегменты

Сложные сегменты обрабатываются путем нежадного сопоставления разделителей литералов справа налево. Например, [Route("/a{b}c{d}")] является сложным сегментом. Сложные сегменты работают определенным способом, который должен быть понятен для их успешного использования. В примере в этом разделе показано, почему сложные сегменты действительно хорошо работают только в том случае, если текст разделителя отсутствует в значениях параметров. Для более сложных случаев требуется использовать регулярное выражение, а затем вручную извлечь значения.

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

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

Это сводка действий, выполняемых маршрутизацией с использованием шаблона /a{b}c{d} и URL-пути /abcd. | используется для визуализации принципа работы алгоритма.

  • Первый литерал, справа налево — c. Таким образом, поиск /abcd выполняется справа, после чего находится /ab|c|d.
  • Все, что находится справа (d), теперь сопоставляется с параметром маршрута {d}.
  • Следующий литерал, справа налево — a. Поэтому поиск /ab|c|d начинается с того места, где мы остановились, после чего находится a в /|a|b|c|d.
  • Значение справа (b) теперь сопоставляется с параметром маршрута {b}.
  • Больше не осталось текста и шаблонов маршрута, поэтому это считается совпадением.

Ниже приведен пример отрицательного результата с использованием того же шаблона /a{b}c{d} и URL-пути /aabcd. | используется для визуализации принципа работы алгоритма: Это не совпадение, что объясняется тем же алгоритмом.

  • Первый литерал, справа налево — c. Таким образом, поиск /aabcd выполняется справа, после чего находится /aab|c|d.
  • Все, что находится справа (d), теперь сопоставляется с параметром маршрута {d}.
  • Следующий литерал, справа налево — a. Поэтому поиск /aab|c|d начинается с того места, где мы остановились, после чего находится a в /a|a|b|c|d.
  • Значение справа (b) теперь сопоставляется с параметром маршрута {b}.
  • На этом этапе имеется оставшийся текст a, однако больше нет шаблонов маршрутов для синтаксического анализа, поэтому это не является совпадением.

Поскольку алгоритм сопоставления нежадный:

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

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

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

Справочник по ограничениям маршрутов

Ограничения маршрута применяются, когда найдено соответствие входящему URL-адресу и путь URL-адреса был разобран на значения маршрута. Как правило, ограничения маршрута служат для проверки значения маршрута, связанного посредством шаблона маршрута, и принятия решения касательно того, является ли значение приемлемым (истина или ложь). Некоторые ограничения маршрута используют данные, не относящиеся к значению маршрута, для определения возможности маршрутизации запроса. Например, HttpMethodRouteConstraint может принимать или отклонять запрос в зависимости от HTTP-команды. Ограничения используются в маршрутизации запросов и создании ссылок.

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

Не используйте ограничения для проверки входных данных. Если для проверки входных данных используются ограничения, недопустимые входные данные приводят к ошибке 404 ("Не найдено"). Недопустимые входные данные должны привести к ошибке 400 ("Неверный запрос") с соответствующим сообщением об ошибке. Ограничения маршрутов следует использовать для разрешения неоднозначности похожих маршрутов, а не для проверки входных данных определенного маршрута.

В приведенной ниже таблице показаны примеры ограничения маршрутов и их ожидаемое поведение.

ограничение Пример Примеры совпадений Примечания
int {id:int} 123456789, -123456789 Соответствует любому целому числу
bool {active:bool} true, FALSE Соответствует true или false. Без учета регистра
datetime {dob:datetime} 2016-12-31, 2016-12-31 7:32pm Соответствует допустимому значению DateTime для инвариантного языка и региональных параметров. См. предупреждение выше.
decimal {price:decimal} 49.99, -1,000.01 Соответствует допустимому значению decimal для инвариантного языка и региональных параметров. См. предупреждение выше.
double {weight:double} 1.234, -1,001.01e8 Соответствует допустимому значению double для инвариантного языка и региональных параметров. См. предупреждение выше.
float {weight:float} 1.234, -1,001.01e8 Соответствует допустимому значению float для инвариантного языка и региональных параметров. См. предупреждение выше.
guid {id:guid} CD2C1638-1638-72D5-1638-DEADBEEF1638 Соответствует допустимому значению Guid
long {ticks:long} 123456789, -123456789 Соответствует допустимому значению long
minlength(value) {username:minlength(4)} Rick Строка должна содержать не менее 4 символов
maxlength(value) {filename:maxlength(8)} MyFile Строка должна содержать не более 8 символов
length(length) {filename:length(12)} somefile.txt Длина строки должна составлять ровно 12 символов
length(min,max) {filename:length(8,16)} somefile.txt Строка должна содержать от 8 до 16 символов
min(value) {age:min(18)} 19 Целочисленное значение не меньше 18
max(value) {age:max(120)} 91 Целочисленное значение не больше 120
range(min,max) {age:range(18,120)} 91 Целочисленное значение от 18 до 120
alpha {name:alpha} Rick Строка должна состоять из одной буквы или нескольких (a-z) без учета регистра.
regex(expression) {ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} 123-45-6789 Строка должна соответствовать регулярному выражению. См. советы по определению регулярного выражения.
required {name:required} Rick Определяет обязательное наличие значения, не относящегося к параметру, во время формирования URL-адреса

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

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

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

[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }

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

Ограничения маршрута, которые проверяют URL-адрес и могут быть преобразованы в тип CLR, всегда используют инвариантный язык и региональные параметры. Например, преобразование в тип CLR int или DateTime. Эти ограничения предполагают, что для URL-адреса не предусмотрена локализация. Предоставляемые платформой ограничения маршрутов не изменяют значения, хранящиеся в значениях маршрута. Все значения маршрута, переданные из URL-адреса, сохраняются как строки. Например, ограничение float пытается преобразовать значение маршрута в число с плавающей запятой, но преобразованное значение служит только для проверки возможности такого преобразования.

Регулярные выражения в ограничениях

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

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

Регулярные выражения могут быть определены как встроенные ограничения с помощью ограничения маршрута regex(...). Методы в семействе MapControllerRoute также принимают объектный литерал ограничений. При использовании этой формы строковые значения будут интерпретироваться как регулярные выражения.

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

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("{message:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)}",
        context => 
        {
            return context.Response.WriteAsync("inline-constraint match");
        });
 });

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

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "people",
        pattern: "People/{ssn}",
        constraints: new { ssn = "^\\d{3}-\\d{2}-\\d{4}$", },
        defaults: new { controller = "People", action = "List", });
});

В платформе ASP.NET Core в конструктор регулярных выражений добавляются члены RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant. Описание этих членов см. в разделе RegexOptions.

В регулярных выражениях применяются разделители и токены, аналогичные используемым функцией маршрутизации и в языке C#. Токены регулярного выражения должны быть экранированы. Чтобы использовать регулярное выражение ^\d{3}-\d{2}-\d{4}$ во встроенном ограничении, используйте один из следующих способов.

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

Регулярное выражение Экранированное регулярное выражение
^\d{3}-\d{2}-\d{4}$ ^\\d{{3}}-\\d{{2}}-\\d{{4}}$
^[a-z]{2}$ ^[[a-z]]{{2}}$

Регулярные выражения, используемые при маршрутизации, часто начинаются с символа ^ и соответствуют начальной позиции строки. Выражения часто заканчиваются символом $ и соответствуют концу строки. Благодаря символам ^ и $ регулярное выражение сопоставляется со всем значением параметра маршрута. Если символы ^ и $ отсутствуют, регулярное выражение сопоставляется с любой подстрокой внутри строки, что обычно нежелательно. В таблице ниже представлен ряд примеров и объясняются причины соответствия или несоответствия.

Выражение Строка Соответствие Добавление примечаний
[a-z]{2} hello Да Соответствие подстроки
[a-z]{2} 123abc456 Да Соответствие подстроки
[a-z]{2} mz Да Соответствует выражению
[a-z]{2} MZ Да Без учета регистра
^[a-z]{2}$ hello Нет См. замечания, касающиеся символов ^ и $, выше
^[a-z]{2}$ 123abc456 Нет См. замечания, касающиеся символов ^ и $, выше

Дополнительные сведения о синтаксисе регулярных выражений см. в статье Регулярные выражения в .NET Framework.

Чтобы ограничить возможные значения параметра набором известных значений, используйте регулярное выражение. Например, при использовании выражения {action:regex(^(list|get|create)$)} значение маршрута action будет соответствовать только list, get или create. При передаче в словарь ограничений строка ^(list|get|create)$ будет эквивалентной. Ограничения, которые передаются в словарь ограничений и не соответствуют одному из известных ограничений, также рассматриваются как регулярные выражения. Ограничения, которые передаются в шаблоне и не соответствуют одному из известных ограничений, не рассматриваются как регулярные выражения.

Пользовательские ограничения маршрутов

Пользовательские ограничения маршрутов можно создать путем внедрения интерфейса IRouteConstraint. Интерфейс IRouteConstraint содержит метод, Match, который возвращает true, если ограничение удовлетворяется, и false — если нет.

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

В папке ASP.NET Core Constraints приведены хорошие примеры создания ограничений. Например, GuidRouteConstraint.

Чтобы применить пользовательский метод IRouteConstraint, тип ограничения маршрута необходимо зарегистрировать с помощью ConstraintMap приложения в контейнере службы. Объект ConstraintMap — это словарь, который сопоставляет ключи ограничений пути с реализациями IRouteConstraint, которые проверяют эти ограничения. ConstraintMap приложения можно преобразовать в Startup.ConfigureServices как часть вызова services.AddRouting или путем настройки RouteOptions непосредственно с помощью services.Configure<RouteOptions>. Пример:

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

    services.AddRouting(options =>
    {
        options.ConstraintMap.Add("customName", typeof(MyCustomConstraint));
    });
}

Предыдущее ограничение применяется в следующем коде.

[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
    // GET /api/test/3
    [HttpGet("{id:customName}")]
    public IActionResult Get(string id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // GET /api/test/my/3
    [HttpGet("my/{id:customName}")]
    public IActionResult Get(int id)
    {
        return ControllerContext.MyDisplayRouteInfo(id);
    }
}

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

Реализация MyCustomConstraint препятствует применению 0 к параметру маршрута:

class MyCustomConstraint : IRouteConstraint
{
    private Regex _regex;

    public MyCustomConstraint()
    {
        _regex = new Regex(@"^[1-9]*$",
                            RegexOptions.CultureInvariant | RegexOptions.IgnoreCase,
                            TimeSpan.FromMilliseconds(100));
    }
    public bool Match(HttpContext httpContext, IRouter route, string routeKey,
                      RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (values.TryGetValue(routeKey, out object value))
        {
            var parameterValueString = Convert.ToString(value,
                                                        CultureInfo.InvariantCulture);
            if (parameterValueString == null)
            {
                return false;
            }

            return _regex.IsMatch(parameterValueString);
        }

        return false;
    }
}

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

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

Предыдущий код:

  • Предотвращает 0 в сегменте {id} маршрута.
  • Отображается для предоставления базового примера реализации настраиваемого ограничения. Не следует использовать в рабочем приложении.

Следующий код является лучшим подходом к предотвращению обработки id с 0.

[HttpGet("{id}")]
public IActionResult Get(string id)
{
    if (id.Contains('0'))
    {
        return StatusCode(StatusCodes.Status406NotAcceptable);
    }

    return ControllerContext.MyDisplayRouteInfo(id);
}

Приведенный выше код имеет следующие преимущества по сравнению с подходом MyCustomConstraint.

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

Справочник по преобразователям параметров

Преобразователи параметров:

  • Выполняются при формировании ссылки с помощью LinkGenerator.
  • Реализуйте расширение Microsoft.AspNetCore.Routing.IOutboundParameterTransformer.
  • Настраиваются с помощью ConstraintMap.
  • Принимают значение маршрута параметра и изменяют его на новое строковое значение.
  • Приводят к использованию преобразованного значения в сформированной ссылке.

Например, пользовательский преобразователь параметра slugify в шаблоне маршрута blog\{article:slugify} с Url.Action(new { article = "MyTestArticle" }) формирует значение blog\my-test-article.

Рассмотрим следующую реализацию IOutboundParameterTransformer.

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

Чтобы использовать преобразователь параметров в шаблоне маршрута, настройте его с помощью ConstraintMap в Startup.ConfigureServices.

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

    services.AddRouting(options =>
    {
        options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
    });
}

В платформе ASP.NET Core преобразователи параметров используются для преобразования URI, где разрешается конечная точка. Например, преобразователи параметров преобразуют значения маршрута, используемые для сопоставления area, controller, action и page.

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

С помощью предыдущего шаблона маршрута действие SubscriptionManagementController.GetAll сопоставляется с URI /subscription-management/get-all. Преобразователь параметра не изменяет значения маршрута, используемые для формирования ссылки. Например, Url.Action("GetAll", "SubscriptionManagement") выводит /subscription-management/get-all.

ASP.NET Core предоставляет соглашения об API для использования преобразователей параметров со сформированными маршрутами.

Справочник по формированию URL-адресов

В этом разделе представлен справочник по алгоритму, реализованному при формировании URL-адреса. На практике в большинстве сложных примеров формирования URL-адресов используются контроллеры или Razor Pages. Дополнительные сведения см. в разделе Маршрутизация в контроллерах.

Процесс формирования URL-адреса начинается с вызова LinkGenerator.GetPathByAddress или аналогичного метода. Метод предоставляется с адресом, набором значений маршрута и при необходимости со сведениями о текущем запросе из HttpContext.

Первым шагом является использование адреса для разрешения набора конечных точек-кандидатов с помощью IEndpointAddressScheme<TAddress>, соответствующих типу адреса.

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

Устранение неполадок при формировании URL-адресов с помощью ведения журнала

Первым шагом при устранении неполадок при формировании URL-адресов является установка уровня ведения журнала Microsoft.AspNetCore.Routing для TRACE. LinkGenerator фиксирует в журнале множество сведений об обработке, которые могут быть полезны при устранении неполадок.

Дополнительные сведения о формировании URL-адресов см. в разделе Справочник по формированию URL-адресов.

Адреса

Адреса являются основным понятием в формировании URL-адресов и используются для привязки вызова генератора ссылок к набору конечных точек-кандидатов.

Адреса — это расширяемое понятие, которое по умолчанию поставляется с двумя реализациями.

  • Использование имени конечной точки (string) в качестве адреса:
    • Предоставляет аналогичные функции для имени маршрута MVC.
    • Использует тип метаданных IEndpointNameMetadata.
    • Разрешает указанную строку в соответствии с метаданными всех зарегистрированных конечных точек.
    • Создает исключение при запуске, если несколько конечных точек использует одно и то же имя.
    • Рекомендуется для общего использования за пределами контроллеров и Razor Pages.
  • Использование значений маршрута (RouteValuesAddress) в качестве адреса:
    • Предоставляет аналогичные устаревшие функции по формированию URL-адресов для контроллеров и Razor Pages.
    • Очень сложные расширение и отладка.
    • Предоставляет реализацию, используемую IUrlHelper, вспомогательными функциями тегов, вспомогательными методами HTML, результатами действий и т. д.

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

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

Значения окружения и явные значения

Из текущего запроса маршрутизация обращается к значениям маршрута текущего запроса HttpContext.Request.RouteValues. Значения, связанные с текущим запросом, называются значениями окружения. В целях ясности в документации подразумеваются значения маршрута, передаваемые в методы как явные значения.

В следующем примере показаны значения окружения и явные значения. Он предоставляет значения окружения из текущего запроса и явные значения: { id = 17, }:

public class WidgetController : Controller
{
    private readonly LinkGenerator _linkGenerator;

    public WidgetController(LinkGenerator linkGenerator)
    {
        _linkGenerator = linkGenerator;
    }

    public IActionResult Index()
    {
        var url = _linkGenerator.GetPathByAction(HttpContext,
                                                 null, null,
                                                 new { id = 17, });
        return Content(url);
    }

Предыдущий код:

  • Возвращает /Widget/Index/17.
  • Получает LinkGenerator через DI.

Следующий код не предоставляет значения окружения и явные значения: { controller = "Home", action = "Subscribe", id = 17, }:

public IActionResult Index2()
{
    var url = _linkGenerator.GetPathByAction("Subscribe", "Home",
                                             new { id = 17, });
    return Content(url);
}

Предыдущий метод возвращает /Home/Subscribe/17

Следующий код в WidgetController возвращает /Widget/Subscribe/17:

var url = _linkGenerator.GetPathByAction("Subscribe", null,
                                         new { id = 17, });

Следующий код предоставляет контроллер из значений окружения в текущем запросе и явные значения: { action = "Edit", id = 17, }:

public class GadgetController : Controller
{
    public IActionResult Index()
    {
        var url = Url.Action("Edit", new { id = 17, });
        return Content(url);
    }

В приведенном выше коде:

  • Возвращается /Gadget/Edit/17.
  • Url получает IUrlHelper.
  • Action
    создает URL-адрес с абсолютным путем для метода действия. URL-адрес содержит указанное имя action и значения route.

Следующий код предоставляет значения окружения из текущего запроса и явные значения: { page = "./Edit, id = 17, }:

public class IndexModel : PageModel
{
    public void OnGet()
    {
        var url = Url.Page("./Edit", new { id = 17, });
        ViewData["URL"] = url;
    }
}

Приведенный выше код задает для /Edit/17 значение url, когда страница Edit Razor содержит следующую директиву:

@page "{id:int}"

Если страница Edit не содержит шаблон маршрута "{id:int}", то url будет /Edit?id=17.

Поведение IUrlHelper MVC добавляет уровень сложности, помимо правил, описанных здесь.

  • IUrlHelper всегда предоставляет значения маршрута из текущего запроса как значения окружения.
  • IUrlHelper.Action всегда копирует текущие значения маршрута action и controller как явные значения, если они не переопределены разработчиком.
  • IUrlHelper.Page всегда копирует текущее значение маршрута page как явное значение, если оно не переопределено.
  • IUrlHelper.Page всегда переопределяет текущее значение маршрута handler на null как явные значения, если оно не переопределено.

Пользователи часто удивляются сведениям о поведении значений окружения, поскольку MVC не следует собственным правилам. По историческим причинам и для обеспечения совместимости для некоторых значений маршрута, таких как action, controller, page и handler, предусмотрено собственное поведение в особых случаях.

Аналогичные функции, предоставляемые LinkGenerator.GetPathByAction и LinkGenerator.GetPathByPage, дублируют эти аномалии IUrlHelper для обеспечения совместимости.

Процесс формирования URL-адреса

После обнаружения набора конечных точек-кандидатов алгоритм формирования URL-адресов:

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

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

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

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

Вызовы LinkGenerator или IUrlHelper, которые возвращают null, обычно вызываются в результате неправильного понимания аннулирования значения маршрута. Для устранения неполадок аннулирования значения маршрута явно укажите дополнительные значения маршрута, чтобы определить, устранена ли проблема.

Аннулирование значения маршрута предполагает, что схема URL-адреса приложения является иерархической, в которой иерархия сформирована слева направо. Рассмотрим шаблон маршрута базового контроллера {controller}/{action}/{id?}, чтобы понять, как это работает на практике. Изменение значения делает недействительными все значения маршрута, которые отображаются справа. Это отражает предположение об иерархии. Если приложение имеет значение окружения для id, а операция указывает другое значение для controller:

  • id не будет использоваться повторно, поскольку {controller} находится слева от {id?}.

Некоторые примеры, демонстрирующие этот принцип

  • Если явные значения содержат значение для id, значение окружения для id игнорируется. Можно использовать значения окружения для controller и action.
  • Если явные значения содержат значение для action, любое значение окружения для action игнорируется. Можно использовать значения окружения для controller. Если явное значение для action отличается от значения окружения для action, значение id не будет использоваться. Если явное значение для action совпадает со значением окружения для action, можно использовать значение id.
  • Если явные значения содержат значение для controller, любое значение окружения для controller игнорируется. Если явное значение для controller отличается от значения окружения для controller, значения action и id не будут использоваться. Если явное значение для controller совпадает со значением окружения для controller, можно использовать значения action и id.

Этот процесс усложняется за счет наличия маршрутов атрибутов и выделенных стандартных маршрутов. Стандартные маршруты контроллера, такие как {controller}/{action}/{id?}, указывают иерархию с помощью параметров маршрута. Для выделенных стандартных маршрутов и маршрутов атрибутов для контроллеров и Razor Pages:

  • Существует иерархия значений маршрута.
  • Они не отображаются в шаблоне.

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

Подробный алгоритм аннулирования значения маршрута

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

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

Затем допустимые значения можно использовать для расширения шаблона маршрута. Шаблон маршрута обрабатывается:

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

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

Значения окружения Явные значения Результат
controller = "Home" action = "About" /Home/About
controller = "Home" controller = "Order", action = "About" /Order/About
controller = "Home", color = "Red" action = "About" /Home/About
controller = "Home" action = "About", color = "Red" /Home/About?color=Red

Проблемы с аннулированием значений маршрута

Начиная с ASP.NET Core 3.0, некоторые схемы формирования URL-адресов, которые используются в более ранних версиях ASP.NET Core, не подходят для формирования URL-адресов. Команда ASP.NET Core планирует добавить функции для решения этих задач в будущем выпуске. В настоящее время самое лучшее решение — использовать устаревшую маршрутизацию.

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

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute("default", 
                                     "{culture}/{controller=Home}/{action=Index}/{id?}");
    endpoints.MapControllerRoute("blog", "{culture}/{**slug}", 
                                      new { controller = "Blog", action = "ReadPost", });
});

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

  • В шаблоне маршрута "default" параметр маршрута culture находится слева от controller, поэтому изменения controller не приведут к аннулированию culture.
  • В шаблоне маршрута "blog" параметр маршрута culture рассматривается как находящийся справа от controller, который имеется в требуемых значениях.

Настройка метаданных конечной точки

Сведения о настройке метаданных конечной точки см. на следующих веб-страницах:

Сопоставление узлов в маршрутах с помощью RequireHost

RequireHost применяет к ограничение маршруту, которому требуется указанный узел. Параметр RequireHost или [Host] может иметь следующее значение.

  • Узел: www.domain.com, соответствует www.domain.com с любым портом.
  • Узел с подстановочным знаком: *.domain.com, соответствует www.domain.com, subdomain.domain.com или www.subdomain.domain.com для любого порта.
  • Порт: *:5000, соответствует порту 5000 с любым узлом.
  • Узел и порт: www.domain.com:5000 или *.domain.com:5000, соответствует узлу и порту.

С помощью RequireHost или [Host] можно указать несколько параметров. Ограничение соответствует узлам, допустимым для любого из параметров. Например, [Host("domain.com", "*.domain.com")] соответствует domain.com, www.domain.com и subdomain.domain.com.

Следующий код использует RequireHost, чтобы запрашивать указанный узел в маршруте:

public void Configure(IApplicationBuilder app)
{
    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGet("/", context => context.Response.WriteAsync("Hi Contoso!"))
            .RequireHost("contoso.com");
        endpoints.MapGet("/", context => context.Response.WriteAsync("AdventureWorks!"))
            .RequireHost("adventure-works.com");
        endpoints.MapHealthChecks("/healthz").RequireHost("*:8080");
    });
}

Следующий код использует атрибут [Host] в контроллере, чтобы запрашивать любой из указанных узлов.

[Host("contoso.com", "adventure-works.com")]
public class ProductController : Controller
{
    public IActionResult Index()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    [Host("example.com:8080")]
    public IActionResult Privacy()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }
}

Если атрибут [Host] применяется как к контроллеру, так и к методу действия, выполняется следующее.

  • Используется атрибут действия.
  • Атрибут контроллера не учитывается.

Рекомендации по производительности для маршрутизации

В ASP.NET Core 3.0 была обновлена большая часть маршрутизации, чтобы повысить производительность.

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

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

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

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

public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
{
    app.Use(next => async context =>
    {
        var sw = Stopwatch.StartNew();
        await next(context);
        sw.Stop();

        logger.LogInformation("Time 1: {ElapsedMilliseconds}ms", sw.ElapsedMilliseconds);
    });

    app.UseRouting();

    app.Use(next => async context =>
    {
        var sw = Stopwatch.StartNew();
        await next(context);
        sw.Stop();

        logger.LogInformation("Time 2: {ElapsedMilliseconds}ms", sw.ElapsedMilliseconds);
    });

    app.UseAuthorization();

    app.Use(next => async context =>
    {
        var sw = Stopwatch.StartNew();
        await next(context);
        sw.Stop();

        logger.LogInformation("Time 3: {ElapsedMilliseconds}ms", sw.ElapsedMilliseconds);
    });

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Timing test.");
        });
    });
}

Для маршрутизации времени:

  • Чередуйте каждое ПО промежуточного слоя с копией ПО промежуточного слоя времени, показанного в предыдущем коде.
  • Добавьте уникальный идентификатор для сопоставления данных о времени с кодом.

Это базовый способ сократить задержку, когда она является существенной, например более 10ms. Вычитание Time 2 из Time 1 позволяет получить время, затраченное в ПО промежуточного слоя UseRouting.

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

public sealed class MyStopwatch : IDisposable
{
    ILogger<Startup> _logger;
    string _message;
    Stopwatch _sw;

    public MyStopwatch(ILogger<Startup> logger, string message)
    {
        _logger = logger;
        _message = message;
        _sw = Stopwatch.StartNew();
    }

    private bool disposed = false;


    public void Dispose()
    {
        if (!disposed)
        {
            _logger.LogInformation("{Message }: {ElapsedMilliseconds}ms",
                                    _message, _sw.ElapsedMilliseconds);

            disposed = true;
        }
    }
}
public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
{
    int count = 0;
    app.Use(next => async context =>
    {
        using (new MyStopwatch(logger, $"Time {++count}"))
        {
            await next(context);
        }

    });

    app.UseRouting();

    app.Use(next => async context =>
    {
        using (new MyStopwatch(logger, $"Time {++count}"))
        {
            await next(context);
        }
    });

    app.UseAuthorization();

    app.Use(next => async context =>
    {
        using (new MyStopwatch(logger, $"Time {++count}"))
        {
            await next(context);
        }
    });

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Timing test.");
        });
    });
}

Потенциально ресурсоемкие функции маршрутизации

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

  • Регулярные выражения: можно написать регулярные выражения, которые являются сложными или имеют длительное время выполнения и небольшой объем входных данных.

  • Сложные сегменты ({x}-{y}-{z}):

    • значительно более ресурсоемкие, чем анализ обычного сегмента URL-пути.
    • В результате выделяется множество дополнительных подстрок.
    • В обновлении производительности маршрутизации ASP.NET Core 3.0 не была обновлена логика комплексного сегмента.
  • Синхронный доступ к данным: многие сложные приложения имеют доступ к базе данных в рамках своей маршрутизации. Маршрутизация в ASP.NET Core 2.2 и более ранних версиях может не предоставлять надлежащие точки расширения для поддержки маршрутизации доступа к базе данных. Например, IRouteConstraint и IActionConstraint являются синхронными. Точки расширения, такие как MatcherPolicy и EndpointSelectorContext, являются асинхронными.

Руководство для авторов библиотек

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

Определение конечных точек

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

ВЫПОЛНИТЕ сборку поверх IEndpointRouteBuilder. Это позволит пользователям создать инфраструктуру с другими функциями ASP.NET Core без путаницы. Каждый шаблон ASP.NET Core включает в себя маршрутизацию. Предположим, что маршрутизация имеется и пользователи знакомы с ней.

app.UseEndpoints(endpoints =>
{
    // Your framework
    endpoints.MapMyFramework(...);

    endpoints.MapHealthChecks("/healthz");
});

ВЕРНИТЕ запечатанный конкретный тип из вызова MapMyFramework(...), реализующего IEndpointConventionBuilder. Большинство методов Map... платформы соответствует этому шаблону. Интерфейс IEndpointConventionBuilder:

  • Обеспечивает сочетаемость метаданных.
  • Предназначен для различных методов расширения.

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

app.UseEndpoints(endpoints =>
{
    // Your framework
    endpoints.MapMyFramework(...).RequireAuthorization()
                                 .WithMyFrameworkFeature(awesome: true);

    endpoints.MapHealthChecks("/healthz");
});

НАПИШИТЕ собственный EndpointDataSource. EndpointDataSource — это низкоуровневый примитив для объявления и обновления коллекции конечных точек. EndpointDataSource — это эффективный API, используемый контроллерами и Razor Pages.

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

НЕ пытайтесь зарегистрировать EndpointDataSource по умолчанию. Требуйте от пользователей, чтобы они регистрировали вашу платформу в UseEndpoints. Философия маршрутизации заключается в том, что по умолчанию ничего не включено и UseEndpoints представляет собой место для регистрации конечных точек.

Создание ПО промежуточного слоя со встроенной маршрутизацией

РАССМОТРИТЕ ВОЗМОЖНОСТЬ определения типов метаданных в качестве интерфейса.

СДЕЛАЙТЕ возможным использование типов метаданных в качестве атрибута в классах и методах.

public interface ICoolMetadata
{
    bool IsCool { get; }
}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
    public bool IsCool => true;
}

Платформы, такие как контроллеры и Razor Pages, поддерживают применение атрибутов метаданных к типам и методам. При объявлении типов метаданных:

  • Сделайте их доступными в качестве атрибутов.
  • Большинство пользователей знакомы с применением атрибутов.

Объявление типа метаданных в качестве интерфейса добавляет еще один уровень гибкости.

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

СДЕЛАЙТЕ возможным переопределение метаданных, как показано в следующем примере.

public interface ICoolMetadata
{
    bool IsCool { get; }
}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CoolMetadataAttribute : Attribute, ICoolMetadata
{
    public bool IsCool => true;
}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class SuppressCoolMetadataAttribute : Attribute, ICoolMetadata
{
    public bool IsCool => false;
}

[CoolMetadata]
public class MyController : Controller
{
    public void MyCool() { }

    [SuppressCoolMetadata]
    public void Uncool() { }
}

Следуйте этим рекомендациям, чтобы избежать определения метаданных маркера.

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

Коллекция метаданных является упорядоченной и поддерживает переопределение приоритета. В случае с контроллерами метаданные в методе действия являются наиболее специфичными.

СДЕЛАЙТЕ ПО промежуточного слоя полезным как с маршрутизацией, так и без нее.

app.UseRouting();

app.UseAuthorization(new AuthorizationPolicy() { ... });

app.UseEndpoints(endpoints =>
{
    // Your framework
    endpoints.MapMyFramework(...).RequireAuthorization();
});

В качестве примера этой рекомендации рассмотрим ПО промежуточного слоя UseAuthorization. ПО промежуточного слоя авторизации позволяет передавать политику отката. Политика отката, если она указана, применяется к обоим элементам:

  • конечные точки без указанной политики;
  • запросы, которые не соответствуют конечной точке.

Это сделает ПО промежуточного слоя авторизации полезным вне контекста маршрутизации. ПО промежуточного слоя авторизации можно использовать для традиционного программирования ПО промежуточного слоя.

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

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

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

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

Чтобы использовать новейшие сценарии маршрутизации в ASP.NET Core 2.2, укажите совместимую версию для регистрации служб MVC в Startup.ConfigureServices:

services.AddMvc()
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

Параметр EnableEndpointRouting определяет, должна ли маршрутизация внутренне использовать логику, основанную на конечных точках, или логику, основанную на IRouter, в ASP.NET Core 2.1 или более ранней версии. Если установлена совместимая версия 2.2 или более поздняя, значение по умолчанию — true. Задайте значение false, чтобы использовать предыдущую логику маршрутизации:

// Use the routing logic of ASP.NET Core 2.1 or earlier:
services.AddMvc(options => options.EnableEndpointRouting = false)
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

Дополнительные сведения о маршрутизации на основе IRouter см. в разделе о версии ASP.NET Core 2.1 в этой статье.

Важно!

В этом документе рассматривается низкоуровневая маршрутизация ASP.NET Core. Сведения о маршрутизации ASP.NET Core MVC см. в разделе Маршрутизация к действиям контроллера в ASP.NET Core. Сведения о соглашениях о маршрутизации в Razor Pages см. в разделе Соглашения для маршрутов и приложений Razor Pages в ASP.NET Core.

Просмотреть или скачать образец кода (как скачивать)

Основы маршрутизации

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

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

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

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

Приложения Razor Pages используют стандартную маршрутизацию по умолчанию для предоставления именованных ресурсов в папке Pages приложения. Доступны дополнительные соглашения, которые позволяют настроить поведение маршрутизации Razor Pages. Дополнительные сведения см. в разделах Введение в Razor Pages в ASP.NET Core и Соглашения для маршрутов и приложений Razor Pages в ASP.NET Core.

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

Маршрутизация использует конечные точки (Endpoint) для представления логических конечных точек в приложении.

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

Система маршрутизации обладает следующими характеристиками:

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

  • Допускается конфигурация конечной точки в стандартном стиле и с атрибутами.

  • IRouteConstraint используется для определения того, содержит ли параметр URL-адреса допустимое значение для ограничения заданной конечной точки.

  • Модели приложения, такие как MVC и Razor Pages, регистрируют все свои конечные точки, имеющие предсказуемую реализацию сценариев маршрутизации.

  • Реализация маршрутизации принимает решения о маршрутизации в нужном месте конвейера ПО промежуточного слоя.

  • ПО промежуточного слоя, которое появляется после ПО промежуточного слоя маршрутизации, может проверить результат принятия решений о конечной точке, принятое ПО промежуточного слоя маршрутизации для заданного URI запроса.

  • Можно перечислить все конечные точки в приложении в любом месте конвейера ПО промежуточного слоя.

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

  • Формирование URL-адреса основано на адресах, которые поддерживают произвольную расширяемость:

    • API генератора ссылок (LinkGenerator) может быть разрешено в любом месте с помощью внедрения зависимостей (DI) для формирования URL-адреса.
    • Если API генератора ссылок недоступно через внедрение зависимостей, IUrlHelper предлагает методы для создания URL-адреса.

Примечание

С появлением маршрутизации по конечным точкам в ASP.NET Core 2.2 связывание конечных точек стало ограничено действиями и страницами MVC и Razor Pages. Расширение функций связывания конечных точек планируется в будущих выпусках.

Функция маршрутизации подключается к конвейеру ПО промежуточного слоя с помощью класса RouterMiddleware. ASP.NET Core MVC добавляет функцию маршрутизации в конвейер ПО промежуточного слоя в процессе настройки и обрабатывает маршрутизацию в приложениях MVC и Razor Pages. Сведения об использовании маршрутизации в виде отдельного компонента см. в руководстве по использованию ПО промежуточного слоя маршрутизации.

Соответствие URL-адресов

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

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

Когда делегат конечной точки выполняется, свойствам RouteContext.RouteData присваиваются значения с учетом уже выполненной на текущий момент обработки.

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

RouteData.DataTokens — это контейнер свойств с дополнительными данными, связанными с соответствующим маршрутом. Свойства DataTokens обеспечивают связывание данных состояния с каждым маршрутом, что позволяет приложению принимать решения в зависимости от соответствующего маршрута. Эти значения определяются разработчиком и никоим образом не влияют на поведение маршрутизации. Кроме того, значения, спрятанные в токенах RouteData.DataToken, могут быть любого типа в отличие от значений RouteData.Value, которые должны легко преобразовываться в строки и из строк.

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

Создание URL-адреса с помощью LinkGenerator

Формирование URL-адреса — это процесс создания пути URL-адреса функцией маршрутизации на основе набора значений маршрута. Он обеспечивает логическое разделение конечных точек и URL-адресов, по которым к ним осуществляется доступ.

Маршрутизация по конечным точкам включает в себя API генератора ссылок (LinkGenerator). LinkGenerator — это отдельная служба, которую можно получить из DI. API можно использовать вне контекста выполнения запроса. IUrlHelper MVC и сценарии, которые зависят от IUrlHelper, такие как вспомогательные функции тегов, вспомогательные методы HTML и результаты действий, используют генератор ссылок для предоставления возможностей создания ссылок.

Генератор ссылок использует концепции адреса и схем адресов. Схема адресов — это способ определения конечных точек, которые должны рассматриваться для создания ссылки. Например, сценарии с именем маршрута и значениями маршрута, с которыми многие пользователи знакомы по MVC и Razor Pages, реализуются как схема адресов.

Генератор ссылок может установить связь с действиями и страницами MVC и Razor Pages с помощью следующих методов расширения:

Перегрузка этих методов принимает аргументы, которые включают HttpContext. Эти методы являются функциональными эквивалентами Url.Action и Url.Page, но предлагают дополнительную гибкость и параметры.

Методы GetPath* наиболее схожи с Url.Action и Url.Page в том, что создают URI, содержащий абсолютный путь. Методы GetUri* всегда создают абсолютный URI, содержащий схему и узел. Методы, которые принимают HttpContext, создают URI в контексте выполнения запроса. Используются значения окружения маршрута, базовый URL-адрес, схема и узел из выполняющегося запроса, если не указано иное.

LinkGenerator вызывается с адресом. Создание URI происходит в два этапа:

  1. Адрес привязан к списку конечных точек, соответствующих адресу.
  2. RoutePattern конечной точки вычисляется, пока не будет найден шаблон маршрута, который соответствует предоставленным значениям. Полученный результат объединяется с другими частями URI, предоставленными генератору ссылок и возвращенными.

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

Метод расширения Описание
GetPathByAddress Создает URI с абсолютным путем на основе предоставленных значений.
GetUriByAddress Создает абсолютный URI на основе предоставленных значений.

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

Обратите внимание на следующие последствия вызова методов LinkGenerator:

  • Используйте методы расширения GetUri* с осторожностью в конфигурации приложения, которая не проверяет заголовок входящих запросов Host. Если не проверить заголовок входящих запросов Host, входные данные в запросе без доверия могут отправляться обратно клиенту в URI в представлении или странице. Рекомендуется, чтобы все рабочие приложения настраивали свой сервер на проверку заголовка Host относительно известных допустимых значений.

  • Используйте LinkGenerator с осторожностью в ПО промежуточного слоя в сочетании с Map или MapWhen. Map* изменяет базовый путь выполняющегося запроса, что влияет на выходные данные создания ссылки. Все API LinkGenerator разрешают указание базового пути. Всегда указывайте пустой базовый путь для отмены влияния Map* на создание ссылок.

Отличия от более ранних версий маршрутизации

Существует несколько различий между маршрутизацией по конечным точкам в ASP.NET Core 2.2 или более поздней версии и более ранними версиями маршрутизации в ASP.NET Core:

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

  • Маршрутизация по конечным точкам не поддерживает WebApiCompatShim. Используйте совместимую версию 2.1 (.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)), чтобы продолжить использование оболочек совместимости.

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

    Рассмотрим следующий шаблон маршрута по умолчанию:

    app.UseMvc(routes =>
    {
        routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
    });
    

    Предположим, вы создаете ссылку на действие с помощью следующего маршрута:

    var link = Url.Action("ReadPost", "blog", new { id = 17, });
    

    С помощью маршрутизации на основе IRouter этот код создает URI из /blog/ReadPost/17, который учитывает регистр предоставленного значения маршрута. Маршрутизация по конечным точкам в ASP.NET Core 2.2 или более поздней версии создает /Blog/ReadPost/17 ("Blog" — с прописной буквы). Маршрутизация по конечным точкам предоставляет интерфейс IOutboundParameterTransformer, который можно использовать для настройки этого поведения на глобальном уровне или применения других соглашения для сопоставления URL-адресов.

    Дополнительные сведения см. в разделе Справочник по преобразователям параметров.

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

    Рассмотрим следующий шаблон маршрута по умолчанию:

    app.UseMvc(routes =>
    {
        routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
    });
    

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

    var link = Url.Action("ReadPost", "Blog", new { id = 17, });
    

    С маршрутизацией на основе IRouter результатом всегда будет /Blog/ReadPost/17, даже если BlogController не существует или не имеет метода действия ReadPost. Как и ожидалось, маршрутизация по конечным точкам в ASP.NET Core 2.2 или более поздней версии создает /Blog/ReadPost/17, если метод действия существует. Но маршрутизация по конечным точкам создает пустую строку, если действие не существует. По существу, маршрутизация по конечным точкам не предполагает, что конечная точка существует, если действие не существует.

  • Алгоритм отмены значения окружения при создании ссылок работает иначе при маршрутизации по конечным точкам.

    Отмена значения окружения — это алгоритм, который решает, какие значения маршрута из текущего выполняемого запроса (значения окружения) могут использоваться в операции создания ссылки. Стандартная маршрутизация всегда отменяет лишние значения маршрута при привязке к другому действию. Маршрутизация с помощью атрибутов работала иначе до выпуска ASP.NET Core 2.2. В более ранних версиях ASP.NET Core ссылки на другое действие, которые используют те же имена параметров маршрута, приводили к ошибкам создания ссылки. В ASP.NET Core 2.2 или более поздней версии обе формы маршрутизации отменяют значения при привязке к другому действию.

    Рассмотрим следующий пример в ASP.NET Core 2.1 или более ранней версии. При привязке к другому действию (или странице) значения маршрута могут использоваться повторно нежелательным образом.

    В /Pages/Store/Product.cshtml:

    @page "{id}"
    @Url.Page("/Login")
    

    В /Pages/Login.cshtml:

    @page "{id?}"
    

    Если URI — /Store/Product/18 в ASP.NET Core 2.1 или более ранней версии, ссылка, созданная на странице Store/Info с помощью @Url.Page("/Login") — /Login/18. Значение id 18 используется повторно, хотя конечный объект ссылки является совсем другой частью приложения. Значение маршрута id в контексте страницы /Login, вероятно, является значением идентификатора пользователя, а не кодом хранимого продукта.

    При маршрутизации по конечным точкам в ASP.NET Core 2.2 или более поздней версии результатом является /Login. Значения окружения не используются повторно, если конечный объект ссылки является другим действием или страницей.

  • Синтаксис параметра кругового маршрута: символы прямой косой черты не кодируются при использовании синтаксиса универсального параметра с двумя звездочками (**).

    Во время создания ссылки система маршрутизации кодирует значение, записанное в универсальном параметре с двумя звездочками (**) (например, {**myparametername}), за исключением прямой косой черты. Универсальный параметр с двумя звездочками поддерживается в маршрутизации на основе IRouter в ASP.NET Core 2.2 или более поздней версии.

    Синтаксис универсального параметра с одной звездочкой в предыдущих версиях ASP.NET Core ({*myparametername}) поддерживается, и прямая косая черта кодируется.

    Маршрут Ссылка, созданная с помощью
    Url.Action(new { category = "admin/products" })
    /search/{*page} /search/admin%2Fproducts (прямая косая черта кодируется)
    /search/{**page} /search/admin/products

Пример ПО промежуточного слоя

В следующем примере ПО промежуточного слоя использует API LinkGenerator, чтобы создать ссылку на метод действия, который перечисляет хранимые продукты. Использование генератора ссылок путем его внедрения в класс и вызова GenerateLink доступно для любого класса в приложении.

using Microsoft.AspNetCore.Routing;

public class ProductsLinkMiddleware
{
    private readonly LinkGenerator _linkGenerator;

    public ProductsLinkMiddleware(RequestDelegate next, LinkGenerator linkGenerator)
    {
        _linkGenerator = linkGenerator;
    }

    public async Task InvokeAsync(HttpContext httpContext)
    {
        var url = _linkGenerator.GetPathByAction("ListProducts", "Store");

        httpContext.Response.ContentType = "text/plain";

        await httpContext.Response.WriteAsync($"Go to {url} to see our products.");
    }
}

Создание маршрутов

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

MapRoute не принимает параметр обработчика маршрутов. MapRoute только добавляет маршруты, которые обрабатываются DefaultHandler. Дополнительные сведения о маршрутизации в MVC см. в разделе Маршрутизация к действиям контроллера в ASP.NET Core.

В следующем коде приводится пример вызова MapRoute, используемого в типичном определении маршрута ASP.NET Core MVC:

routes.MapRoute(
    name: "default",
    template: "{controller=Home}/{action=Index}/{id?}");

Этот шаблон соответствует пути URL-адреса и извлекает значения маршрута. Например, путь /Products/Details/17 создает следующие значения маршрута: { controller = Products, action = Details, id = 17 }.

Значения маршрута определяются с помощью разделения пути URL-адреса на сегменты и сопоставления каждого сегмента с именем параметра маршрута в шаблоне маршрута. Параметры маршрута являются именованными. Параметры определяются путем заключения имени параметра в фигурные скобки { ... }.

Приведенный выше шаблон может также соответствовать пути URL-адреса /. В этом случае он предоставляет значения { controller = Home, action = Index }. Связано это с тем, что параметры маршрута {controller} и {action} имеют значения по умолчанию, а параметр маршрута id является необязательным. Значение по умолчанию для параметра маршрута определяется с помощью знака равенства (=), за которым следует значение после имени параметра. Вопросительный знак (?) после имени параметра маршрута определяет параметр как необязательный.

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

В следующем примере в определении параметра маршрута {id:int} определяется ограничение маршрута для параметра маршрута id:

routes.MapRoute(
    name: "default",
    template: "{controller=Home}/{action=Index}/{id:int}");

Этот шаблон соответствует такому пути URL-адреса, как /Products/Details/17, но не /Products/Details/Apples. Ограничения маршрута реализуют интерфейс IRouteConstraint и проверяют значения маршрута. В этом примере значение маршрута id должно иметь возможность преобразования в целое число. Более подробное описание ограничений маршрутов, предоставляемых платформой, см. в разделе Справочник по ограничениям маршрутов.

Дополнительные перегрузки MapRoute принимают значения для параметров constraints, dataTokens и defaults. Типичное применение этих параметров состоит в передаче анонимно типизированного объекта, причем имена свойств анонимного типа соответствуют именам параметров маршрута.

В следующих примерах MapRoute создаются эквивалентные маршруты:

routes.MapRoute(
    name: "default_route",
    template: "{controller}/{action}/{id?}",
    defaults: new { controller = "Home", action = "Index" });

routes.MapRoute(
    name: "default_route",
    template: "{controller=Home}/{action=Index}/{id?}");

Совет

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

В следующем примере показано еще несколько сценариев:

routes.MapRoute(
    name: "blog",
    template: "Blog/{**article}",
    defaults: new { controller = "Blog", action = "ReadArticle" });

Предыдущий шаблон соответствует такому пути URL-адреса, как /Blog/All-About-Routing/Introduction, и извлекает значения { controller = Blog, action = ReadArticle, article = All-About-Routing/Introduction }. Значения маршрута по умолчанию для controller и action предоставляются маршрутом несмотря на то, что в шаблоне нет соответствующих параметров маршрута. Значения по умолчанию можно указать в шаблоне маршрута. Параметр маршрута article определяется как универсальный с помощью двух звездочек (**) перед именем. Универсальные параметры маршрута служат для фиксации оставшейся части пути URL-адреса, а также могут соответствовать пустой строке.

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

routes.MapRoute(
    name: "us_english_products",
    template: "en-US/Products/{id}",
    defaults: new { controller = "Products", action = "Details" },
    constraints: new { id = new IntRouteConstraint() },
    dataTokens: new { locale = "en-US" });

Предыдущий шаблон будет соответствовать такому пути URL-адреса, как /en-US/Products/5, и будет извлекать значения { controller = Products, action = Details, id = 5 } и токены данных { locale = en-US }.

Токены в окне "Локальные"

<a name="route-class-url-generation">Формирование URL-адреса класса маршрута

Класс Route может также формировать URL-адрес, объединяя набор значений маршрута с шаблоном маршрута. С логической точки зрения, этот процесс обратен сопоставлению пути URL-адреса.

Совет

Чтобы лучше понять процесс формирования URL-адреса, представьте себе, какой URL-адрес нужно создать, а затем подумайте, как шаблон маршрута будет сопоставляться с этим URL-адресом. Какие значения будут получены? Примерно так производится формирование URL-адреса в классе Route.

В следующем примере используется общий маршрут по умолчанию ASP.NET Core MVC:

routes.MapRoute(
    name: &quot;default&quot;,
    template: &quot;{controller=Home}/{action=Index}/{id?}");

При значениях маршрута { controller = Products, action = List } создается URL-адрес /Products/List. Значения маршрута заменяются на соответствующие параметры маршрута для образования пути URL-адреса. Так как id является необязательным параметром маршрута, URL-адрес успешно создается без значения для id.

При значениях маршрута { controller = Home, action = Index } создается URL-адрес /. Предоставленные значения маршрута соответствуют значениям по умолчанию, поэтому сегменты, соответствующие значениям по умолчанию, можно спокойно опустить.

Оба созданных URL-адреса будут совершать круговой путь с таким же определением маршрута (/Home/Index и /) и предоставлять те же значения маршрута, которые использовались для формирования URL-адреса.

Примечание

Приложение, использующее платформу ASP.NET Core MVC, должно создавать URL-адреса с помощью объекта UrlHelper, а не вызывать маршрутизацию напрямую.

Дополнительные сведения о формировании URL-адресов см. в справочнике по формированию URL-адресов.

Использование ПО промежуточного слоя маршрутизации

Ссылка на метапакет Microsoft.AspNetCore.App в файле проекта приложения.

Добавьте маршрутизацию в контейнер службы в файле Startup.ConfigureServices:

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

Маршруты должны настраиваться в методе Startup.Configure. В этом примере приложения используются следующие API:

var trackPackageRouteHandler = new RouteHandler(context =>
{
    var routeValues = context.GetRouteData().Values;
    return context.Response.WriteAsync(
        $"Hello! Route values: {string.Join(", ", routeValues)}");
});

var routeBuilder = new RouteBuilder(app, trackPackageRouteHandler);

routeBuilder.MapRoute(
    "Track Package Route",
    "package/{operation:regex(^track|create$)}/{id:int}");

routeBuilder.MapGet("hello/{name}", context =>
{
    var name = context.GetRouteValue("name");
    // The route handler when HTTP GET "hello/<anything>" matches
    // To match HTTP GET "hello/<anything>/<anything>, 
    // use routeBuilder.MapGet("hello/{*name}"
    return context.Response.WriteAsync($"Hi, {name}!");
});

var routes = routeBuilder.Build();
app.UseRouter(routes);

В таблице ниже приведены ответы с данными универсальными кодами ресурсов (URI).

URI Ответ
/package/create/3 Hello! Значения маршрута: [operation, create], [id, 3]
/package/track/-3 Hello! Значения маршрута: [operation, track], [id, -3]
/package/track/-3/ Hello! Значения маршрута: [operation, track], [id, -3]
/package/track/ Запрос не дал результата, нет совпадений.
GET /hello/Joe Hi, Joe!
POST /hello/Joe Запрос не дал результата, совпадение только с HTTP GET.
GET /hello/Joe/Smith Запрос не дал результата, нет совпадений.

Платформа предоставляет наборов методов расширения для создания маршрутов (RequestDelegateRouteBuilderExtensions):

Методы Map[Verb] используют ограничения, чтобы ограничить маршрут к HTTP-команде в имени метода. Например, см. класс MapGet и тип MapVerb.

Справочник по шаблону маршрута

Токены в фигурных скобках ({ ... }) определяют параметры маршрута, которые будут привязаны при совпадении маршрута. В сегменте маршрута можно определить несколько параметров маршрута, но они должны разделяться литеральным значением. Например, {controller=Home}{action=Index} будет недопустимым маршрутом, так как между {controller} и {action} нет литерального значения. Эти параметры маршрута должны иметь имена, и для них могут быть определены дополнительные атрибуты.

Весь текст, кроме параметров маршрута (например, {id}) и разделителя пути /, должен соответствовать тексту в URL-адресе. Сопоставление текста производится без учета регистра на основе декодированного представления путь URL-адреса. Для сопоставления с литеральным разделителем параметров маршрута ({ или }) разделитель следует экранировать путем повтора символа ({{ или }}).

Шаблоны URL-адресов, которые пытаются получить имя файла с необязательным расширением, имеют свои особенности. Например, рассмотрим шаблон files/{filename}.{ext?}. Когда значения для filename и ext существуют, заполняются оба значения. Если в URL-адресе есть только значение для filename, маршрут совпадает, так как точка в конце (.) является необязательной. Следующие URL-адреса соответствуют этому маршруту:

  • /files/myFile.txt
  • /files/myFile

Вы можете использовать звездочку (*) или две звездочки (**) в качестве префикса параметра маршрута для привязки к остальной части URI. Такие параметры называются универсальными. Например, blog/{**slug} соответствует любому URI, начинающемуся с сегмента /blog, за которым следует любое значение, присваиваемое в качестве значения маршрута slug. Универсальные параметры также могут соответствовать пустой строке.

Универсальный параметр экранирует соответствующие символы, если маршрут использует для формирования URL-адрес, включая символы разделителей пути (/). Например, маршрут foo/{*path} со значениями маршрутов { path = "my/path" } формирует foo/my%2Fpath. Обратите внимание на экранированный знак косой черты. В качестве символов разделителя кругового пути используйте префикс параметра маршрута **. Маршрут foo/{**path} с { path = "my/path" } формирует foo/my/path.

Параметры маршрута могут иметь значения по умолчанию. Они указываются после имени параметра и знака равенства (=). Например, {controller=Home} определяет Home в качестве значения по умолчанию для controller. Значение по умолчанию используется, если для параметра нет значения в URL-адресе. Параметры маршрута могут быть необязательными (для этого необходимо добавить вопросительный знак (?) в конец имени параметра, например id?). Различие между необязательными параметрами и параметрами маршрута по умолчанию в том, что вторые всегда имеют значения — необязательный параметр имеет значение, только если оно предоставлено URL-адресом запроса.

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

Имя и аргументы ограничения передаются в службу IInlineConstraintResolver для создания экземпляра интерфейса IRouteConstraint, который будет использоваться при обработке URL-адреса. Например, в шаблоне маршрута blog/{article:minlength(10)} определяется ограничение minlength с аргументом 10. Более подробное описание ограничений маршрутов и список ограничений, предоставляемых платформой, см. в разделе Справочник по ограничениям маршрутов.

Параметры маршрута также могут иметь преобразователи параметров, которые преобразуют значение параметра при создании ссылок и сопоставлении действий и страниц с URL-адресами. Как и ограничения, преобразователи параметров можно включать в параметр маршрута, добавив двоеточие (:) и имя преобразователя после имени параметра маршрута. Например, шаблон маршрута blog/{article:slugify} задает преобразователь slugify. Дополнительные сведения о преобразователях параметров см. в разделе Справочник по преобразователям параметров.

В приведенной ниже таблице показаны некоторые примеры шаблонов маршрутов и их поведение.

Шаблон маршрута Пример соответствующего URI URI запроса…
hello /hello Соответствует только одному пути /hello.
{Page=Home} / Соответствует и задает для параметра Page значение Home.
{Page=Home} /Contact Соответствует и задает для параметра Page значение Contact.
{controller}/{action}/{id?} /Products/List Сопоставляется с контроллером Products и действием List.
{controller}/{action}/{id?} /Products/Details/123 Сопоставляется с контроллером Products и действием Details (id имеет значение 123).
{controller=Home}/{action=Index}/{id?} / Сопоставляется с контроллером Home и методом Index (id пропускается).

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

Совет

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

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

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

  • action
  • area
  • controller
  • handler
  • page

Существуют также определенные наборы ключевых слов, которые ASP.NET Core MVC использует для внутренних целей, которые не следует использовать в приложениях ASP.NET Core, так как это может привести к неожиданным результатам в определенных сценариях.

  • controller, action, area и page являются зарезервированными ключевыми словами, используемыми системой маршрутизации MVC. Используя их в качестве части ссылок, параметров привязки модели или свойств верхнего уровня, можно привязать зарезервированное значение маршрута.

Consider

// /ListProducts/Index.cshtml
@page "{page:int?}"

@functions {
   public async Task OnGetAsync(int page)
   {
        ...
    }

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

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

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

Справочник по ограничениям маршрутов

Ограничения маршрута применяются, когда найдено соответствие входящему URL-адресу и путь URL-адреса был разобран на значения маршрута. Как правило, ограничения маршрута служат для проверки значения маршрута, связанного посредством шаблона маршрута, и принятия решения касательно того, является ли значение приемлемым (да или нет). Некоторые ограничения маршрута используют данные, не относящиеся к значению маршрута, для определения возможности маршрутизации запроса. Например, HttpMethodRouteConstraint может принимать или отклонять запрос в зависимости от HTTP-команды. Ограничения используются в маршрутизации запросов и создании ссылок.

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

Не используйте ограничения для проверки входных данных. Если ограничения используются для проверки входных данных, недопустимые входные данные будут вызывать ошибку 404 (не найдено) вместо ошибки 400 (неверный запрос) с соответствующим сообщением. Ограничения маршрутов следует использовать для разрешения неоднозначности похожих маршрутов, а не для проверки входных данных определенного маршрута.

В приведенной ниже таблице показаны примеры ограничения маршрутов и их ожидаемое поведение.

Ограничение Пример Примеры совпадений Примечания
int {id:int} 123456789, -123456789 Соответствует любому целому числу.
bool {active:bool} true, FALSE Соответствует true или false. Без учета регистра.
datetime {dob:datetime} 2016-12-31, 2016-12-31 7:32pm Соответствует допустимому значению DateTime для инвариантного языка и региональных параметров. См. предупреждение выше.
decimal {price:decimal} 49.99, -1,000.01 Соответствует допустимому значению decimal для инвариантного языка и региональных параметров. См. предупреждение выше.
double {weight:double} 1.234, -1,001.01e8 Соответствует допустимому значению double для инвариантного языка и региональных параметров. См. предупреждение выше.
float {weight:float} 1.234, -1,001.01e8 Соответствует допустимому значению float для инвариантного языка и региональных параметров. См. предупреждение выше.
guid {id:guid} CD2C1638-1638-72D5-1638-DEADBEEF1638, {CD2C1638-1638-72D5-1638-DEADBEEF1638} Соответствует допустимому значению Guid.
long {ticks:long} 123456789, -123456789 Соответствует допустимому значению long.
minlength(value) {username:minlength(4)} Rick Строка должна содержать не менее 4 символов.
maxlength(value) {filename:maxlength(8)} MyFile Строка должна содержать не более 8 символов.
length(length) {filename:length(12)} somefile.txt Длина строки должна составлять ровно 12 символов.
length(min,max) {filename:length(8,16)} somefile.txt Строка должна содержать не менее 8 и не более 16 символов.
min(value) {age:min(18)} 19 Целочисленное значение не меньше 18.
max(value) {age:max(120)} 91 Целочисленное значение не больше 120.
range(min,max) {age:range(18,120)} 91 Целочисленное значение не меньше 18 и не больше 120.
alpha {name:alpha} Rick Строка должна состоять из одной буквы или нескольких (a-z). Без учета регистра.
regex(expression) {ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} 123-45-6789 Строка должна соответствовать регулярному выражению. См. советы по определению регулярного выражения.
required {name:required} Rick Определяет обязательное наличие значения, не относящегося к параметру, во время формирования URL-адреса.

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

[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }

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

Ограничения маршрута, которые проверяют URL-адрес и могут быть преобразованы в тип CLR (например, int или DateTime), всегда используют инвариантные язык и региональные параметры. Эти ограничения предполагают, что URL-адрес является нелокализуемым. Предоставляемые платформой ограничения маршрутов не изменяют значения, хранящиеся в значениях маршрута. Все значения маршрута, переданные из URL-адреса, сохраняются как строки. Например, ограничение float пытается преобразовать значение маршрута в число с плавающей запятой, но преобразованное значение служит только для проверки возможности такого преобразования.

Регулярные выражения

В платформе ASP.NET Core в конструктор регулярных выражений добавляются члены RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant. Описание этих членов см. в разделе RegexOptions.

В регулярных выражениях применяются разделители и токены, аналогичные используемым функцией маршрутизации и в языке C#. Токены регулярного выражения должны быть экранированы. Чтобы использовать регулярное выражение ^\d{3}-\d{2}-\d{4}$ в маршрутизации:

  • Выражение должно содержать символы одинарной обратной косой черты (\), представленные в строке с символами двойной обратной косой черты (\\) в исходном коде.
  • Регулярное выражение должно использовать \\ для экранирования строкового escape-символа \.
  • Регулярное выражение не требует \\ при использовании буквальных строковых литералов.

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

Регулярное выражение Экранированное регулярное выражение
^\d{3}-\d{2}-\d{4}$ ^\\d{{3}}-\\d{{2}}-\\d{{4}}$
^[a-z]{2}$ ^[[a-z]]{{2}}$

Регулярные выражения, используемые при маршрутизации, часто начинаются с символа карет (^) и соответствуют начальной позиции строки. Выражения часто заканчиваются знаком доллара ($) и соответствуют концу строки. Благодаря символам ^ и $ регулярное выражение сопоставляется со всем значением параметра маршрута. Если символы ^ и $ отсутствуют, регулярное выражение сопоставляется с любой подстрокой внутри строки, что обычно нежелательно. В следующей таблице представлен ряд примеров и объясняются причины соответствия или несоответствия.

Выражение Строка Соответствие Добавление примечаний
[a-z]{2} hello Да Соответствие подстроки
[a-z]{2} 123abc456 Да Соответствие подстроки
[a-z]{2} mz Да Соответствует выражению
[a-z]{2} MZ Да Без учета регистра
^[a-z]{2}$ hello Нет См. замечания, касающиеся символов ^ и $, выше
^[a-z]{2}$ 123abc456 Нет См. замечания, касающиеся символов ^ и $, выше

Дополнительные сведения о синтаксисе регулярных выражений см. в статье Регулярные выражения в .NET Framework.

Чтобы ограничить возможные значения параметра набором известных значений, используйте регулярное выражение. Например, при использовании выражения {action:regex(^(list|get|create)$)} значение маршрута action будет соответствовать только list, get или create. При передаче в словарь ограничений строка ^(list|get|create)$ будет эквивалентной. Ограничения, которые передаются в словарь ограничений (то есть не являются встроенными ограничениями шаблона) и не соответствуют одному из известных ограничений, также рассматриваются как регулярные выражения.

Пользовательские ограничения маршрутов

Помимо встроенных ограничений маршрутов пользовательские ограничения маршрутов можно создать путем внедрения интерфейса IRouteConstraint. Интерфейс IRouteConstraint содержит один метод, Match, который возвращает true, если ограничение удовлетворяется, и false — если нет.

Чтобы применить пользовательский метод IRouteConstraint, тип ограничения маршрута необходимо зарегистрировать с помощью ConstraintMap приложения в контейнере службы приложения. Объект ConstraintMap — это словарь, который сопоставляет ключи ограничений пути с реализациями IRouteConstraint, которые проверяют эти ограничения. ConstraintMap приложения можно преобразовать в Startup.ConfigureServices как часть вызова services.AddRouting или путем настройки RouteOptions непосредственно с помощью services.Configure<RouteOptions>. Пример:

services.AddRouting(options =>
{
    options.ConstraintMap.Add("customName", typeof(MyCustomConstraint));
});

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

[HttpGet("{id:customName}")]
public ActionResult<string> Get(string id)

Справочник по преобразователям параметров

Преобразователи параметров:

  • Выполняются при формировании ссылки для Route.
  • Реализуйте расширение Microsoft.AspNetCore.Routing.IOutboundParameterTransformer.
  • Настраиваются с помощью ConstraintMap.
  • Принимают значение маршрута параметра и изменяют его на новое строковое значение.
  • Приводят к использованию преобразованного значения в сформированной ссылке.

Например, пользовательский преобразователь параметра slugify в шаблоне маршрута blog\{article:slugify} с Url.Action(new { article = "MyTestArticle" }) формирует значение blog\my-test-article.

Чтобы использовать преобразователь параметров в шаблоне маршрута, сначала настройте его с помощью ConstraintMap в Startup.ConfigureServices:

services.AddRouting(options =>
{
    // Replace the type and the name used to refer to it with your own
    // IOutboundParameterTransformer implementation
    options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
});

Преобразователи параметров также используются платформами для преобразования URI, где разрешается конечная точка. Например, ASP.NET Core MVC с помощью преобразователей параметров преобразует значение маршрута, используемое для сопоставления area, controller, action и page.

routes.MapRoute(
    name: "default",
    template: "{controller:slugify=Home}/{action:slugify=Index}/{id?}");

С помощью предыдущего маршрута действие SubscriptionManagementController.GetAll сопоставляется с URI /subscription-management/get-all. Преобразователь параметра не изменяет значения маршрута, используемые для формирования ссылки. Например, Url.Action("GetAll", "SubscriptionManagement") выводит /subscription-management/get-all.

ASP.NET Core предоставляет соглашения об API для использования преобразователей параметров со сформированными маршрутами:

  • ASP.NET Core MVC поддерживает соглашение об API Microsoft.AspNetCore.Mvc.ApplicationModels.RouteTokenTransformerConvention. Это соглашение применяет указанный преобразователь параметров ко всем маршрутам атрибутов в приложении. Преобразователь параметров преобразует маркеры маршрутов атрибутов по мере их замены. Дополнительные сведения см. в разделе об использовании преобразователя параметров для настройки замены маркеров.
  • Razor Pages поддерживает соглашение об API Microsoft.AspNetCore.Mvc.ApplicationModels.PageRouteTransformerConvention. Это соглашение применяет указанный преобразователь параметров ко всем автоматически обнаруженным страницам Razor Pages. Преобразователь параметров преобразует сегменты папок и имен файлов маршрутов Razor Pages. Дополнительные сведения см. в разделе об использовании преобразователя параметров для настройки маршрутов страниц.

Справочник по формированию URL-адресов

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

app.Run(async (context) =>
{
    var dictionary = new RouteValueDictionary
    {
        { "operation", "create" },
        { "id", 123}
    };

    var vpc = new VirtualPathContext(context, null, dictionary, 
        "Track Package Route");
    var path = routes.GetVirtualPath(vpc).VirtualPath;

    context.Response.ContentType = "text/html";
    await context.Response.WriteAsync("Menu<hr/>");
    await context.Response.WriteAsync(
        $"<a href='{path}'>Create Package 123</a><br/>");
});

В результате приведенного выше примера создается VirtualPath со значением /package/create/123. Словарь предоставляет значения маршрута operation и id шаблона "Отслеживание маршрута пакета", package/{operation}/{id}. Дополнительные сведения см. в примере кода в разделе Использование ПО промежуточного слоя маршрутизации или в примере приложения.

Второй параметр конструктора VirtualPathContext — это коллекция значений окружения. Значения окружения упрощают разработку, ограничивая число значений, которые необходимо указывать в определенном контексте запроса. Текущие значения маршрута текущего запроса считаются значениями окружения для создания ссылки. В приложении ASP.NET MVC в действии About контроллера HomeController не нужно задавать значение маршрута контроллера, указывающее на действие Index— используется значение окружения Home.

Значения окружения, которые не соответствуют параметру, игнорируются. Значения окружения также не учитываются, когда явно указанное значение переопределяет значение окружения. Сопоставление выполняется слева направо в URL-адресе.

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

Значения окружения Явные значения Результат
controller = "Home" action = "About" /Home/About
controller = "Home" controller = "Order", action = "About" /Order/About
controller = "Home", color = "Red" action = "About" /Home/About
controller = "Home" action = "About", color = "Red" /Home/About?color=Red

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

routes.MapRoute("blog_route", "blog/{*slug}",
    defaults: new { controller = "Blog", action = "ReadPost" });

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

Сложные сегменты

Сложные сегменты (например, [Route("/x{token}y")]) обрабатываются путем "нежадного" сопоставления литералов справа налево. Подробные сведения о сопоставлении сложных сегментов см. в этом коде. Пример кода не используется в ASP.NET Core, но он предоставляет подробное объяснение сложных сегментов.

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

Чтобы использовать новейшие сценарии маршрутизации в ASP.NET Core 2.1, укажите совместимую версию для регистрации служб MVC в Startup.ConfigureServices:

services.AddMvc()
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

Важно!

В этом документе рассматривается низкоуровневая маршрутизация ASP.NET Core. Сведения о маршрутизации ASP.NET Core MVC см. в разделе Маршрутизация к действиям контроллера в ASP.NET Core. Сведения о соглашениях о маршрутизации в Razor Pages см. в разделе Соглашения для маршрутов и приложений Razor Pages в ASP.NET Core.

Просмотреть или скачать образец кода (как скачивать)

Основы маршрутизации

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

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

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

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

Приложения Razor Pages используют стандартную маршрутизацию по умолчанию для предоставления именованных ресурсов в папке Pages приложения. Доступны дополнительные соглашения, которые позволяют настроить поведение маршрутизации Razor Pages. Дополнительные сведения см. в разделах Введение в Razor Pages в ASP.NET Core и Соглашения для маршрутов и приложений Razor Pages в ASP.NET Core.

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

Маршрутизация использует маршруты (реализации интерфейса IRouter) в следующих целях:

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

По умолчанию приложение имеет одну коллекцию маршрутов. Когда запрос прибывает, маршруты в коллекции обрабатываются в порядке, в котором они существуют в коллекции. Платформа пытается сопоставить URL-адрес входящего запроса с маршрутом в коллекции, вызывая метод RouteAsync для каждого маршрута в коллекции. Отклик может использовать маршрутизацию для формирования URL-адресов (например, для перенаправления или ссылок) на основе сведений о маршруте, что позволяет избежать жесткого задания URL-адресов и упрощает поддержку.

Система маршрутизации обладает следующими характеристиками:

  • Синтаксис шаблона маршрута используется для определения маршрутов с помощью параметров размеченного маршрута.
  • Допускается конфигурация конечной точки в стандартном стиле и с атрибутами.
  • IRouteConstraint используется для определения того, содержит ли параметр URL-адреса допустимое значение для ограничения заданной конечной точки.
  • Модели приложения, такие как MVC и Razor Pages, регистрируют все свои маршруты, имеющие предсказуемую реализацию сценариев маршрутизации.
  • Отклик может использовать маршрутизацию для формирования URL-адресов (например, для перенаправления или ссылок) на основе сведений о маршруте, что позволяет избежать жесткого задания URL-адресов и упрощает поддержку.
  • Формирование URL-адреса основано на маршрутах, которые поддерживают произвольную расширяемость. IUrlHelper предоставляет методы для создания URL-адресов.

Функция маршрутизации подключается к конвейеру ПО промежуточного слоя с помощью класса RouterMiddleware. ASP.NET Core MVC добавляет функцию маршрутизации в конвейер ПО промежуточного слоя в процессе настройки и обрабатывает маршрутизацию в приложениях MVC и Razor Pages. Сведения об использовании маршрутизации в виде отдельного компонента см. в руководстве по использованию ПО промежуточного слоя маршрутизации.

Соответствие URL-адресов

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

Входящие запросы поступают в объект RouterMiddleware, который вызывает метод RouteAsync для каждого маршрута по порядку. Экземпляр IRouter решает, следует ли обрабатывать запрос, присваивая свойству RouteContext.Handler значение RequestDelegate, отличное от NULL. Если маршрут задает обработчик для запроса, обработка маршрутов останавливается и обработчик вызывается для обработки запроса. Если обработчик маршрута для обработки запроса не найден, ПО промежуточного слоя передает запрос следующему ПО промежуточного слоя в конвейере запросов.

Основные входные данные метода RouteAsync — это объект RouteContext.HttpContext, связанный с текущим запросом. RouteContext.Handler и RouteContext.RouteData — это выходные значения, заданные после нахождения соответствующего маршрута.

При нахождении соответствия, которое вызывает RouteAsync, свойствам RouteContext.RouteData также присваиваются значения с учетом уже выполненной на текущий момент обработки.

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

RouteData.DataTokens — это контейнер свойств с дополнительными данными, связанными с соответствующим маршрутом. Свойства DataTokens обеспечивают связывание данных состояния с каждым маршрутом, что позволяет приложению принимать решения в зависимости от соответствующего маршрута. Эти значения определяются разработчиком и никоим образом не влияют на поведение маршрутизации. Кроме того, значения, спрятанные в токенах RouteData.DataToken, могут быть любого типа в отличие от значений RouteData.Value, которые должны легко преобразовываться в строки и из строк.

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

Формирование URL-адреса

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

Формирование URL-адреса — это итеративный процесс, который начинается с того, что пользовательский код или код платформы вызывает метод GetVirtualPath коллекции маршрутов. Метод GetVirtualPath каждого маршрута вызывается по очереди, пока не будет возвращен объект VirtualPathData, отличный от NULL.

Основные входные параметры метода GetVirtualPath:

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

Совет

Можно рассматривать VirtualPathContext.Values как набор переопределений для VirtualPathContext.AmbientValues. При формировании URL-адреса производится попытка повторного использования значений маршрута из текущего запроса для создания URL-адресов для ссылок с помощью того же маршрута или значений маршрута.

Выходные данные метода GetVirtualPath содержатся в объекте VirtualPathData. Объект VirtualPathData аналогичен RouteData. VirtualPathData содержит VirtualPath для выходного URL-адреса, а также ряд дополнительных свойств, которые должны быть заданы маршрутом.

Свойство VirtualPathData.VirtualPath содержит виртуальный путь, создаваемый маршрутом. В зависимости от ситуации путь может требовать дальнейшей обработки. Чтобы представить созданный URL-адрес в формате HTML, добавьте в его начало базовый путь приложения.

VirtualPathData.Router — это ссылка на маршрут, который успешно создал URL-адрес.

Свойства VirtualPathData.DataTokens представляют собой словарь дополнительных данных, связанных с маршрутом, который создал URL-адрес. Это эквивалент объекта RouteData.DataTokens.

Создание маршрутов

Маршрутизация предоставляет класс Route в качестве стандартной реализации IRouter. Класс Route использует синтаксис шаблона маршрута для определения шаблонов, которые будут сопоставляться с путем URL-адреса при вызове метода RouteAsync. При вызове метода GetVirtualPath объект Route будет использовать тот же шаблон маршрута для создания URL-адреса.

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

MapRoute не принимает параметр обработчика маршрутов. MapRoute только добавляет маршруты, которые обрабатываются DefaultHandler. Обработчиком по умолчанию является IRouter, и обработчик может не обработать запрос. Например, ASP.NET Core MVC обычно настраивается в качестве обработчика по умолчанию, который обрабатывает только те запросы, которые соответствуют доступным контроллеру и действию. Дополнительные сведения о маршрутизации в MVC см. в разделе Маршрутизация к действиям контроллера в ASP.NET Core.

В следующем коде приводится пример вызова MapRoute, используемого в типичном определении маршрута ASP.NET Core MVC:

routes.MapRoute(
    name: "default",
    template: "{controller=Home}/{action=Index}/{id?}");

Этот шаблон соответствует пути URL-адреса и извлекает значения маршрута. Например, путь /Products/Details/17 создает следующие значения маршрута: { controller = Products, action = Details, id = 17 }.

Значения маршрута определяются с помощью разделения пути URL-адреса на сегменты и сопоставления каждого сегмента с именем параметра маршрута в шаблоне маршрута. Параметры маршрута являются именованными. Параметры определяются путем заключения имени параметра в фигурные скобки { ... }.

Приведенный выше шаблон может также соответствовать пути URL-адреса /. В этом случае он предоставляет значения { controller = Home, action = Index }. Связано это с тем, что параметры маршрута {controller} и {action} имеют значения по умолчанию, а параметр маршрута id является необязательным. Значение по умолчанию для параметра маршрута определяется с помощью знака равенства (=), за которым следует значение после имени параметра. Вопросительный знак (?) после имени параметра маршрута определяет параметр как необязательный.

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

В следующем примере в определении параметра маршрута {id:int} определяется ограничение маршрута для параметра маршрута id:

routes.MapRoute(
    name: "default",
    template: "{controller=Home}/{action=Index}/{id:int}");

Этот шаблон соответствует такому пути URL-адреса, как /Products/Details/17, но не /Products/Details/Apples. Ограничения маршрута реализуют интерфейс IRouteConstraint и проверяют значения маршрута. В этом примере значение маршрута id должно иметь возможность преобразования в целое число. Более подробное описание ограничений маршрутов, предоставляемых платформой, см. в разделе Справочник по ограничениям маршрутов.

Дополнительные перегрузки MapRoute принимают значения для параметров constraints, dataTokens и defaults. Типичное применение этих параметров состоит в передаче анонимно типизированного объекта, причем имена свойств анонимного типа соответствуют именам параметров маршрута.

В следующих примерах MapRoute создаются эквивалентные маршруты:

routes.MapRoute(
    name: "default_route",
    template: "{controller}/{action}/{id?}",
    defaults: new { controller = "Home", action = "Index" });

routes.MapRoute(
    name: "default_route",
    template: "{controller=Home}/{action=Index}/{id?}");

Совет

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

В следующем примере показано еще несколько сценариев:

routes.MapRoute(
    name: "blog",
    template: "Blog/{*article}",
    defaults: new { controller = "Blog", action = "ReadArticle" });

Предыдущий шаблон соответствует такому пути URL-адреса, как /Blog/All-About-Routing/Introduction, и извлекает значения { controller = Blog, action = ReadArticle, article = All-About-Routing/Introduction }. Значения маршрута по умолчанию для controller и action предоставляются маршрутом несмотря на то, что в шаблоне нет соответствующих параметров маршрута. Значения по умолчанию можно указать в шаблоне маршрута. Параметр маршрута article определяется как универсальный с помощью звездочки (*) перед его именем. Универсальные параметры маршрута служат для фиксации оставшейся части пути URL-адреса, а также могут соответствовать пустой строке.

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

routes.MapRoute(
    name: "us_english_products",
    template: "en-US/Products/{id}",
    defaults: new { controller = "Products", action = "Details" },
    constraints: new { id = new IntRouteConstraint() },
    dataTokens: new { locale = "en-US" });

Предыдущий шаблон будет соответствовать такому пути URL-адреса, как /en-US/Products/5, и будет извлекать значения { controller = Products, action = Details, id = 5 } и токены данных { locale = en-US }.

Токены в окне "Локальные"

<a name="route-class-url-generation">Формирование URL-адреса класса маршрута

Класс Route может также формировать URL-адрес, объединяя набор значений маршрута с шаблоном маршрута. С логической точки зрения, этот процесс обратен сопоставлению пути URL-адреса.

Совет

Чтобы лучше понять процесс формирования URL-адреса, представьте себе, какой URL-адрес нужно создать, а затем подумайте, как шаблон маршрута будет сопоставляться с этим URL-адресом. Какие значения будут получены? Примерно так производится формирование URL-адреса в классе Route.

В следующем примере используется общий маршрут по умолчанию ASP.NET Core MVC:

routes.MapRoute(
    name: &quot;default&quot;,
    template: &quot;{controller=Home}/{action=Index}/{id?}");

При значениях маршрута { controller = Products, action = List } создается URL-адрес /Products/List. Значения маршрута заменяются на соответствующие параметры маршрута для образования пути URL-адреса. Так как id является необязательным параметром маршрута, URL-адрес успешно создается без значения для id.

При значениях маршрута { controller = Home, action = Index } создается URL-адрес /. Предоставленные значения маршрута соответствуют значениям по умолчанию, поэтому сегменты, соответствующие значениям по умолчанию, можно спокойно опустить.

Оба созданных URL-адреса будут совершать круговой путь с таким же определением маршрута (/Home/Index и /) и предоставлять те же значения маршрута, которые использовались для формирования URL-адреса.

Примечание

Приложение, использующее платформу ASP.NET Core MVC, должно создавать URL-адреса с помощью объекта UrlHelper, а не вызывать маршрутизацию напрямую.

Дополнительные сведения о формировании URL-адресов см. в справочнике по формированию URL-адресов.

Использование ПО промежуточного слоя маршрутизации

Ссылка на метапакет Microsoft.AspNetCore.App в файле проекта приложения.

Добавьте маршрутизацию в контейнер службы в файле Startup.ConfigureServices:

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

Маршруты должны настраиваться в методе Startup.Configure. В этом примере приложения используются следующие API:

var trackPackageRouteHandler = new RouteHandler(context =>
{
    var routeValues = context.GetRouteData().Values;
    return context.Response.WriteAsync(
        $"Hello! Route values: {string.Join(", ", routeValues)}");
});

var routeBuilder = new RouteBuilder(app, trackPackageRouteHandler);

routeBuilder.MapRoute(
    "Track Package Route",
    "package/{operation:regex(^track|create$)}/{id:int}");

routeBuilder.MapGet("hello/{name}", context =>
{
    var name = context.GetRouteValue("name");
    // The route handler when HTTP GET "hello/<anything>" matches
    // To match HTTP GET "hello/<anything>/<anything>, 
    // use routeBuilder.MapGet("hello/{*name}"
    return context.Response.WriteAsync($"Hi, {name}!");
});

var routes = routeBuilder.Build();
app.UseRouter(routes);

В таблице ниже приведены ответы с данными универсальными кодами ресурсов (URI).

URI Ответ
/package/create/3 Hello! Значения маршрута: [operation, create], [id, 3]
/package/track/-3 Hello! Значения маршрута: [operation, track], [id, -3]
/package/track/-3/ Hello! Значения маршрута: [operation, track], [id, -3]
/package/track/ Запрос не дал результата, нет совпадений.
GET /hello/Joe Hi, Joe!
POST /hello/Joe Запрос не дал результата, совпадение только с HTTP GET.
GET /hello/Joe/Smith Запрос не дал результата, нет совпадений.

Если вы настраиваете один маршрут, вызовите UseRouter, передав экземпляр IRouter. Использовать RouteBuilder не нужно.

Платформа предоставляет наборов методов расширения для создания маршрутов (RequestDelegateRouteBuilderExtensions):

Некоторые из перечисленных методов, такие как MapGet, требуют RequestDelegate. RequestDelegate используется в качестве обработчика маршрутов при совпадении маршрута. Другие методы из этого семейства позволяют настраивать конвейер ПО промежуточного слоя, который будет использоваться в качестве обработчика маршрутов. Если метод Map* не принимает обработчик, например MapRoute, он будет использовать объект DefaultHandler.

Методы Map[Verb] используют ограничения, чтобы ограничить маршрут к HTTP-команде в имени метода. Например, см. класс MapGet и тип MapVerb.

Справочник по шаблону маршрута

Токены в фигурных скобках ({ ... }) определяют параметры маршрута, которые будут привязаны при совпадении маршрута. В сегменте маршрута можно определить несколько параметров маршрута, но они должны разделяться литеральным значением. Например, {controller=Home}{action=Index} будет недопустимым маршрутом, так как между {controller} и {action} нет литерального значения. Эти параметры маршрута должны иметь имена, и для них могут быть определены дополнительные атрибуты.

Весь текст, кроме параметров маршрута (например, {id}) и разделителя пути /, должен соответствовать тексту в URL-адресе. Сопоставление текста производится без учета регистра на основе декодированного представления путь URL-адреса. Для сопоставления с литеральным разделителем параметров маршрута ({ или }) разделитель следует экранировать путем повтора символа ({{ или }}).

Шаблоны URL-адресов, которые пытаются получить имя файла с необязательным расширением, имеют свои особенности. Например, рассмотрим шаблон files/{filename}.{ext?}. Когда значения для filename и ext существуют, заполняются оба значения. Если в URL-адресе есть только значение для filename, маршрут совпадает, так как точка в конце (.) является необязательной. Следующие URL-адреса соответствуют этому маршруту:

  • /files/myFile.txt
  • /files/myFile

Вы можете использовать звездочку (*) в качестве префикса параметра маршрута для привязки к остальной части URI. Такой параметр называется универсальным. Например, blog/{*slug} соответствует любому URI, начинающемуся с сегмента /blog, за которым следует любое значение, присваиваемое в качестве значения маршрута slug. Универсальные параметры также могут соответствовать пустой строке.

Универсальный параметр экранирует соответствующие символы, если маршрут использует для формирования URL-адрес, включая символы разделителей пути (/). Например, маршрут foo/{*path} со значениями маршрутов { path = "my/path" } формирует foo/my%2Fpath. Обратите внимание на экранированный знак косой черты.

Параметры маршрута могут иметь значения по умолчанию. Они указываются после имени параметра и знака равенства (=). Например, {controller=Home} определяет Home в качестве значения по умолчанию для controller. Значение по умолчанию используется, если для параметра нет значения в URL-адресе. Параметры маршрута могут быть необязательными (для этого необходимо добавить вопросительный знак (?) в конец имени параметра, например id?). Различие между необязательными параметрами и параметрами маршрута по умолчанию в том, что вторые всегда имеют значения — необязательный параметр имеет значение, только если оно предоставлено URL-адресом запроса.

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

Имя и аргументы ограничения передаются в службу IInlineConstraintResolver для создания экземпляра интерфейса IRouteConstraint, который будет использоваться при обработке URL-адреса. Например, в шаблоне маршрута blog/{article:minlength(10)} определяется ограничение minlength с аргументом 10. Более подробное описание ограничений маршрутов и список ограничений, предоставляемых платформой, см. в разделе Справочник по ограничениям маршрутов.

В приведенной ниже таблице показаны некоторые примеры шаблонов маршрутов и их поведение.

Шаблон маршрута Пример соответствующего URI URI запроса…
hello /hello Соответствует только одному пути /hello.
{Page=Home} / Соответствует и задает для параметра Page значение Home.
{Page=Home} /Contact Соответствует и задает для параметра Page значение Contact.
{controller}/{action}/{id?} /Products/List Сопоставляется с контроллером Products и действием List.
{controller}/{action}/{id?} /Products/Details/123 Сопоставляется с контроллером Products и действием Details (id имеет значение 123).
{controller=Home}/{action=Index}/{id?} / Сопоставляется с контроллером Home и методом Index (id пропускается).

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

Совет

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

Справочник по ограничениям маршрутов

Ограничения маршрута применяются, когда найдено соответствие входящему URL-адресу и путь URL-адреса был разобран на значения маршрута. Как правило, ограничения маршрута служат для проверки значения маршрута, связанного посредством шаблона маршрута, и принятия решения касательно того, является ли значение приемлемым (да или нет). Некоторые ограничения маршрута используют данные, не относящиеся к значению маршрута, для определения возможности маршрутизации запроса. Например, HttpMethodRouteConstraint может принимать или отклонять запрос в зависимости от HTTP-команды. Ограничения используются в маршрутизации запросов и создании ссылок.

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

Не используйте ограничения для проверки входных данных. Если ограничения используются для проверки входных данных, недопустимые входные данные будут вызывать ошибку 404 (не найдено) вместо ошибки 400 (неверный запрос) с соответствующим сообщением. Ограничения маршрутов следует использовать для разрешения неоднозначности похожих маршрутов, а не для проверки входных данных определенного маршрута.

В приведенной ниже таблице показаны примеры ограничения маршрутов и их ожидаемое поведение.

ограничение Пример Примеры совпадений Примечания
int {id:int} 123456789, -123456789 Соответствует любому целому числу
bool {active:bool} true, FALSE Соответствует true или false (без учета регистра)
datetime {dob:datetime} 2016-12-31, 2016-12-31 7:32pm Соответствует допустимому значению DateTime для инвариантного языка и региональных параметров. См. предупреждение выше.
decimal {price:decimal} 49.99, -1,000.01 Соответствует допустимому значению decimal для инвариантного языка и региональных параметров. См. предупреждение выше.
double {weight:double} 1.234, -1,001.01e8 Соответствует допустимому значению double для инвариантного языка и региональных параметров. См. предупреждение выше.
float {weight:float} 1.234, -1,001.01e8 Соответствует допустимому значению float для инвариантного языка и региональных параметров. См. предупреждение выше.
guid {id:guid} CD2C1638-1638-72D5-1638-DEADBEEF1638, {CD2C1638-1638-72D5-1638-DEADBEEF1638} Соответствует допустимому значению Guid
long {ticks:long} 123456789, -123456789 Соответствует допустимому значению long
minlength(value) {username:minlength(4)} Rick Строка должна содержать не менее 4 символов
maxlength(value) {filename:maxlength(8)} Richard Строка должна содержать не более 8 символов
length(length) {filename:length(12)} somefile.txt Длина строки должна составлять ровно 12 символов
length(min,max) {filename:length(8,16)} somefile.txt Строка должна содержать от 8 до 16 символов
min(value) {age:min(18)} 19 Целочисленное значение не меньше 18
max(value) {age:max(120)} 91 Целочисленное значение не больше 120
range(min,max) {age:range(18,120)} 91 Целочисленное значение от 18 до 120
alpha {name:alpha} Rick Строка должна состоять из одной или нескольких букв (a-z, без учета регистра)
regex(expression) {ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} 123-45-6789 Строка должна соответствовать регулярному выражению (см. советы по определению регулярного выражения)
required {name:required} Rick Определяет обязательное наличие значения, не относящегося к параметру, во время формирования URL-адреса

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

[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }

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

Ограничения маршрута, которые проверяют URL-адрес и могут быть преобразованы в тип CLR (например, int или DateTime), всегда используют инвариантные язык и региональные параметры. Эти ограничения предполагают, что URL-адрес является нелокализуемым. Предоставляемые платформой ограничения маршрутов не изменяют значения, хранящиеся в значениях маршрута. Все значения маршрута, переданные из URL-адреса, сохраняются как строки. Например, ограничение float пытается преобразовать значение маршрута в число с плавающей запятой, но преобразованное значение служит только для проверки возможности такого преобразования.

Регулярные выражения

В платформе ASP.NET Core в конструктор регулярных выражений добавляются члены RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant. Описание этих членов см. в разделе RegexOptions.

В регулярных выражениях применяются разделители и токены, аналогичные используемым функцией маршрутизации и в языке C#. Токены регулярного выражения должны быть экранированы. Чтобы использовать регулярное выражение ^\d{3}-\d{2}-\d{4}$ при маршрутизации, выражение должно иметь символы \ (обратная косая черта), представленные в строке в виде символов \\ (двойная обратная косая черта) в исходном файле C#, для экранирования escape-символов строки \ (если не используются буквальные строковые литералы). Чтобы экранировать символы разделения параметров маршрутизации ({, }, [, ]), используйте их дважды в выражении ({{, }, [[, ]]). В следующей таблице показаны регулярные выражения и их экранированные варианты.

Регулярное выражение Экранированное регулярное выражение
^\d{3}-\d{2}-\d{4}$ ^\\d{{3}}-\\d{{2}}-\\d{{4}}$
^[a-z]{2}$ ^[[a-z]]{{2}}$

Регулярные выражения, используемые при маршрутизации, часто начинаются с символа карета (^) и соответствуют начальной позиции строки. Выражения часто заканчиваются знаком доллара ($) и соответствуют концу строки. Благодаря символам ^ и $ регулярное выражение сопоставляется со всем значением параметра маршрута. Если символы ^ и $ отсутствуют, регулярное выражение сопоставляется с любой подстрокой внутри строки, что обычно нежелательно. В следующей таблице представлен ряд примеров и объясняются причины соответствия или несоответствия.

Выражение Строка Соответствие Добавление примечаний
[a-z]{2} hello Да Соответствие подстроки
[a-z]{2} 123abc456 Да Соответствие подстроки
[a-z]{2} mz Да Соответствует выражению
[a-z]{2} MZ Да Без учета регистра
^[a-z]{2}$ hello Нет См. замечания, касающиеся символов ^ и $, выше
^[a-z]{2}$ 123abc456 Нет См. замечания, касающиеся символов ^ и $, выше

Дополнительные сведения о синтаксисе регулярных выражений см. в статье Регулярные выражения в .NET Framework.

Чтобы ограничить возможные значения параметра набором известных значений, используйте регулярное выражение. Например, при использовании выражения {action:regex(^(list|get|create)$)} значение маршрута action будет соответствовать только list, get или create. При передаче в словарь ограничений строка ^(list|get|create)$ будет эквивалентной. Ограничения, которые передаются в словарь ограничений (то есть не являются встроенными ограничениями шаблона) и не соответствуют одному из известных ограничений, также рассматриваются как регулярные выражения.

Пользовательские ограничения маршрутов

Помимо встроенных ограничений маршрутов пользовательские ограничения маршрутов можно создать путем внедрения интерфейса IRouteConstraint. Интерфейс IRouteConstraint содержит один метод, Match, который возвращает true, если ограничение удовлетворяется, и false — если нет.

Чтобы применить пользовательский метод IRouteConstraint, тип ограничения маршрута необходимо зарегистрировать с помощью ConstraintMap приложения в контейнере службы приложения. Объект ConstraintMap — это словарь, который сопоставляет ключи ограничений пути с реализациями IRouteConstraint, которые проверяют эти ограничения. ConstraintMap приложения можно преобразовать в Startup.ConfigureServices как часть вызова services.AddRouting или путем настройки RouteOptions непосредственно с помощью services.Configure<RouteOptions>. Пример:

services.AddRouting(options =>
{
    options.ConstraintMap.Add("customName", typeof(MyCustomConstraint));
});

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

[HttpGet("{id:customName}")]
public ActionResult<string> Get(string id)

Справочник по формированию URL-адресов

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

app.Run(async (context) =>
{
    var dictionary = new RouteValueDictionary
    {
        { "operation", "create" },
        { "id", 123}
    };

    var vpc = new VirtualPathContext(context, null, dictionary, 
        "Track Package Route");
    var path = routes.GetVirtualPath(vpc).VirtualPath;

    context.Response.ContentType = "text/html";
    await context.Response.WriteAsync("Menu<hr/>");
    await context.Response.WriteAsync(
        $"<a href='{path}'>Create Package 123</a><br/>");
});

В результате приведенного выше примера создается VirtualPath со значением /package/create/123. Словарь предоставляет значения маршрута operation и id шаблона "Отслеживание маршрута пакета", package/{operation}/{id}. Дополнительные сведения см. в примере кода в разделе Использование ПО промежуточного слоя маршрутизации или в примере приложения.

Второй параметр конструктора VirtualPathContext — это коллекция значений окружения. Значения окружения упрощают разработку, ограничивая число значений, которые необходимо указывать в определенном контексте запроса. Текущие значения маршрута текущего запроса считаются значениями окружения для создания ссылки. В приложении ASP.NET MVC в действии About контроллера HomeController не нужно задавать значение маршрута контроллера, указывающее на действие Index— используется значение окружения Home.

Значения окружения, которые не соответствуют параметру, игнорируются. Значения окружения также не учитываются, когда явно указанное значение переопределяет значение окружения. Сопоставление выполняется слева направо в URL-адресе.

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

Значения окружения Явные значения Результат
controller = "Home" action = "About" /Home/About
controller = "Home" controller = "Order", action = "About" /Order/About
controller = "Home", color = "Red" action = "About" /Home/About
controller = "Home" action = "About", color = "Red" /Home/About?color=Red

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

routes.MapRoute("blog_route", "blog/{*slug}",
    defaults: new { controller = "Blog", action = "ReadPost" });

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

Сложные сегменты

Сложные сегменты (например, [Route("/x{token}y")]) обрабатываются путем "нежадного" сопоставления литералов справа налево. Подробные сведения о сопоставлении сложных сегментов см. в этом коде. Пример кода не используется в ASP.NET Core, но он предоставляет подробное объяснение сложных сегментов.