ASP.NET Core 中的路由Routing in ASP.NET Core

作者:Ryan NowakKirk LarkinnRick AndersonBy Ryan Nowak, Kirk Larkin, and Rick Anderson

路由负责匹配传入的 HTTP 请求,然后将这些请求发送到应用的可执行终结点。Routing is responsible for matching incoming HTTP requests and dispatching those requests to the app's executable endpoints. 终结点是应用的可执行请求处理代码单元。Endpoints are the app's units of executable request-handling code. 终结点在应用中进行定义,并在应用启动时进行配置。Endpoints are defined in the app and configured when the app starts. 终结点匹配过程可以从请求的 URL 中提取值,并为请求处理提供这些值。The endpoint matching process can extract values from the request's URL and provide those values for request processing. 通过使用应用中的终结点信息,路由还能生成映射到终结点的 URL。Using endpoint information from the app, routing is also able to generate URLs that map to endpoints.

应用可以使用以下内容配置路由:Apps can configure routing using:

  • ControllersControllers
  • Razor PagesRazor Pages
  • SignalR
  • gRPC 服务gRPC Services
  • 启用终结点的中间件,例如运行状况检查Endpoint-enabled middleware such as Health Checks.
  • 通过路由注册的委托和 Lambda。Delegates and lambdas registered with routing.

本文档介绍 ASP.NET Core 路由的较低级别详细信息。This document covers low-level details of ASP.NET Core routing. 有关配置路由的信息,请参阅:For information on configuring routing:

本文档中所述的终结点路由系统适用于 ASP.NET Core 3.0 及更高版本。The endpoint routing system described in this document applies to ASP.NET Core 3.0 and later. 有关以前基于 IRouter 的路由系统信息,请使用以下方法之一选择 ASP.NET Core 2.1 版本:For information on the previous routing system based on IRouter, select the ASP.NET Core 2.1 version using one of the following approaches:

查看或下载示例代码如何下载View or download sample code (how to download)

此文档的下载示例由特定 Startup 类启用。The download samples for this document are enabled by a specific Startup class. 若要运行特定的示例,请修改 Program.cs,以便调用所需的 Startup 类。To run a specific sample, modify Program.cs to call the desired Startup class.

路由基础知识Routing basics

所有 ASP.NET Core 模板都包括生成的代码中的路由。All ASP.NET Core templates include routing in the generated code. 路由在 Startup.Configure 中的中间件管道中进行注册。Routing is registered in the middleware pipeline in Startup.Configure.

以下代码演示路由的基本示例:The following code shows a basic example of routing:

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

路由使用一对由 UseRoutingUseEndpoints 注册的中间件:Routing uses a pair of middleware, registered by UseRouting and UseEndpoints:

  • UseRouting 向中间件管道添加路由匹配。UseRouting adds route matching to the middleware pipeline. 此中间件会查看应用中定义的终结点集,并根据请求选择最佳匹配This middleware looks at the set of endpoints defined in the app, and selects the best match based on the request.
  • UseEndpoints 向中间件管道添加终结点执行。UseEndpoints adds endpoint execution to the middleware pipeline. 它会运行与所选终结点关联的委托。It runs the delegate associated with the selected endpoint.

前面的示例包含使用 MapGet 方法的单一路由到代码终结点:The preceding example includes a single route to code endpoint using the MapGet method:

  • 当 HTTP GET 请求发送到根 URL / 时:When an HTTP GET request is sent to the root URL /:
    • 将执行显示的请求委托。The request delegate shown executes.
    • Hello World! 会写入 HTTP 响应。Hello World! is written to the HTTP response. 默认情况下,根 URL /https://localhost:5001/By default, the root URL / is https://localhost:5001/.
  • 如果请求方法不是 GET 或根 URL 不是 /,则无路由匹配,并返回 HTTP 404。If the request method is not GET or the root URL is not /, no route matches and an HTTP 404 is returned.

终结点Endpoint

MapGet 方法用于定义终结点。The MapGet method is used to define an endpoint. 终结点可以:An endpoint is something that can be:

  • 通过匹配 URL 和 HTTP 方法来选择。Selected, by matching the URL and HTTP method.
  • 通过运行委托来执行。Executed, by running the delegate.

可通过应用匹配和执行的终结点在 UseEndpoints 中进行配置。Endpoints that can be matched and executed by the app are configured in UseEndpoints. 例如,MapGetMapPost类似的方法将请求委托连接到路由系统。For example, MapGet, MapPost, and similar methods connect request delegates to the routing system. 其他方法可用于将 ASP.NET Core 框架功能连接到路由系统:Additional methods can be used to connect ASP.NET Core framework features to the routing system:

下面的示例演示如何使用更复杂的路由模板进行路由:The following example shows routing with a more sophisticated route template:

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} 字符串是一个路由模板。The string /hello/{name:alpha} is a route template. 用于配置终结点的匹配方式。It is used to configure how the endpoint is matched. 在这种情况下,模板将匹配:In this case, the template matches:

  • 类似 /hello/Ryan 的 URLA URL like /hello/Ryan
  • /hello/ 开头、后跟一系列字母字符的任何 URL 路径。Any URL path that begins with /hello/ followed by a sequence of alphabetic characters. :alpha 应用仅匹配字母字符的路由约束。:alpha applies a route constraint that matches only alphabetic characters. 路由约束将在本文档的后面详细介绍。Route constraints are explained later in this document.

URL 路径的第二段 {name:alpha}The second segment of the URL path, {name:alpha}:

本文档中所述的终结点路由系统是新版本 ASP.NET Core 3.0。The endpoint routing system described in this document is new as of ASP.NET Core 3.0. 但是,所有版本的 ASP.NET Core 都支持相同的路由模板功能和路由约束。However, all versions of ASP.NET Core support the same set of route template features and route constraints.

下面的示例演示如何通过运行状况检查和授权进行路由:The following example shows routing with health checks and authorization:

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 讨论问题中告诉我们。If you would like to see code comments translated to languages other than English, let us know in this GitHub discussion issue.

前面的示例展示了如何:The preceding example demonstrates how:

  • 将授权中间件与路由一起使用。The authorization middleware can be used with routing.
  • 将终结点用于配置授权行为。Endpoints can be used to configure authorization behavior.

MapHealthChecks 调用添加运行状况检查终结点。The MapHealthChecks call adds a health check endpoint. RequireAuthorization 链接到此调用会将授权策略附加到该终结点。Chaining RequireAuthorization on to this call attaches an authorization policy to the endpoint.

调用 UseAuthenticationUseAuthorization 会添加身份验证和授权中间件。Calling UseAuthentication and UseAuthorization adds the authentication and authorization middleware. 这些中间件位于 UseRoutingUseEndpoints 之间,因此它们可以:These middleware are placed between UseRouting and UseEndpoints so that they can:

  • 查看 UseRouting 选择的终结点。See which endpoint was selected by UseRouting.
  • UseEndpoints 发送到终结点之前应用授权策略。Apply an authorization policy before UseEndpoints dispatches to the endpoint.

终结点元数据Endpoint metadata

前面的示例中有两个终结点,但只有运行状况检查终结点附加了授权策略。In the preceding example, there are two endpoints, but only the health check endpoint has an authorization policy attached. 如果请求与运行状况检查终结点 /healthz 匹配,则执行授权检查。If the request matches the health check endpoint, /healthz, an authorization check is performed. 这表明,终结点可以附加额外的数据。This demonstrates that endpoints can have extra data attached to them. 此额外数据称为终结点元数据:This extra data is called endpoint metadata:

  • 可以通过路由感知中间件来处理元数据。The metadata can be processed by routing-aware middleware.
  • 元数据可以是任意的 .NET 类型。The metadata can be of any .NET type.

路由概念Routing concepts

路由系统通过添加功能强大的终结点概念,构建在中间件管道之上。The routing system builds on top of the middleware pipeline by adding the powerful endpoint concept. 终结点代表应用的功能单元,在路由、授权和任意数量的 ASP.NET Core 系统方面彼此不同。Endpoints represent units of the app's functionality that are distinct from each other in terms of routing, authorization, and any number of ASP.NET Core's systems.

ASP.NET Core 终结点定义ASP.NET Core endpoint definition

ASP.NET Core 终结点是:An ASP.NET Core endpoint is:

以下代码显示了如何检索和检查与当前请求匹配的终结点:The following code shows how to retrieve and inspect the endpoint matching the current request:

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 中进行检索。The endpoint, if selected, can be retrieved from the HttpContext. 可以检查其属性。Its properties can be inspected. 终结点对象是不可变的,并且在创建后无法修改。Endpoint objects are immutable and cannot be modified after creation. 最常见的终结点类型是 RouteEndpointThe most common type of endpoint is a RouteEndpoint. RouteEndpoint 包括允许自己被路由系统选择的信息。RouteEndpoint includes information that allows it to be to selected by the routing system.

在前面的代码中,app.Use 配置了一个内联中间件In the preceding code, app.Use configures an in-line middleware.

下面的代码显示,根据管道中调用 app.Use 的位置,可能不存在终结点:The following code shows that, depending on where app.Use is called in the pipeline, there may not be an endpoint:

// 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 语句,这些语句显示是否已选择终结点。This preceding sample adds Console.WriteLine statements that display whether or not an endpoint has been selected. 为清楚起见,该示例将显示名称分配给提供的 / 终结点。For clarity, the sample assigns a display name to the provided / endpoint.

使用 / URL 运行此代码将显示:Running this code with a URL of / displays:

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

使用任何其他 URL 运行此代码将显示:Running this code with any other URL displays:

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

此输出说明:This output demonstrates that:

  • 调用 UseRouting 之前,终结点始终为 null。The endpoint is always null before UseRouting is called.
  • 如果找到匹配项,则 UseRoutingUseEndpoints 之间的终结点为非 null。If a match is found, the endpoint is non-null between UseRouting and UseEndpoints.
  • 如果找到匹配项,则 UseEndpoints 中间件即为终端。The UseEndpoints middleware is terminal when a match is found. 稍后会在本文档中定义终端中间件Terminal middleware is defined later in this document.
  • 仅当找不到匹配项时才执行 UseEndpoints 后的中间件。The middleware after UseEndpoints execute only when no match is found.

UseRouting 中间件使用 SetEndpoint 方法将终结点附加到当前上下文。The UseRouting middleware uses the SetEndpoint method to attach the endpoint to the current context. 可以将 UseRouting 中间件替换为自定义逻辑,同时仍可获得使用终结点的益处。It's possible to replace the UseRouting middleware with custom logic and still get the benefits of using endpoints. 终结点是中间件等低级别基元,不与路由实现耦合。Endpoints are a low-level primitive like middleware, and aren't coupled to the routing implementation. 大多数应用都不需要将 UseRouting 替换为自定义逻辑。Most apps don't need to replace UseRouting with custom logic.

UseEndpoints 中间件旨在与 UseRouting 中间件配合使用。The UseEndpoints middleware is designed to be used in tandem with the UseRouting middleware. 执行终结点的核心逻辑并不复杂。The core logic to execute an endpoint isn't complicated. 使用 GetEndpoint 检索终结点,然后调用其 RequestDelegate 属性。Use GetEndpoint to retrieve the endpoint, and then invoke its RequestDelegate property.

下面的代码演示中间件如何影响或响应路由:The following code demonstrates how middleware can influence or react to routing:

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

前面的示例演示两个重要概念:The preceding example demonstrates two important concepts:

  • 中间件可以在 UseRouting 之前运行,以修改路由操作的数据。Middleware can run before UseRouting to modify the data that routing operates upon.
  • 中间件可以在 UseRoutingUseEndpoints 之间运行,以便在执行终结点前处理路由结果。Middleware can run between UseRouting and UseEndpoints to process the results of routing before the endpoint is executed.
    • UseRoutingUseEndpoints 之间运行的中间件:Middleware that runs between UseRouting and UseEndpoints:
      • 通常会检查元数据以了解终结点。Usually inspects metadata to understand the endpoints.
      • 通常会根据 UseAuthorizationUseCors 做出安全决策。Often makes security decisions, as done by UseAuthorization and UseCors.
    • 中间件和元数据的组合允许按终结点配置策略。The combination of middleware and metadata allows configuring policies per-endpoint.

前面的代码显示了支持按终结点策略的自定义中间件示例。The preceding code shows an example of a custom middleware that supports per-endpoint policies. 中间件将访问敏感数据的审核日志写入控制台。The middleware writes an audit log of access to sensitive data to the console. 可以将中间件配置为审核具有 AuditPolicyAttribute 元数据的终结点。The middleware can be configured to audit an endpoint with the AuditPolicyAttribute metadata. 此示例演示选择加入模式,其中仅审核标记为敏感的终结点。This sample demonstrates an opt-in pattern where only endpoints that are marked as sensitive are audited. 例如,可以反向定义此逻辑,从而审核未标记为安全的所有内容。It's possible to define this logic in reverse, auditing everything that isn't marked as safe, for example. 终结点元数据系统非常灵活。The endpoint metadata system is flexible. 此逻辑可以以任何适合用例的方法进行设计。This logic could be designed in whatever way suits the use case.

前面的示例代码旨在演示终结点的基本概念。The preceding sample code is intended to demonstrate the basic concepts of endpoints. 该示例不应在生产环境中使用。The sample is not intended for production use. 审核日志中间件的更完整版本如下:A more complete version of an audit log middleware would:

  • 记录到文件或数据库。Log to a file or database.
  • 包括详细信息,如用户、IP 地址、敏感终结点的名称等。Include details such as the user, IP address, name of the sensitive endpoint, and more.

审核策略元数据 AuditPolicyAttribute 定义为一个 Attribute,便于和基于类的框架(如控制器和 SignalR)结合使用。The audit policy metadata AuditPolicyAttribute is defined as an Attribute for easier use with class-based frameworks such as controllers and SignalR. 使用路由到代码时:When using route to code:

  • 元数据附有生成器 API。Metadata is attached with a builder API.
  • 基于类的框架在创建终结点时,包含了相应方法和类的所有特性。Class-based frameworks include all attributes on the corresponding method and class when creating endpoints.

对于元数据类型,最佳做法是将它们定义为接口或特性。The best practices for metadata types are to define them either as interfaces or attributes. 接口和特性允许代码重用。Interfaces and attributes allow code reuse. 元数据系统非常灵活,无任何限制。The metadata system is flexible and doesn't impose any limitations.

比较终端中间件和路由Comparing a terminal middleware and routing

下面的代码示例对比使用中间件和使用路由:The following code sample contrasts using middleware with using routing:

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: 显示的中间件样式是终端中间件。The style of middleware shown with Approach 1: is terminal middleware. 之所以称之为终端中间件,是因为它执行匹配的操作:It's called terminal middleware because it does a matching operation:

  • 前面示例中的匹配操作是用于中间件的 Path == "/" 和用于路由的 Path == "/Movie"The matching operation in the preceding sample is Path == "/" for the middleware and Path == "/Movie" for routing.
  • 如果匹配成功,它将执行一些功能并返回,而不是调用 next 中间件。When a match is successful, it executes some functionality and returns, rather than invoking the next middleware.

之所以称之为终端中间件,是因为它会终止搜索,执行一些功能,然后返回。It's called terminal middleware because it terminates the search, executes some functionality, and then returns.

比较终端中间件和路由:Comparing a terminal middleware and routing:

  • 这两种方法都允许终止处理管道:Both approaches allow terminating the processing pipeline:
    • 中间件通过返回而不是调用 next 来终止管道。Middleware terminates the pipeline by returning rather than invoking next.
    • 终结点始终是终端。Endpoints are always terminal.
  • 终端中间件允许在管道中的任意位置放置中间件:Terminal middleware allows positioning the middleware at an arbitrary place in the pipeline:
  • 终端中间件允许任意代码确定中间件匹配的时间:Terminal middleware allows arbitrary code to determine when the middleware matches:
    • 自定义路由匹配代码可能比较复杂,且难以正确编写。Custom route matching code can be verbose and difficult to write correctly.
    • 路由为典型应用提供了简单的解决方案。Routing provides straightforward solutions for typical apps. 大多数应用不需要自定义路由匹配代码。Most apps don't require custom route matching code.
  • 带有中间件的终结点接口,如 UseAuthorizationUseCorsEndpoints interface with middleware such as UseAuthorization and UseCors.
    • 通过 UseAuthorizationUseCors 使用终端中间件需要与授权系统进行手动交互。Using a terminal middleware with UseAuthorization or UseCors requires manual interfacing with the authorization system.

终结点定义以下两者:An endpoint defines both:

  • 用于处理请求的委托。A delegate to process requests.
  • 任意元数据的集合。A collection of arbitrary metadata. 元数据用于实现横切关注点,该实现基于附加到每个终结点的策略和配置。The metadata is used to implement cross-cutting concerns based on policies and configuration attached to each endpoint.

终端中间件可以是一种有效的工具,但可能需要:Terminal middleware can be an effective tool, but can require:

  • 大量的编码和测试。A significant amount of coding and testing.
  • 手动与其他系统集成,以实现所需的灵活性级别。Manual integration with other systems to achieve the desired level of flexibility.

请考虑在写入终端中间件之前与路由集成。Consider integrating with routing before writing a terminal middleware.

MapMapWhen 相集成的现有终端中间件通常会转换为路由感知终结点。Existing terminal middleware that integrates with Map or MapWhen can usually be turned into a routing aware endpoint. MapHealthChecks 演示了路由器软件的模式:MapHealthChecks demonstrates the pattern for router-ware:

下面的代码演示了 MapHealthChecks 的使用方法:The following code shows use of 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!");
        });
    });
}

前面的示例说明了为什么返回生成器对象很重要。The preceding sample shows why returning the builder object is important. 返回生成器对象后,应用开发者可以配置策略,如终结点的授权。Returning the builder object allows the app developer to configure policies such as authorization for the endpoint. 在此示例中,运行状况检查中间件不与授权系统直接集成。In this example, the health checks middleware has no direct integration with the authorization system.

元数据系统的创建目的是为了响应扩展性创建者使用终端中间件时遇到的问题。The metadata system was created in response to the problems encountered by extensibility authors using terminal middleware. 对于每个中间件,实现自己与授权系统的集成都会出现问题。It's problematic for each middleware to implement its own integration with the authorization system.

URL 匹配URL matching

  • 是路由将传入请求匹配到终结点的过程。Is the process by which routing matches an incoming request to an endpoint.
  • 基于 URL 路径中的数据和标头。Is based on data in the URL path and headers.
  • 可进行扩展,以考虑请求中的任何数据。Can be extended to consider any data in the request.

当路由中间件执行时,它会设置 Endpoint,并将值从当前请求路由到 HttpContext 上的请求功能When a routing middleware executes, it sets an Endpoint and route values to a request feature on the HttpContext from the current request:

  • 调用 GetEndpoint 获取终结点。Calling HttpContext.GetEndpoint gets the endpoint.
  • HttpRequest.RouteValues 将获取路由值的集合。HttpRequest.RouteValues gets the collection of route values.

在路由中间件之后运行的中间件可以检查终结点并采取措施。Middleware running after the routing middleware can inspect the endpoint and take action. 例如,授权中间件可以在终结点的元数据集合中询问授权策略。For example, an authorization middleware can interrogate the endpoint's metadata collection for an authorization policy. 请求处理管道中的所有中间件执行后,将调用所选终结点的委托。After all of the middleware in the request processing pipeline is executed, the selected endpoint's delegate is invoked.

终结点路由中的路由系统负责所有的调度决策。The routing system in endpoint routing is responsible for all dispatching decisions. 中间件基于所选终结点应用策略,因此重要的是:Because the middleware applies policies based on the selected endpoint, it's important that:

  • 任何可能影响发送或安全策略应用的决定都应在路由系统中做出。Any decision that can affect dispatching or the application of security policies is made inside the routing system.

警告

对于后向兼容性,执行 Controller 或 Razor Pages 终结点委托时,应根据迄今执行的请求处理将 RouteContext.RouteData 的属性设为适当的值。For backwards-compatibility, when a Controller or Razor Pages endpoint delegate is executed, the properties of RouteContext.RouteData are set to appropriate values based on the request processing performed thus far.

在未来的版本中,会将 RouteContext 类型标记为已过时:The RouteContext type will be marked obsolete in a future release:

  • RouteData.Values 迁移到 HttpRequest.RouteValuesMigrate RouteData.Values to HttpRequest.RouteValues.
  • 迁移 RouteData.DataTokens 以从终结点元数据检索 IDataTokensMetadataMigrate RouteData.DataTokens to retrieve IDataTokensMetadata from the endpoint metadata.

URL 匹配在可配置的阶段集中运行。URL matching operates in a configurable set of phases. 在每个阶段中,输出为一组匹配项。In each phase, the output is a set of matches. 下一阶段可以进一步缩小这一组匹配项。The set of matches can be narrowed down further by the next phase. 路由实现不保证匹配终结点的处理顺序。The routing implementation does not guarantee a processing order for matching endpoints. 所有可能的匹配项一次性处理。All possible matches are processed at once. URL 匹配阶段按以下顺序出现。The URL matching phases occur in the following order. ASP.NET Core:ASP.NET Core:

  1. 针对终结点集及其路由模板处理 URL 路径,收集所有匹配项。Processes the URL path against the set of endpoints and their route templates, collecting all of the matches.
  2. 采用前面的列表并删除在应用路由约束时失败的匹配项。Takes the preceding list and removes matches that fail with route constraints applied.
  3. 采用前面的列表并删除 MatcherPolicy 实例集失败的匹配项。Takes the preceding list and removes matches that fail the set of MatcherPolicy instances.
  4. 使用 EndpointSelector 从前面的列表中做出最终决定。Uses the EndpointSelector to make a final decision from the preceding list.

根据以下内容设置终结点列表的优先级:The list of endpoints is prioritized according to:

在每个阶段中处理所有匹配的终结点,直到达到 EndpointSelectorAll matching endpoints are processed in each phase until the EndpointSelector is reached. EndpointSelector 是最后一个阶段。The EndpointSelector is the final phase. 它从匹配项中选择最高优先级终结点作为最佳匹配项。It chooses the highest priority endpoint from the matches as the best match. 如果存在具有与最佳匹配相同优先级的其他匹配项,则会引发不明确的匹配异常。If there are other matches with the same priority as the best match, an ambiguous match exception is thrown.

路由优先顺序基于更具体的路由模板(优先级更高)进行计算。The route precedence is computed based on a more specific route template being given a higher priority. 例如,考虑模板 /hello/{message}For example, consider the templates /hello and /{message}:

  • 两者都匹配 URL 路径 /helloBoth match the URL path /hello.
  • /hello 更具体,因此优先级更高。/hello is more specific and therefore higher priority.

通常,路由优先顺序非常适合为实践操作中使用的各种 URL 方案选择最佳匹配项。In general, route precedence does a good job of choosing the best match for the kinds of URL schemes used in practice. 仅在必要时才使用 Order 来避免多义性。Use Order only when necessary to avoid an ambiguity.

由于路由提供的扩展性种类,路由系统无法提前计算不明确的路由。Due to the kinds of extensibility provided by routing, it isn't possible for the routing system to compute ahead of time the ambiguous routes. 假设有一个示例,例如路由模板 /{message:alpha}/{message:int}Consider an example such as the route templates /{message:alpha} and /{message:int}:

  • alpha 约束仅匹配字母数字字符。The alpha constraint matches only alphabetic characters.
  • int 约束仅匹配数字。The int constraint matches only numbers.
  • 这些模板具有相同的路由优先顺序,但没有两者均匹配的单一 URL。These templates have the same route precedence, but there's no single URL they both match.
  • 如果路由系统在启动时报告了多义性错误,则会阻止此有效用例。If the routing system reported an ambiguity error at startup, it would block this valid use case.

警告

UseEndpoints 内的操作顺序并不会影响路由行为,但有一个例外。The order of operations inside UseEndpoints doesn't influence the behavior of routing, with one exception. MapControllerRouteMapAreaRoute 会根据调用顺序,自动将顺序值分配给其终结点。MapControllerRoute and MapAreaRoute automatically assign an order value to their endpoints based on the order they are invoked. 这会模拟控制器的长时间行为,而无需路由系统提供与早期路由实现相同的保证。This simulates long-time behavior of controllers without the routing system providing the same guarantees as older routing implementations.

在路由的早期实现中,可以实现具有依赖于路由处理顺序的路由扩展性。In the legacy implementation of routing, it's possible to implement routing extensibility that has a dependency on the order in which routes are processed. ASP.NET Core 3.0 及更高版本中的终结点路由:Endpoint routing in ASP.NET Core 3.0 and later:

  • 没有路由的概念。Doesn't have a concept of routes.
  • 不提供顺序保证。Doesn't provide ordering guarantees. 同时处理所有终结点。All endpoints are processed at once.

路由模板优先顺序和终结点选择顺序Route template precedence and endpoint selection order

路由模板优先顺序是一种系统,该系统根据每个路由模板的具体程度为其分配值。Route template precedence is a system that assigns each route template a value based on how specific it is. 路由模板优先顺序:Route template precedence:

  • 无需在常见情况下调整终结点的顺序。Avoids the need to adjust the order of endpoints in common cases.
  • 尝试匹配路由行为的常识性预期。Attempts to match the common-sense expectations of routing behavior.

例如,考虑模板 /Products/List/Products/{id}For example, consider templates /Products/List and /Products/{id}. 我们可合理地假设,对于 URL 路径 /Products/List/Products/List 匹配项比 /Products/{id} 更好。It would be reasonable to assume that /Products/List is a better match than /Products/{id} for the URL path /Products/List. 这种假设合理是因为文本段 /List 比参数段 /{id} 具有更好的优先顺序。This works because the literal segment /List is considered to have better precedence than the parameter segment /{id}.

优先顺序工作原理的详细信息与路由模板的定义方式相耦合:The details of how precedence works are coupled to how route templates are defined:

  • 具有更多段的模板则更具体。Templates with more segments are considered more specific.
  • 带有文本的段比参数段更具体。A segment with literal text is considered more specific than a parameter segment.
  • 具有约束的参数段比没有约束的参数段更具体。A parameter segment with a constraint is considered more specific than one without.
  • 复杂段与具有约束的参数段同样具体。A complex segment is considered as specific as a parameter segment with a constraint.
  • catch-all 参数是最不具体的参数。Catch-all parameters are the least specific. 有关 catch-all 路由的重要信息,请参阅 路由模板参考 中的“catch-all”。See catch-all in the Route template reference for important information on catch-all routes.

有关确切值的参考,请参阅 GitHub 上的源代码See the source code on GitHub for a reference of exact values.

URL 生成概念URL generation concepts

URL 生成:URL generation:

  • 是指路由基于一系列路由值创建 URL 路径的过程。Is the process by which routing can create a URL path based on a set of route values.
  • 允许终结点与访问它们的 URL 之间存在逻辑分隔。Allows for a logical separation between endpoints and the URLs that access them.

终结点路由包含 LinkGenerator API。Endpoint routing includes the LinkGenerator API. LinkGeneratorDI 中可用的单一实例服务。LinkGenerator is a singleton service available from DI. LinkGenerator API 可在执行请求的上下文之外使用。The LinkGenerator API can be used outside of the context of an executing request. Mvc.IUrlHelper 和依赖 IUrlHelper 的方案(如标记帮助程序、HTML 帮助程序和操作结果)在内部使用 LinkGenerator API 提供链接生成功能。Mvc.IUrlHelper and scenarios that rely on IUrlHelper, such as Tag Helpers, HTML Helpers, and Action Results, use the LinkGenerator API internally to provide link generating capabilities.

链接生成器基于“地址”和“地址方案”概念 。The link generator is backed by the concept of an address and address schemes. 地址方案是确定哪些终结点用于链接生成的方式。An address scheme is a way of determining the endpoints that should be considered for link generation. 例如,许多用户熟悉的来自控制器或 Razor Pages 的路由名称和路由值方案都是作为地址方案实现的。For example, the route name and route values scenarios many users are familiar with from controllers and Razor Pages are implemented as an address scheme.

链接生成器可以通过以下扩展方法链接到控制器或 Razor Pages:The link generator can link to controllers and Razor Pages via the following extension methods:

这些方法的重载接受包含 HttpContext 的参数。Overloads of these methods accept arguments that include the HttpContext. 这些方法在功能上等同于 Url.ActionUrl.Page,但提供了更大的灵活性和更多选项。These methods are functionally equivalent to Url.Action and Url.Page, but offer additional flexibility and options.

GetPath* 方法与 Url.ActionUrl.Page 最相似,因为它们生成包含绝对路径的 URI。The GetPath* methods are most similar to Url.Action and Url.Page, in that they generate a URI containing an absolute path. GetUri* 方法始终生成包含方案和主机的绝对 URI。The GetUri* methods always generate an absolute URI containing a scheme and host. 接受 HttpContext 的方法在执行请求的上下文中生成 URI。The methods that accept an HttpContext generate a URI in the context of the executing request. 除非重写,否则将使用来自执行请求的环境路由值、URL 基路径、方案和主机。The ambient route values, URL base path, scheme, and host from the executing request are used unless overridden.

使用地址调用 LinkGeneratorLinkGenerator is called with an address. 生成 URI 的过程分两步进行:Generating a URI occurs in two steps:

  1. 将地址绑定到与地址匹配的终结点列表。An address is bound to a list of endpoints that match the address.
  2. 计算每个终结点的 RoutePattern,直到找到与提供的值匹配的路由模式。Each endpoint's RoutePattern is evaluated until a route pattern that matches the supplied values is found. 输出结果会与提供给链接生成器的其他 URI 部分进行组合并返回。The resulting output is combined with the other URI parts supplied to the link generator and returned.

对任何类型的地址,LinkGenerator 提供的方法均支持标准链接生成功能。The methods provided by LinkGenerator support standard link generation capabilities for any type of address. 使用链接生成器的最简便方法是通过扩展方法对特定地址类型执行操作:The most convenient way to use the link generator is through extension methods that perform operations for a specific address type:

扩展方法Extension Method 描述Description
GetPathByAddress 根据提供的值生成具有绝对路径的 URI。Generates a URI with an absolute path based on the provided values.
GetUriByAddress 根据提供的值生成绝对 URI。Generates an absolute URI based on the provided values.

警告

请注意有关调用 LinkGenerator 方法的下列含义:Pay attention to the following implications of calling LinkGenerator methods:

  • 对于不验证传入请求的 Host 标头的应用配置,请谨慎使用 GetUri* 扩展方法。Use GetUri* extension methods with caution in an app configuration that doesn't validate the Host header of incoming requests. 如果未验证传入请求的 Host 标头,则可能以视图或页面中 URI 的形式将不受信任的请求输入发送回客户端。If the Host header of incoming requests isn't validated, untrusted request input can be sent back to the client in URIs in a view or page. 建议所有生产应用都将其服务器配置为针对已知有效值验证 Host 标头。We recommend that all production apps configure their server to validate the Host header against known valid values.

  • 在中间件中将 LinkGeneratorMapMapWhen 结合使用时,请小心谨慎。Use LinkGenerator with caution in middleware in combination with Map or MapWhen. Map* 会更改执行请求的基路径,这会影响链接生成的输出。Map* changes the base path of the executing request, which affects the output of link generation. 所有 LinkGenerator API 都允许指定基路径。All of the LinkGenerator APIs allow specifying a base path. 指定一个空的基路径来撤消 Map* 对链接生成的影响。Specify an empty base path to undo the Map* affect on link generation.

中间件示例Middleware example

在以下示例中,中间件使用 LinkGenerator API 创建列出存储产品的操作方法的链接。In the following example, a middleware uses the LinkGenerator API to create a link to an action method that lists store products. 应用中的任何类都可通过将链接生成器注入类并调用 GenerateLink 来使用链接生成器:Using the link generator by injecting it into a class and calling GenerateLink is available to any class in an app:

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

路由模板参考Route template reference

如果路由找到匹配项,{} 内的令牌定义绑定的路由参数。Tokens within {} define route parameters that are bound if the route is matched. 可在路由段中定义多个路由参数,但必须用文本值隔开这些路由参数。More than one route parameter can be defined in a route segment, but route parameters must be separated by a literal value. 例如,{controller=Home}{action=Index} 不是有效的路由,因为 {controller}{action} 之间没有文本值。For example, {controller=Home}{action=Index} isn't a valid route, since there's no literal value between {controller} and {action}. 路由参数必须具有名称,且可能指定了其他特性。Route parameters must have a name and may have additional attributes specified.

路由参数以外的文本(例如 {id})和路径分隔符 / 必须匹配 URL 中的文本。Literal text other than route parameters (for example, {id}) and the path separator / must match the text in the URL. 文本匹配区分大小写,并且基于 URL 路径已解码的表示形式。Text matching is case-insensitive and based on the decoded representation of the URL's path. 要匹配文字路由参数分隔符({}),请通过重复该字符来转义分隔符。To match a literal route parameter delimiter { or }, escape the delimiter by repeating the character. 例如 {{}}For example {{ or }}.

星号 * 或双星号 **Asterisk * or double asterisk **:

  • 可用作路由参数的前缀,以绑定到 URI 的其余部分。Can be used as a prefix to a route parameter to bind to the rest of the URI.
  • 称为 catch-all 参数。Are called a catch-all parameters. 例如,blog/{**slug}For example, blog/{**slug}:
    • 匹配以 /blog 开头并在其后面包含任何值的任何 URI。Matches any URI that starts with /blog and has any value following it.
    • /blog 后的值分配给 slug 路由值。The value following /blog is assigned to the slug route value.

警告

由于路由中的 bugcatch-all 参数可能无法正确匹配相应路由。A catch-all parameter may match routes incorrectly due to a bug in routing. 受此 Bug 影响的应用具有以下特征:Apps impacted by this bug have the following characteristics:

  • “全部捕获”路由,例如 {**slug}"A catch-all route, for example, {**slug}"
  • “全部捕获”路由未能匹配应与之匹配的请求。The catch-all route fails to match requests it should match.
  • 删除其他路由可使“全部捕获”路由开始运行。Removing other routes makes catch-all route start working.

请参阅 GitHub bug 1867716579,了解遇到此 bug 的示例。See GitHub bugs 18677 and 16579 for example cases that hit this bug.

.NET Core 3.1.301 SDK 及更高版本中包含此 bug 的修补程序(可选用)。An opt-in fix for this bug is contained in .NET Core 3.1.301 SDK and later. 以下代码设置了一个可修复此 bug 的内部开关:The following code sets an internal switch that fixes this bug:

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

全方位参数还可以匹配空字符串。Catch-all parameters can also match the empty string.

使用路由生成 URL(包括路径分隔符 /)时,catch-all 参数会转义相应的字符。The catch-all parameter escapes the appropriate characters when the route is used to generate a URL, including path separator / characters. 例如,路由值为 { path = "my/path" } 的路由 foo/{*path} 生成 foo/my%2FpathFor example, the route foo/{*path} with route values { path = "my/path" } generates foo/my%2Fpath. 请注意转义的正斜杠。Note the escaped forward slash. 要往返路径分隔符,请使用 ** 路由参数前缀。To round-trip path separator characters, use the ** route parameter prefix. { path = "my/path" } 的路由 foo/{**path} 生成 foo/my/pathThe route foo/{**path} with { path = "my/path" } generates foo/my/path.

尝试捕获具有可选文件扩展名的文件名的 URL 模式还有其他注意事项。URL patterns that attempt to capture a file name with an optional file extension have additional considerations. 例如,考虑模板 files/{filename}.{ext?}For example, consider the template files/{filename}.{ext?}. filenameext 的值都存在时,将填充这两个值。When values for both filename and ext exist, both values are populated. 如果 URL 中仅存在 filename 的值,则路由匹配,因为尾随 . 是可选的。If only a value for filename exists in the URL, the route matches because the trailing . is optional. 以下 URL 与此路由相匹配:The following URLs match this route:

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

路由参数可能具有指定的默认值,方法是在参数名称后使用等号 (=) 隔开以指定默认值。Route parameters may have default values designated by specifying the default value after the parameter name separated by an equals sign (=). 例如,{controller=Home}Home 定义为 controller 的默认值。For example, {controller=Home} defines Home as the default value for controller. 如果参数的 URL 中不存在任何值,则使用默认值。The default value is used if no value is present in the URL for the parameter. 通过在参数名称的末尾附加问号 (?) 可使路由参数成为可选项。Route parameters are made optional by appending a question mark (?) to the end of the parameter name. 例如 id?For example, id?. 可选值和默认路由参数之间的差异是:The difference between optional values and default route parameters is:

  • 具有默认值的路由参数始终生成一个值。A route parameter with a default value always produces a value.
  • 仅当请求 URL 提供值时,可选参数才具有值。An optional parameter has a value only when a value is provided by the request URL.

路由参数可能具有必须与从 URL 中绑定的路由值匹配的约束。Route parameters may have constraints that must match the route value bound from the URL. 在路由参数后面添加一个 : 和约束名称可指定路由参数上的内联约束。Adding : and constraint name after the route parameter name specifies an inline constraint on a route parameter. 如果约束需要参数,将以在约束名称后括在括号 (...) 中的形式提供。If the constraint requires arguments, they're enclosed in parentheses (...) after the constraint name. 通过追加另一个 : 和约束名称,可指定多个内联约束。Multiple inline constraints can be specified by appending another : and constraint name.

约束名称和参数将传递给 IInlineConstraintResolver 服务,以创建 IRouteConstraint 的实例,用于处理 URL。The constraint name and arguments are passed to the IInlineConstraintResolver service to create an instance of IRouteConstraint to use in URL processing. 例如,路由模板 blog/{article:minlength(10)} 使用参数 10 指定 minlength 约束。For example, the route template blog/{article:minlength(10)} specifies a minlength constraint with the argument 10. 有关路由约束详情以及框架提供的约束列表,请参阅路由约束引用部分。For more information on route constraints and a list of the constraints provided by the framework, see the Route constraint reference section.

路由参数还可以具有参数转换器。Route parameters may also have parameter transformers. 参数转换器在生成链接以及将操作和页面匹配到 URI 时转换参数的值。Parameter transformers transform a parameter's value when generating links and matching actions and pages to URLs. 与约束类似,可在路由参数名称后面添加 : 和转换器名称,将参数变换器内联添加到路径参数。Like constraints, parameter transformers can be added inline to a route parameter by adding a : and transformer name after the route parameter name. 例如,路由模板 blog/{article:slugify} 指定 slugify 转换器。For example, the route template blog/{article:slugify} specifies a slugify transformer. 有关参数转换的详细信息,请参阅参数转换器参考部分。For more information on parameter transformers, see the Parameter transformer reference section.

下表演示了示例路由模板及其行为:The following table demonstrates example route templates and their behavior:

路由模板Route Template 示例匹配 URIExample Matching URI 请求 URI…The request URI…
hello /hello 仅匹配单个路径 /helloOnly matches the single path /hello.
{Page=Home} / 匹配并将 Page 设置为 HomeMatches and sets Page to Home.
{Page=Home} /Contact 匹配并将 Page 设置为 ContactMatches and sets Page to Contact.
{controller}/{action}/{id?} /Products/List 映射到 Products 控制器和 List 操作。Maps to the Products controller and List action.
{controller}/{action}/{id?} /Products/Details/123 映射到 Products 控制器和 Details 操作,并将 id 设置为 123。Maps to the Products controller and Details action withid set to 123.
{controller=Home}/{action=Index}/{id?} / 映射到 Home 控制器和 Index 方法。Maps to the Home controller and Index method. id 将被忽略。id is ignored.
{controller=Home}/{action=Index}/{id?} /Products 映射到 Products 控制器和 Index 方法。Maps to the Products controller and Index method. id 将被忽略。id is ignored.

使用模板通常是进行路由最简单的方法。Using a template is generally the simplest approach to routing. 还可在路由模板外指定约束和默认值。Constraints and defaults can also be specified outside the route template.

复杂段Complex segments

复杂段通过非贪婪的方式从右到左匹配文字进行处理。Complex segments are processed by matching up literal delimiters from right to left in a non-greedy way. 例如,[Route("/a{b}c{d}")] 是一个复杂段。For example, [Route("/a{b}c{d}")] is a complex segment. 复杂段以一种特定的方式工作,必须理解这种方式才能成功地使用它们。Complex segments work in a particular way that must be understood to use them successfully. 本部分中的示例演示了为什么复杂段只有在分隔符文本没有出现在参数值中时才真正有效。The example in this section demonstrates why complex segments only really work well when the delimiter text doesn't appear inside the parameter values. 对于更复杂的情况,需要使用 regex,然后手动提取值。Using a regex and then manually extracting the values is needed for more complex cases.

警告

如果使用 System.Text.RegularExpressions 处理不受信任的输入,则传递一个超时。When using System.Text.RegularExpressions to process untrusted input, pass a timeout. 恶意用户可能会向 RegularExpressions 提供输入,从而导致拒绝服务攻击A malicious user can provide input to RegularExpressions causing a Denial-of-Service attack. 使用 RegularExpressions 的 ASP.NET Core 框架 API 会传递一个超时。ASP.NET Core framework APIs that use RegularExpressions pass a timeout.

这是使用模板 /a{b}c{d} 和 URL 路径 /abcd 执行路由的步骤摘要。This is a summary of the steps that routing performs with the template /a{b}c{d} and the URL path /abcd. | 有助于可视化算法的工作方式:The | is used to help visualize how the algorithm works:

  • 从右到左的第一个文本是 cThe first literal, right to left, is c. 因此从右侧搜索 /abcd,并查找 /ab|c|dSo /abcd is searched from right and finds /ab|c|d.
  • 右侧的所有内容 (d) 现在都与路由参数 {d} 相匹配。Everything to the right (d) is now matched to the route parameter {d}.
  • 从右到左的下一个文本是 aThe next literal, right to left, is a. 从我们停止的地方开始搜索 /ab|c|d,然后 /|a|b|c|d 找到 aSo /ab|c|d is searched starting where we left off, then a is found /|a|b|c|d.
  • 右侧的值 (b) 现在与路由参数 {b} 相匹配。The value to the right (b) is now matched to the route parameter {b}.
  • 没有剩余的文本,并且没有剩余的路由模板,因此这是一个匹配项。There is no remaining text and no remaining route template, so this is a match.

下面是使用相同模板 /a{b}c{d} 和 URL 路径 /aabcd 的负面案例示例。Here's an example of a negative case using the same template /a{b}c{d} and the URL path /aabcd. | 有助于可视化算法的工作方式。The | is used to help visualize how the algorithm works. 该案例不是匹配项,可由相同算法解释:This case isn't a match, which is explained by the same algorithm:

  • 从右到左的第一个文本是 cThe first literal, right to left, is c. 因此从右侧搜索 /aabcd,并查找 /aab|c|dSo /aabcd is searched from right and finds /aab|c|d.
  • 右侧的所有内容 (d) 现在都与路由参数 {d} 相匹配。Everything to the right (d) is now matched to the route parameter {d}.
  • 从右到左的下一个文本是 aThe next literal, right to left, is a. 从我们停止的地方开始搜索 /aab|c|d,然后 /a|a|b|c|d 找到 aSo /aab|c|d is searched starting where we left off, then a is found /a|a|b|c|d.
  • 右侧的值 (b) 现在与路由参数 {b} 相匹配。The value to the right (b) is now matched to the route parameter {b}.
  • 此时还有剩余的文本 a,但是算法已经耗尽了要解析的路由模板,所以这不是一个匹配项。At this point there is remaining text a, but the algorithm has run out of route template to parse, so this is not a match.

匹配算法是非贪婪算法:Since the matching algorithm is non-greedy:

  • 它匹配每个步骤中最小可能文本量。It matches the smallest amount of text possible in each step.
  • 如果分隔符值出现在参数值内,则会导致不匹配。Any case where the delimiter value appears inside the parameter values results in not matching.

正则表达式可以更好地控制它们的匹配行为。Regular expressions provide much more control over their matching behavior.

贪婪匹配(也称为懒惰匹配)匹配最大可能字符串。Greedy matching, also know as lazy matching, matches the largest possible string. 非贪婪匹配最小可能字符串。Non-greedy matches the smallest possible string.

路由约束参考Route constraint reference

路由约束在传入 URL 发生匹配时执行,URL 路径标记为路由值。Route constraints execute when a match has occurred to the incoming URL and the URL path is tokenized into route values. 路径约束通常检查通过路径模板关联的路径值,并对该值是否为可接受做出对/错决定。Route constraints generally inspect the route value associated via the route template and make a true or false decision about whether the value is acceptable. 某些路由约束使用路由值以外的数据来考虑是否可以路由请求。Some route constraints use data outside the route value to consider whether the request can be routed. 例如,HttpMethodRouteConstraint 可以根据其 HTTP 谓词接受或拒绝请求。For example, the HttpMethodRouteConstraint can accept or reject a request based on its HTTP verb. 约束用于路由请求和链接生成。Constraints are used in routing requests and link generation.

警告

请勿将约束用于输入验证。Don't use constraints for input validation. 如果约束用于输入验证,则无效的输入将导致 404(找不到页面)响应。If constraints are used for input validation, invalid input results in a 404 Not Found response. 无效输入可能生成包含相应错误消息的 400 错误请求。Invalid input should produce a 400 Bad Request with an appropriate error message. 路由约束用于消除类似路由的歧义,而不是验证特定路由的输入。Route constraints are used to disambiguate similar routes, not to validate the inputs for a particular route.

下表演示示例路由约束及其预期行为:The following table demonstrates example route constraints and their expected behavior:

约束constraint 示例Example 匹配项示例Example Matches 说明Notes
int {id:int} 123456789, -123456789123456789, -123456789 匹配任何整数Matches any integer
bool {active:bool} true, FALSEtrue, FALSE 匹配 truefalseMatches true or false. 不区分大小写Case-insensitive
datetime {dob:datetime} 2016-12-31, 2016-12-31 7:32pm2016-12-31, 2016-12-31 7:32pm 在固定区域性中匹配有效的 DateTime 值。Matches a valid DateTime value in the invariant culture. 请参阅前面的警告。See preceding warning.
decimal {price:decimal} 49.99, -1,000.0149.99, -1,000.01 在固定区域性中匹配有效的 decimal 值。Matches a valid decimal value in the invariant culture. 请参阅前面的警告。See preceding warning.
double {weight:double} 1.234, -1,001.01e81.234, -1,001.01e8 在固定区域性中匹配有效的 double 值。Matches a valid double value in the invariant culture. 请参阅前面的警告。See preceding warning.
float {weight:float} 1.234, -1,001.01e81.234, -1,001.01e8 在固定区域性中匹配有效的 float 值。Matches a valid float value in the invariant culture. 请参阅前面的警告。See preceding warning.
guid {id:guid} CD2C1638-1638-72D5-1638-DEADBEEF1638 匹配有效的 GuidMatches a valid Guid value
long {ticks:long} 123456789, -123456789123456789, -123456789 匹配有效的 longMatches a valid long value
minlength(value) {username:minlength(4)} Rick 字符串必须至少为 4 个字符String must be at least 4 characters
maxlength(value) {filename:maxlength(8)} MyFile 字符串不得超过 8 个字符String must be no more than 8 characters
length(length) {filename:length(12)} somefile.txt 字符串必须正好为 12 个字符String must be exactly 12 characters long
length(min,max) {filename:length(8,16)} somefile.txt 字符串必须至少为 8 个字符,且不得超过 16 个字符String must be at least 8 and no more than 16 characters long
min(value) {age:min(18)} 19 整数值必须至少为 18Integer value must be at least 18
max(value) {age:max(120)} 91 整数值不得超过 120Integer value must be no more than 120
range(min,max) {age:range(18,120)} 91 整数值必须至少为 18,且不得超过 120Integer value must be at least 18 but no more than 120
alpha {name:alpha} Rick 字符串必须由一个或多个字母字符组成,a-z,并区分大小写。String must consist of one or more alphabetical characters, a-z and case-insensitive.
regex(expression) {ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} 123-45-6789 字符串必须与正则表达式匹配。String must match the regular expression. 请参阅有关定义正则表达式的提示。See tips about defining a regular expression.
required {name:required} Rick 用于强制在 URL 生成过程中存在非参数值Used to enforce that a non-parameter value is present during URL generation

警告

如果使用 System.Text.RegularExpressions 处理不受信任的输入,则传递一个超时。When using System.Text.RegularExpressions to process untrusted input, pass a timeout. 恶意用户可能会向 RegularExpressions 提供输入,从而导致拒绝服务攻击A malicious user can provide input to RegularExpressions causing a Denial-of-Service attack. 使用 RegularExpressions 的 ASP.NET Core 框架 API 会传递一个超时。ASP.NET Core framework APIs that use RegularExpressions pass a timeout.

可向单个参数应用多个用冒号分隔的约束。Multiple, colon delimited constraints can be applied to a single parameter. 例如,以下约束将参数限制为大于或等于 1 的整数值:For example, the following constraint restricts a parameter to an integer value of 1 or greater:

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

警告

验证 URL 的路由约束并将转换为始终使用固定区域性的 CLR 类型。Route constraints that verify the URL and are converted to a CLR type always use the invariant culture. 例如,转换为 CLR 类型 intDateTimeFor example, conversion to the CLR type int or DateTime. 这些约束假定 URL 不可本地化。These constraints assume that the URL is not localizable. 框架提供的路由约束不会修改存储于路由值中的值。The framework-provided route constraints don't modify the values stored in route values. 从 URL 中分析的所有路由值都将存储为字符串。All route values parsed from the URL are stored as strings. 例如,float 约束会尝试将路由值转换为浮点数,但转换后的值仅用来验证其是否可转换为浮点数。For example, the float constraint attempts to convert the route value to a float, but the converted value is used only to verify it can be converted to a float.

约束中的正则表达式Regular expressions in constraints

警告

如果使用 System.Text.RegularExpressions 处理不受信任的输入,则传递一个超时。When using System.Text.RegularExpressions to process untrusted input, pass a timeout. 恶意用户可能会向 RegularExpressions 提供输入,从而导致拒绝服务攻击A malicious user can provide input to RegularExpressions causing a Denial-of-Service attack. 使用 RegularExpressions 的 ASP.NET Core 框架 API 会传递一个超时。ASP.NET Core framework APIs that use RegularExpressions pass a timeout.

使用 regex(...) 路由约束可以将正则表达式指定为内联约束。Regular expressions can be specified as inline constraints using the regex(...) route constraint. MapControllerRoute 系列中的方法还接受约束的对象文字。Methods in the MapControllerRoute family also accept an object literal of constraints. 如果使用该窗体,则字符串值将解释为正则表达式。If that form is used, string values are interpreted as regular expressions.

下面的代码使用内联 regex 约束:The following code uses an inline regex constraint:

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

下面的代码使用对象文字来指定 regex 约束:The following code uses an object literal to specify a regex constraint:

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.CultureInvariantThe ASP.NET Core framework adds RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant to the regular expression constructor. 有关这些成员的说明,请参阅 RegexOptionsSee RegexOptions for a description of these members.

正则表达式与路由和 C# 语言使用的分隔符和令牌相似。Regular expressions use delimiters and tokens similar to those used by routing and the C# language. 必须对正则表达式令牌进行转义。Regular expression tokens must be escaped. 若要在内联约束中使用正则表达式 ^\d{3}-\d{2}-\d{4}$,请使用以下某项:To use the regular expression ^\d{3}-\d{2}-\d{4}$ in an inline constraint, use one of the following:

  • 将字符串中提供的 \ 字符替换为 C# 源文件中的 \\ 字符,以便对 \ 字符串转义字符进行转义。Replace \ characters provided in the string as \\ characters in the C# source file in order to escape the \ string escape character.
  • 逐字字符串文本Verbatim string literals.

要对路由参数分隔符 {}[] 进行转义,请将表达式(例如 {{}}[[]])中的字符数加倍。To escape routing parameter delimiter characters {, }, [, ], double the characters in the expression, for example, {{, }}, [[, ]]. 下表展示了正则表达式及其转义的版本:The following table shows a regular expression and its escaped version:

正则表达式Regular expression 转义后的正则表达式Escaped regular expression
^\d{3}-\d{2}-\d{4}$ ^\\d{{3}}-\\d{{2}}-\\d{{4}}$
^[a-z]{2}$ ^[[a-z]]{{2}}$

路由中使用的正则表达式通常以 ^ 字符开头,并匹配字符串的起始位置。Regular expressions used in routing often start with the ^ character and match the starting position of the string. 表达式通常以 $ 字符结尾,并匹配字符串的结尾。The expressions often end with the $ character and match the end of the string. ^$ 字符可确保正则表达式匹配整个路由参数值。The ^ and $ characters ensure that the regular expression matches the entire route parameter value. 如果没有 ^$ 字符,正则表达式将匹配字符串内的所有子字符串,而这通常是不需要的。Without the ^ and $ characters, the regular expression matches any substring within the string, which is often undesirable. 下表提供了示例并说明了它们匹配或匹配失败的原因:The following table provides examples and explains why they match or fail to match:

表达式Expression StringString 匹配Match 注释Comment
[a-z]{2} hellohello Yes 子字符串匹配Substring matches
[a-z]{2} 123abc456123abc456 Yes 子字符串匹配Substring matches
[a-z]{2} mzmz Yes 匹配表达式Matches expression
[a-z]{2} MZMZ Yes 不区分大小写Not case sensitive
^[a-z]{2}$ hellohello No 参阅上述 ^$See ^ and $ above
^[a-z]{2}$ 123abc456123abc456 No 参阅上述 ^$See ^ and $ above

有关正则表达式语法的详细信息,请参阅 .NET Framework 正则表达式For more information on regular expression syntax, see .NET Framework Regular Expressions.

若要将参数限制为一组已知的可能值,可使用正则表达式。To constrain a parameter to a known set of possible values, use a regular expression. 例如,{action:regex(^(list|get|create)$)} 仅将 action 路由值匹配到 listgetcreateFor example, {action:regex(^(list|get|create)$)} only matches the action route value to list, get, or create. 如果传递到约束字典中,字符串 ^(list|get|create)$ 将等效。If passed into the constraints dictionary, the string ^(list|get|create)$ is equivalent. 已传递到约束字典且不匹配任何已知约束的约束也将被视为正则表达式。Constraints that are passed in the constraints dictionary that don't match one of the known constraints are also treated as regular expressions. 模板内传递且不匹配任何已知约束的约束将不被视为正则表达式。Constraints that are passed within a template that don't match one of the known constraints are not treated as regular expressions.

自定义路由约束Custom route constraints

可实现 IRouteConstraint 接口来创建自定义路由约束。Custom route constraints can be created by implementing the IRouteConstraint interface. IRouteConstraint 接口包含 Match,当满足约束时,它返回 true,否则返回 falseThe IRouteConstraint interface contains Match, which returns true if the constraint is satisfied and false otherwise.

很少需要自定义路由约束。Custom route constraints are rarely needed. 在实现自定义路由约束之前,请考虑替代方法,如模型绑定。Before implementing a custom route constraint, consider alternatives, such as model binding.

ASP.NET Core Constraints 文件夹提供了创建约束的经典示例。The ASP.NET Core Constraints folder provides good examples of creating a constraints. 例如 GuidRouteConstraintFor example, GuidRouteConstraint.

若要使用自定义 IRouteConstraint,必须在服务容器中使用应用的 ConstraintMap 注册路由约束类型。To use a custom IRouteConstraint, the route constraint type must be registered with the app's ConstraintMap in the service container. ConstraintMap 是将路由约束键映射到验证这些约束的 IRouteConstraint 实现的目录。A ConstraintMap is a dictionary that maps route constraint keys to IRouteConstraint implementations that validate those constraints. 应用的 ConstraintMap 可作为 services.AddRouting 调用的一部分在 Startup.ConfigureServices 中进行更新,也可以通过使用 services.Configure<RouteOptions> 直接配置 RouteOptions 进行更新。An app's ConstraintMap can be updated in Startup.ConfigureServices either as part of a services.AddRouting call or by configuring RouteOptions directly with services.Configure<RouteOptions>. 例如:For example:

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

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

前面的约束应用于以下代码:The preceding constraint is applied in the following code:

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

MyDisplayRouteInfoRick.Docs.Samples.RouteInfo NuGet 包提供,会显示路由信息。MyDisplayRouteInfo is provided by the Rick.Docs.Samples.RouteInfo NuGet package and displays route information.

实现 MyCustomConstraint 可防止将 0 应用于路由参数:The implementation of MyCustomConstraint prevents 0 being applied to a route parameter:

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 处理不受信任的输入,则传递一个超时。When using System.Text.RegularExpressions to process untrusted input, pass a timeout. 恶意用户可能会向 RegularExpressions 提供输入,从而导致拒绝服务攻击A malicious user can provide input to RegularExpressions causing a Denial-of-Service attack. 使用 RegularExpressions 的 ASP.NET Core 框架 API 会传递一个超时。ASP.NET Core framework APIs that use RegularExpressions pass a timeout.

前面的代码:The preceding code:

  • 阻止路由的 {id} 段中的 0Prevents 0 in the {id} segment of the route.
  • 显示以提供实现自定义约束的基本示例。Is shown to provide a basic example of implementing a custom constraint. 不应在产品应用中使用。It should not be used in a production app.

下面的代码是防止处理包含 0id 的更好方法:The following code is a better approach to preventing an id containing a 0 from being processed:

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

    return ControllerContext.MyDisplayRouteInfo(id);
}

前面的代码与 MyCustomConstraint 方法相比具有以下优势:The preceding code has the following advantages over the MyCustomConstraint approach:

  • 它不需要自定义约束。It doesn't require a custom constraint.
  • 当路由参数包括 0 时,它将返回更具描述性的错误。It returns a more descriptive error when the route parameter includes 0.

参数转换器参考Parameter transformer reference

参数转换器:Parameter transformers:

例如,路由模式 blog\{article:slugify}(具有 Url.Action(new { article = "MyTestArticle" }))中的自定义 slugify 参数转换器生成 blog\my-test-articleFor example, a custom slugify parameter transformer in route pattern blog\{article:slugify} with Url.Action(new { article = "MyTestArticle" }) generates blog\my-test-article.

请考虑以下 IOutboundParameterTransformer 实现:Consider the following IOutboundParameterTransformer implementation:

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

若要在路由模式中使用参数转换器,请在 Startup.ConfigureServices 中使用 ConstraintMap 对其进行配置:To use a parameter transformer in a route pattern, configure it using ConstraintMap in Startup.ConfigureServices:

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

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

ASP.NET Core 框架使用参数转化器来转换进行终结点解析的 URI。The ASP.NET Core framework uses parameter transformers to transform the URI where an endpoint resolves. 例如,参数转换器转换用于匹配 areacontrolleractionpage 的路由值。For example, parameter transformers transform the route values used to match an area, controller, action, and page.

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

使用上述路由模板,可将操作 SubscriptionManagementController.GetAll 与 URI /subscription-management/get-all 相匹配。With the preceding route template, the action SubscriptionManagementController.GetAll is matched with the URI /subscription-management/get-all. 参数转换器不会更改用于生成链接的路由值。A parameter transformer doesn't change the route values used to generate a link. 例如,Url.Action("GetAll", "SubscriptionManagement") 输出 /subscription-management/get-allFor example, Url.Action("GetAll", "SubscriptionManagement") outputs /subscription-management/get-all.

对于结合使用参数转换器和所生成的路由,ASP.NET Core 提供了 API 约定:ASP.NET Core provides API conventions for using parameter transformers with generated routes:

URL 生成参考URL generation reference

本部分包含 URL 生成实现的算法的参考。This section contains a reference for the algorithm implemented by URL generation. 在实践中,最复杂的 URL 生成示例使用控制器或 Razor Pages。In practice, most complex examples of URL generation use controllers or Razor Pages. 有关其他信息,请参阅控制器中的路由See routing in controllers for additional information.

URL 生成过程首先调用 GetPathByAddress 或类似方法。The URL generation process begins with a call to LinkGenerator.GetPathByAddress or a similar method. 此方法提供了一个地址、一组路由值以及有关 HttpContext 中当前请求的可选信息。The method is provided with an address, a set of route values, and optionally information about the current request from HttpContext.

第一步是使用地址解析一组候选终结点,该终结点使用与该地址类型匹配的 IEndpointAddressScheme<TAddress>The first step is to use the address to resolve a set of candidate endpoints using an IEndpointAddressScheme<TAddress> that matches the address's type.

地址方案找到一组候选项后,就会以迭代方式对终结点进行排序和处理,直到 URL 生成操作成功。Once of set of candidates is found by the address scheme, the endpoints are ordered and processed iteratively until a URL generation operation succeeds. URL 生成不检查多义性,返回的第一个结果就是最终结果。URL generation does not check for ambiguities, the first result returned is the final result.

使用日志记录对 URL 生成进行故障排除Troubleshooting URL generation with logging

对 URL 生成进行故障排除的第一步是将 Microsoft.AspNetCore.Routing 的日志记录级别设置为 TRACEThe first step in troubleshooting URL generation is setting the logging level of Microsoft.AspNetCore.Routing to TRACE. LinkGenerator 记录有关其处理的许多详细信息,有助于排查问题。LinkGenerator logs many details about its processing which can be useful to troubleshoot problems.

有关 URL 生成的详细信息,请参阅 URL 生成参考See URL generation reference for details on URL generation.

地址Addresses

地址是 URL 生成中的概念,用于将链接生成器中的调用绑定到一组终结点。Addresses are the concept in URL generation used to bind a call into the link generator to a set of candidate endpoints.

地址是一个可扩展的概念,默认情况下有两种实现:Addresses are an extensible concept that come with two implementations by default:

  • 使用终结点名称 (string) 作为地址:Using endpoint name (string) as the address:
    • 提供与 MVC 的路由名称相似的功能。Provides similar functionality to MVC's route name.
    • 使用 IEndpointNameMetadata 元数据类型。Uses the IEndpointNameMetadata metadata type.
    • 根据所有注册的终结点的元数据解析提供的字符串。Resolves the provided string against the metadata of all registered endpoints.
    • 如果多个终结点使用相同的名称,启动时会引发异常。Throws an exception on startup if multiple endpoints use the same name.
    • 建议用于控制器和 Razor Pages 之外的常规用途。Recommended for general-purpose use outside of controllers and Razor Pages.
  • 使用路由值 (RouteValuesAddress) 作为地址:Using route values (RouteValuesAddress) as the address:
    • 提供与控制器和 Razor Pages 生成旧 URL 相同的功能。Provides similar functionality to controllers and Razor Pages legacy URL generation.
    • 扩展和调试非常复杂。Very complex to extend and debug.
    • 提供 IUrlHelper、标记帮助程序、HTML 帮助程序、操作结果等所使用的实现。Provides the implementation used by IUrlHelper, Tag Helpers, HTML Helpers, Action Results, etc.

地址方案的作用是根据任意条件在地址和匹配终结点之间建立关联:The role of the address scheme is to make the association between the address and matching endpoints by arbitrary criteria:

  • 终结点名称方案执行基本字典查找。The endpoint name scheme performs a basic dictionary lookup.
  • 路由值方案有一个复杂的子集算法,这是集算法的最佳子集。The route values scheme has a complex best subset of set algorithm.

环境值和显式值Ambient values and explicit values

从当前请求开始,路由将访问当前请求 HttpContext.Request.RouteValues 的路由值。From the current request, routing accesses the route values of the current request HttpContext.Request.RouteValues. 与当前请求关联的值称为环境值。The values associated with the current request are referred to as the ambient values. 为清楚起见,文档是指作为显式值传入方法的路由值。For the purpose of clarity, the documentation refers to the route values passed in to methods as explicit values.

下面的示例演示了环境值和显式值。The following example shows ambient values and explicit values. 它将提供当前请求中的环境值和显式值:{ id = 17, }It provides ambient values from the current request and explicit values: { 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);
    }

前面的代码:The preceding code:

下面的代码不提供环境值和显式值:{ controller = "Home", action = "Subscribe", id = 17, }The following code provides no ambient values and explicit values: { controller = "Home", action = "Subscribe", id = 17, }:

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

前面的方法返回 /Home/Subscribe/17The preceding method returns /Home/Subscribe/17

WidgetController 中的下列代码返回 /Widget/Subscribe/17The following code in the WidgetController returns /Widget/Subscribe/17:

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

下面的代码提供当前请求中的环境值和显式值中的控制器:{ action = "Edit", id = 17, }The following code provides the controller from ambient values in the current request and explicit values: { action = "Edit", id = 17, }:

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

在上述代码中:In the preceding code:

  • 返回 /Gadget/Edit/17/Gadget/Edit/17 is returned.
  • Url 获取 IUrlHelperUrl gets the IUrlHelper.
  • Action
    生成一个 URL,其中包含操作方法的绝对路径。generates a URL with an absolute path for an action method. URL 包含指定的 action 名称和 route 值。The URL contains the specified action name and route values.

下面的代码提供当前请求中的环境值和显式值:{ page = "./Edit, id = 17, }The following code provides ambient values from the current request and explicit values: { page = "./Edit, id = 17, }:

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

当“编辑 Razor”页包含以下页面指令时,前面的代码会将 url 设置为 /Edit/17The preceding code sets url to /Edit/17 when the Edit Razor Page contains the following page directive:

@page "{id:int}"

如果“编辑”页面不包含 "{id:int}" 路由模板,url/Edit?id=17If the Edit page doesn't contain the "{id:int}" route template, url is /Edit?id=17.

除了此处所述的规则外,MVC IUrlHelper 的行为还增加了一层复杂性:The behavior of MVC's IUrlHelper adds a layer of complexity in addition to the rules described here:

  • IUrlHelper 始终将当前请求中的路由值作为环境值提供。IUrlHelper always provides the route values from the current request as ambient values.
  • IUrlHelper 始终将当前 actioncontroller 路由值复制为显式值,除非由开发者重写。IUrlHelper.Action always copies the current action and controller route values as explicit values unless overridden by the developer.
  • IUrlHelper 始终将当前 page 路由值复制为显式值,除非重写。IUrlHelper.Page always copies the current page route value as an explicit value unless overridden.
  • IUrlHelper.Page 始终替代当前 handler 路由值,且 null 为显式值,除非重写。IUrlHelper.Page always overrides the current handler route value with null as an explicit values unless overridden.

用户常常对环境值的行为详细信息感到惊讶,因为 MVC 似乎不遵循自己的规则。Users are often surprised by the behavioral details of ambient values, because MVC doesn't seem to follow its own rules. 出于历史和兼容性原因,某些路由值(例如 actioncontrollerpagehandler)具有其自己的特殊行为。For historical and compatibility reasons, certain route values such as action, controller, page, and handler have their own special-case behavior.

LinkGenerator.GetPathByActionLinkGenerator.GetPathByPage 提供的等效功能与 IUrlHelper 的兼容性异常相同。The equivalent functionality provided by LinkGenerator.GetPathByAction and LinkGenerator.GetPathByPage duplicates these anomalies of IUrlHelper for compatibility.

URL 生成过程URL generation process

找到候选终结点集后,URL 生成算法将:Once the set of candidate endpoints are found, the URL generation algorithm:

  • 以迭代方式处理终结点。Processes the endpoints iteratively.
  • 返回第一个成功的结果。Returns the first successful result.

此过程的第一步称为路由值失效。The first step in this process is called route value invalidation. 路由值失效是路由决定应使用环境值中的哪些路由值以及应忽略哪些路由值的过程。Route value invalidation is the process by which routing decides which route values from the ambient values should be used and which should be ignored. 将考虑每个环境值,要么与显式值组合,要么忽略它们。Each ambient value is considered and either combined with the explicit values, or ignored.

考虑环境值角色的最佳方式是在某些常见情况下,尝试保存应用程序开发者的键入内容。The best way to think about the role of ambient values is that they attempt to save application developers typing, in some common cases. 就传统意义而言,环境值非常有用的情况与 MVC 相关:Traditionally, the scenarios where ambient values are helpful are related to MVC:

  • 链接到同一控制器中的其他操作时,不需要指定控制器名称。When linking to another action in the same controller, the controller name doesn't need to be specified.
  • 链接到同一区域中的另一个控制器时,不需要指定区域名称。When linking to another controller in the same area, the area name doesn't need to be specified.
  • 链接到相同的操作方法时,不需要指定路由值。When linking to the same action method, route values don't need to be specified.
  • 链接到应用的其他部分时,不需要在应用的该部分中传递无意义的路由值。When linking to another part of the app, you don't want to carry over route values that have no meaning in that part of the app.

如果不了解路由值失效,通常就会导致对返回 nullLinkGeneratorIUrlHelper 执行调用。Calls to LinkGenerator or IUrlHelper that return null are usually caused by not understanding route value invalidation. 显式指定更多路由值,对路由值失效进行故障排除,以查看是否解决了问题。Troubleshoot route value invalidation by explicitly specifying more of the route values to see if that solves the problem.

路由值失效的前提是应用的 URL 方案是分层的,并按从左到右的顺序组成层次结构。Route value invalidation works on the assumption that the app's URL scheme is hierarchical, with a hierarchy formed from left-to-right. 请考虑基本控制器路由模板 {controller}/{action}/{id?},以直观地了解实践操作中该模板的工作方式。Consider the basic controller route template {controller}/{action}/{id?} to get an intuitive sense of how this works in practice. 对值进行更改会使右侧显示的所有路由值都失效 。A change to a value invalidates all of the route values that appear to the right. 这反映了关于层次结构的假设。This reflects the assumption about hierarchy. 如果应用的 id 有一个环境值,则操作会为 controller 指定一个不同的值:If the app has an ambient value for id, and the operation specifies a different value for the controller:

  • id 不会重复使用,因为 {controller}{id?} 的左侧。id won't be reused because {controller} is to the left of {id?}.

演示此原则的一些示例如下:Some examples demonstrating this principle:

  • 如果显式值包含 id 的值,则将忽略 id 的环境值。If the explicit values contain a value for id, the ambient value for id is ignored. 可以使用 controlleraction 的环境值。The ambient values for controller and action can be used.
  • 如果显式值包含 action 的值,则将忽略 action 的任何环境值。If the explicit values contain a value for action, any ambient value for action is ignored. 可以使用 controller 的环境值。The ambient values for controller can be used. 如果 action 的显式值不同于 action 的环境值,则不会使用 id 值。If the explicit value for action is different from the ambient value for action, the id value won't be used. 如果 action 的显式值与 action 的环境值相同,则可以使用 id 值。If the explicit value for action is the same as the ambient value for action, the id value can be used.
  • 如果显式值包含 controller 的值,则将忽略 controller 的任何环境值。If the explicit values contain a value for controller, any ambient value for controller is ignored. 如果 controller 的显式值不同于 controller 的环境值,则不会使用 actionid 值。If the explicit value for controller is different from the ambient value for controller, the action and id values won't be used. 如果 controller 的显式值与 controller 的环境值相同,则可以使用 actionid 值。If the explicit value for controller is the same as the ambient value for controller, the action and id values can be used.

由于存在特性路由和专用传统路由,此过程将变得更加复杂。This process is further complicated by the existence of attribute routes and dedicated conventional routes. 控制器传统路由(例如 {controller}/{action}/{id?})使用路由参数指定层次结构。Controller conventional routes such as {controller}/{action}/{id?} specify a hierarchy using route parameters. 对于控制器和 Razor Pages 的专用传统路由特性路由For dedicated conventional routes and attribute routes to controllers and Razor Pages:

  • 有一个路由值层次结构。There is a hierarchy of route values.
  • 它们不会出现在模板中。They don't appear in the template.

对于这些情况,URL 生成将定义“所需值”这一概念。For these cases, URL generation defines the required values concept. 而由控制器和 Razor Pages 创建的终结点将指定所需值,以允许路由值失效起作用。Endpoints created by controllers and Razor Pages have required values specified that allow route value invalidation to work.

路由值失效算法的详细信息:The route value invalidation algorithm in detail:

  • 所需值名称与路由参数组合在一起,然后从左到右进行处理。The required value names are combined with the route parameters, then processed from left-to-right.
  • 对于每个参数,将比较环境值和显式值:For each parameter, the ambient value and explicit value are compared:
    • 如果环境值和显式值相同,则该过程将继续。If the ambient value and explicit value are the same, the process continues.
    • 如果环境值存在而显式值不存在,则在生成 URL 时使用环境值。If the ambient value is present and the explicit value isn't, the ambient value is used when generating the URL.
    • 如果环境值不存在而显式值存在,则拒绝环境值和所有后续环境值。If the ambient value isn't present and the explicit value is, reject the ambient value and all subsequent ambient values.
    • 如果环境值和显式值均存在,并且这两个值不同,则拒绝环境值和所有后续环境值。If the ambient value and the explicit value are present, and the two values are different, reject the ambient value and all subsequent ambient values.

此时,URL 生成操作就可以计算路由约束了。At this point, the URL generation operation is ready to evaluate route constraints. 接受的值集与提供给约束的参数默认值相结合。The set of accepted values is combined with the parameter default values, which is provided to constraints. 如果所有约束都通过,则操作将继续。If the constraints all pass, the operation continues.

接下来,接受的值可用于扩展路由模板。Next, the accepted values can be used to expand the route template. 处理路由模板:The route template is processed:

  • 从左到右。From left-to-right.
  • 每个参数都替换了接受的值。Each parameter has its accepted value substituted.
  • 具有以下特殊情况:With the following special cases:
    • 如果接受的值缺少一个值并且参数具有默认值,则使用默认值。If the accepted values is missing a value and the parameter has a default value, the default value is used.
    • 如果接受的值缺少一个值并且参数是可选的,则继续处理。If the accepted values is missing a value and the parameter is optional, processing continues.
    • 如果缺少的可选参数右侧的任何路由参数都具有值,则操作将失败。If any route parameter to the right of a missing optional parameter has a value, the operation fails.
    • 如果可能,连续的默认值参数和可选参数会折叠。Contiguous default-valued parameters and optional parameters are collapsed where possible.

显式提供且与路由片段不匹配的值将添加到查询字符串中。Values explicitly provided that don't match a segment of the route are added to the query string. 下表显示使用路由模板 {controller}/{action}/{id?} 时的结果。The following table shows the result when using the route template {controller}/{action}/{id?}.

环境值Ambient Values 显式值Explicit Values 结果Result
控制器 =“Home”controller = "Home" 操作 =“About”action = "About" /Home/About
控制器 =“Home”controller = "Home" 控制器 =“Order”,操作 =“About”controller = "Order", action = "About" /Order/About
控制器 = “Home”,颜色 = “Red”controller = "Home", color = "Red" 操作 =“About”action = "About" /Home/About
控制器 =“Home”controller = "Home" 操作 =“About”,颜色 =“Red”action = "About", color = "Red" /Home/About?color=Red

路由值失效的问题Problems with route value invalidation

从 ASP.NET Core 3.0 开始,早期 ASP.NET Core 版本中使用的一些 URL 生成方案不适用于 URL 生成。As of ASP.NET Core 3.0, some URL generation schemes used in earlier ASP.NET Core versions don't work well with URL generation. ASP.NET Core 团队计划在未来的版本中添加功能来满足这些需求。The ASP.NET Core team plans to add features to address these needs in a future release. 现在,最佳解决方案是使用旧路由。For now the best solution is to use legacy routing.

下面的代码显示了路由不支持的 URL 生成方案示例。The following code shows an example of a URL generation scheme that's not supported by routing.

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

在前面的代码中,culture 路由参数用于本地化。In the preceding code, the culture route parameter is used for localization. 需要将 culture 参数始终作为环境值接受。The desire is to have the culture parameter always accepted as an ambient value. 但由于所需值的工作方式,不会将 culture 参数作为环境值接受:However, the culture parameter is not accepted as an ambient value because of the way required values work:

  • "default" 路由模板中,culture 路由参数位于 controller 的左侧,因此对 controller 的更改不会使 culture 失效。In the "default" route template, the culture route parameter is to the left of controller, so changes to controller won't invalidate culture.
  • "blog" 路由模板中,culture 路由参数应在 controller 的右侧,这会显示在所需值中。In the "blog" route template, the culture route parameter is considered to be to the right of controller, which appears in the required values.

配置终结点元数据Configuring endpoint metadata

以下链接提供有关配置终结点元数据的信息:The following links provide information on configuring endpoint metadata:

路由中与 RequireHost 匹配的主机Host matching in routes with RequireHost

RequireHost 将约束应用于需要指定主机的路由。RequireHost applies a constraint to the route which requires the specified host. RequireHost[Host] 参数可以是:The RequireHost or [Host] parameter can be:

  • 主机:www.domain.com匹配任何端口的 www.domain.comHost: www.domain.com, matches www.domain.com with any port.
  • 带有通配符的主机:*.domain.com,匹配任何端口上的 www.domain.comsubdomain.domain.comwww.subdomain.domain.comHost with wildcard: *.domain.com, matches www.domain.com, subdomain.domain.com, or www.subdomain.domain.com on any port.
  • 端口:*:5000 匹配任何主机的端口 5000。Port: *:5000, matches port 5000 with any host.
  • 主机和端口:www.domain.com:5000*.domain.com:5000(匹配主机和端口)。Host and port: www.domain.com:5000 or *.domain.com:5000, matches host and port.

可以使用 RequireHost[Host] 指定多个参数。Multiple parameters can be specified using RequireHost or [Host]. 约束匹配对任何参数均有效的主机。The constraint matches hosts valid for any of the parameters. 例如,[Host("domain.com", "*.domain.com")] 匹配 domain.comwww.domain.comsubdomain.domain.comFor example, [Host("domain.com", "*.domain.com")] matches domain.com, www.domain.com, and subdomain.domain.com.

以下代码使用 RequireHost 来要求路由上的指定主机:The following code uses RequireHost to require the specified host on the route:

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] 特性来要求任何指定的主机:The following code uses the [Host] attribute on the controller to require any of the specified hosts:

[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] 属性同时应用于控制器和操作方法时:When the [Host] attribute is applied to both the controller and action method:

  • 使用操作上的属性。The attribute on the action is used.
  • 忽略控制器属性。The controller attribute is ignored.

路由的性能指南Performance guidance for routing

大多数路由在 ASP.NET Core 3.0 中都进行了更新,以提高性能。Most of routing was updated in ASP.NET Core 3.0 to increase performance.

当应用出现性能问题时,人们常常会怀疑路由是问题所在。When an app has performance problems, routing is often suspected as the problem. 怀疑路由的原因是,控制器和 Razor Pages 等框架在其日志记录消息中报告框架内所用的时间。The reason routing is suspected is that frameworks like controllers and Razor Pages report the amount of time spent inside the framework in their logging messages. 如果控制器报告的时间与请求的总时间之间存在明显的差异:When there's a significant difference between the time reported by controllers and the total time of the request:

  • 开发者会将应用代码排除在问题根源之外。Developers eliminate their app code as the source of the problem.
  • 他们通常认为路由是问题的原因。It's common to assume routing is the cause.

路由的性能使用数千个终结点进行测试。Routing is performance tested using thousands of endpoints. 典型的应用不太可能仅仅因为太大而遇到性能问题。It's unlikely that a typical app will encounter a performance problem just by being too large. 路由性能缓慢的最常见根本原因通常在于性能不佳的自定义中间件。The most common root cause of slow routing performance is usually a badly-behaving custom middleware.

下面的代码示例演示了一种用于缩小延迟源的基本方法:This following code sample demonstrates a basic technique for narrowing down the source of delay:

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

时间路由:To time routing:

  • 使用前面代码中所示的计时中间件的副本交错执行每个中间件。Interleave each middleware with a copy of the timing middleware shown in the preceding code.
  • 添加唯一标识符,以便将计时数据与代码相关联。Add a unique identifier to correlate the timing data with the code.

这是一种可在延迟显著的情况下减少延迟的基本方法,例如超过 10msThis is a basic way to narrow down the delay when it's significant, for example, more than 10ms. Time 1 中减去 Time 2 会报告 UseRouting 中间件内所用的时间。Subtracting Time 2 from Time 1 reports the time spent inside the UseRouting middleware.

下面的代码使用一种更紧凑的方法来处理前面的计时代码:The following code uses a more compact approach to the preceding timing code:

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

可能比较昂贵的路由功能Potentially expensive routing features

下面的列表提供了一些路由功能,这些功能相对于基本路由模板来说比较昂贵:The following list provides some insight into routing features that are relatively expensive compared with basic route templates:

  • 正则表达式:可以编写复杂的正则表达式,或具有少量输入的长时间运行时间。Regular expressions: It's possible to write regular expressions that are complex, or have long running time with a small amount of input.

  • 复杂段 ({x}-{y}-{z}):Complex segments ({x}-{y}-{z}):

    • 比分析常规 URL 路径段贵得多。Are significantly more expensive than parsing a regular URL path segment.
    • 导致更多的子字符串被分配。Result in many more substrings being allocated.
    • ASP.NET Core 3.0 路由性能更新中未更新的复杂段逻辑。The complex segment logic was not updated in ASP.NET Core 3.0 routing performance update.
  • 同步数据访问:许多复杂应用都将数据库访问作为其路由的一部分。Synchronous data access: Many complex apps have database access as part of their routing. ASP.NET Core 2.2 及更低版本的路由可能未提供适当的扩展点,因此无法支持数据库访问路由。ASP.NET Core 2.2 and earlier routing might not provide the right extensibility points to support database access routing. 例如,IRouteConstraintIActionConstraint 是同步的。For example, IRouteConstraint, and IActionConstraint are synchronous. 扩展点(如 MatcherPolicyEndpointSelectorContext)是异步的。Extensibility points such as MatcherPolicy and EndpointSelectorContext are asynchronous.

库创建者指南Guidance for library authors

本部分包含基于路由构建的库创建者指南。This section contains guidance for library authors building on top of routing. 这些详细信息旨在确保应用开发者可以在使用扩展路由的库和框架时具有良好的体验。These details are intended to ensure that app developers have a good experience using libraries and frameworks that extend routing.

定义终结点Define endpoints

若要创建一个使用路由进行 URL 匹配的框架,请先定义在 UseEndpoints 之上进行构建的用户体验。To create a framework that uses routing for URL matching, start by defining a user experience that builds on top of UseEndpoints.

IEndpointRouteBuilder 之上进行构建。DO build on top of IEndpointRouteBuilder. 由此,用户就可以用其他 ASP.NET Core 功能编写框架,而不会造成混淆。This allows users to compose your framework with other ASP.NET Core features without confusion. 每个 ASP.NET Core 模板都包含路由。Every ASP.NET Core template includes routing. 假定路由存在并且用户熟悉路由。Assume routing is present and familiar for users.

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

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

从对实现 IEndpointConventionBuilderMapMyFramework(...) 的调用返回密式具体类型。DO return a sealed concrete type from a call to MapMyFramework(...) that implements IEndpointConventionBuilder. 大多数框架 Map... 方法都遵循此模式。Most framework Map... methods follow this pattern. IEndpointConventionBuilder 接口:The IEndpointConventionBuilder interface:

  • 允许元数据的可组合性。Allows composability of metadata.
  • 面向多种扩展方法。Is targeted by a variety of extension methods.

通过声明自己的类型,你可以将自己的框架特定功能添加到生成器中。Declaring your own type allows you to add your own framework-specific functionality to the builder. 可以包装一个框架声明的生成器并向其转发调用。It's ok to wrap a framework-declared builder and forward calls to it.

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

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

请考虑编写自己的 EndpointDataSourceCONSIDER writing your own EndpointDataSource. EndpointDataSource 是用于声明和更新终结点集合的低级别基元。EndpointDataSource is the low-level primitive for declaring and updating a collection of endpoints. EndpointDataSource 是控制器和 Razor Pages 使用的强大 API。EndpointDataSource is a powerful API used by controllers and Razor Pages.

路由测试具有非更新数据源的基本示例The routing tests have a basic example of a non-updating data source.

默认情况下,请不要尝试注册 EndpointDataSourceDO NOT attempt to register an EndpointDataSource by default. 要求用户在 UseEndpoints 中注册你的框架。Require users to register your framework in UseEndpoints. 路由的理念是,默认情况下不包含任何内容,并且 UseEndpoints 是注册终结点的位置。The philosophy of routing is that nothing is included by default, and that UseEndpoints is the place to register endpoints.

创建路由集成式中间件Creating routing-integrated middleware

请考虑将元数据类型定义为接口。CONSIDER defining metadata types as an interface.

请实现在类和方法上使用元数据类型。DO make it possible to use metadata types as an attribute on classes and methods.

public interface ICoolMetadata
{
    bool IsCool { get; }
}

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

控制器和 Razor Pages 等框架支持将元数据特性应用到类型和方法。Frameworks like controllers and Razor Pages support applying metadata attributes to types and methods. 如果声明元数据类型:If you declare metadata types:

  • 将它们作为特性进行访问。Make them accessible as attributes.
  • 大多数用户都熟悉应用特性。Most users are familiar with applying attributes.

将元数据类型声明为接口可增加另一层灵活性:Declaring a metadata type as an interface adds another layer of flexibility:

  • 接口是可组合的。Interfaces are composable.
  • 开发者可以声明自己的且组合多个策略的类型。Developers can declare their own types that combine multiple policies.

请实现元数据替代,如以下示例中所示:DO make it possible to override metadata, as shown in the following example:

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

遵循这些准则的最佳方式是避免定义标记元数据:The best way to follow these guidelines is to avoid defining marker metadata:

  • 不要只是查找元数据类型的状态。Don't just look for the presence of a metadata type.
  • 定义元数据的属性并检查该属性。Define a property on the metadata and check the property.

对元数据集合进行排序,并支持按优先级替代。The metadata collection is ordered and supports overriding by priority. 对于控制器,操作方法上的元数据是最具体的。In the case of controllers, metadata on the action method is most specific.

使中间件始终有用,不管有没有路由。DO make middleware useful with and without routing.

app.UseRouting();

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

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

作为此准则的一个示例,请考虑 UseAuthorization 中间件。As an example of this guideline, consider the UseAuthorization middleware. 授权中间件允许传入回退策略。The authorization middleware allows you to pass in a fallback policy. 如果指定了回退策略,则适用于以下两者:The fallback policy, if specified, applies to both:

  • 无指定策略的终结点。Endpoints without a specified policy.
  • 与终结点不匹配的请求。Requests that don't match an endpoint.

这使得授权中间件在路由上下文之外很有用。This makes the authorization middleware useful outside of the context of routing. 授权中间件可用于传统中间件的编程。The authorization middleware can be used for traditional middleware programming.

调试诊断Debug diagnostics

要获取详细的路由诊断输出,请将 Logging:LogLevel:Microsoft 设置为 DebugFor detailed routing diagnostic output, set Logging:LogLevel:Microsoft to Debug. 在开发环境中,将appsettings.Development.js上的日志级别设置为:In the development environment, set the log level in appsettings.Development.json:

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

路由负责将请求 URI 映射到终结点并向这些终结点调度传入的请求。Routing is responsible for mapping request URIs to endpoints and dispatching incoming requests to those endpoints. 路由在应用中定义,并在应用启动时进行配置。Routes are defined in the app and configured when the app starts. 路由可以选择从请求包含的 URL 中提取值,然后这些值便可用于处理请求。A route can optionally extract values from the URL contained in the request, and these values can then be used for request processing. 通过使用应用中的路由信息,路由还能生成映射到终结点的 URL。Using route information from the app, routing is also able to generate URLs that map to endpoints.

要在 ASP.NET Core 2.2 中使用最新路由方案,请在 Startup.ConfigureServices 中为 MVC 服务注册指定兼容性版本To use the latest routing scenarios in ASP.NET Core 2.2, specify the compatibility version to the MVC services registration in Startup.ConfigureServices:

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

EnableEndpointRouting 选项确定路由是应在内部使用 ASP.NET Core 2.1 或更早版本的基于终结点的逻辑还是使用其基于 IRouter 的逻辑。The EnableEndpointRouting option determines if routing should internally use endpoint-based logic or the IRouter-based logic of ASP.NET Core 2.1 or earlier. 兼容性版本设置为 2.2 或更高版本时,默认值为 trueWhen the compatibility version is set to 2.2 or later, the default value is true. 将值设置为 false 以使用先前的路由逻辑:Set the value to false to use the prior routing logic:

// 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 版本For more information on IRouter-based routing, see the ASP.NET Core 2.1 version of this topic.

重要

本文档介绍较低级别的 ASP.NET Core 路由。This document covers low-level ASP.NET Core routing. 有关 ASP.NET Core MVC 路由的信息,请参阅 在 ASP.NET Core 中路由到控制器操作For information on ASP.NET Core MVC routing, see 在 ASP.NET Core 中路由到控制器操作. 有关 Razor Pages 中路由约定的信息,请参阅 ASP.NET Core 中的 Razor Pages 路由和应用约定For information on routing conventions in Razor Pages, see ASP.NET Core 中的 Razor Pages 路由和应用约定.

查看或下载示例代码如何下载View or download sample code (how to download)

路由基础知识Routing basics

大多数应用应选择基本的描述性路由方案,让 URL 有可读性和意义。Most apps should choose a basic and descriptive routing scheme so that URLs are readable and meaningful. 默认传统路由 {controller=Home}/{action=Index}/{id?}The default conventional route {controller=Home}/{action=Index}/{id?}:

  • 支持基本的描述性路由方案。Supports a basic and descriptive routing scheme.
  • 是基于 UI 的应用的有用起点。Is a useful starting point for UI-based apps.

开发者通常在专业情况下使用特性路由或专用传统路由向应用的高流量区域添加其他简洁路由。Developers commonly add additional terse routes to high-traffic areas of an app in specialized situations using attribute routing or dedicated conventional routes. 专用情况示例包括:博客和电子商务终结点。Specialized situations examples include, blog and ecommerce endpoints.

Web API 应使用属性路由,将应用功能建模为一组资源,其中操作是由 HTTP 谓词表示。Web APIs should use attribute routing to model the app's functionality as a set of resources where operations are represented by HTTP verbs. 也就是说,对同一逻辑资源执行的许多操作(例如,GET 和 POST)都使用相同 URL。This means that many operations, for example, GET, and POST, on the same logical resource use the same URL. 属性路由提供了精心设计 API 的公共终结点布局所需的控制级别。Attribute routing provides a level of control that's needed to carefully design an API's public endpoint layout.

Razor Pages 应用使用默认的传统路由从应用的“页面”文件夹中提供命名资源。Razor Pages apps use default conventional routing to serve named resources in the Pages folder of an app. 还可以使用其他约定来自定义 Razor Pages 路由行为。Additional conventions are available that allow you to customize Razor Pages routing behavior. 有关详细信息,请参阅 ASP.NET Core 中的 Razor Pages 介绍ASP.NET Core 中的 Razor Pages 路由和应用约定For more information, see ASP.NET Core 中的 Razor Pages 介绍 and ASP.NET Core 中的 Razor Pages 路由和应用约定.

借助 URL 生成支持,无需通过硬编码 URL 将应用关联到一起,即可开发应用。URL generation support allows the app to be developed without hard-coding URLs to link the app together. 此支持允许从基本路由配置入手,并在确定应用的资源布局后修改路由。This support allows for starting with a basic routing configuration and modifying the routes after the app's resource layout is determined.

路由使用“终结点”(Endpoint) 来表示应用中的逻辑终结点。Routing uses endpoints (Endpoint) to represent logical endpoints in an app.

终结点定义用于处理请求的委托和任意元数据的集合。An endpoint defines a delegate to process requests and a collection of arbitrary metadata. 元数据用于实现横切关注点,该实现基于附加到每个终结点的策略和配置。The metadata is used implement cross-cutting concerns based on policies and configuration attached to each endpoint.

路由系统具有以下特征:The routing system has the following characteristics:

  • 路由模板语法用于通过标记化路由参数来定义路由。Route template syntax is used to define routes with tokenized route parameters.

  • 可以使用常规样式和属性样式终结点配置。Conventional-style and attribute-style endpoint configuration is permitted.

  • IRouteConstraint 用于确定 URL 参数是否包含给定的终结点约束的有效值。IRouteConstraint is used to determine whether a URL parameter contains a valid value for a given endpoint constraint.

  • 应用模型(如 MVC/Razor Pages)注册其所有终结点,这些终结点具有可预测的路由方案实现。App models, such as MVC/Razor Pages, register all of their endpoints, which have a predictable implementation of routing scenarios.

  • 路由实现会在中间件管道中任何所需位置制定路由决策。The routing implementation makes routing decisions wherever desired in the middleware pipeline.

  • 路由中间件之后出现的中间件可以检查路由中间件针对给定请求 URI 的终结点决策结果。Middleware that appears after a Routing Middleware can inspect the result of the Routing Middleware's endpoint decision for a given request URI.

  • 可以在中间件管道中的任何位置枚举应用中的所有终结点。It's possible to enumerate all of the endpoints in the app anywhere in the middleware pipeline.

  • 应用可根据终结点信息使用路由生成 URL(例如,用于重定向或链接),从而避免硬编码 URL,这有助于可维护性。An app can use routing to generate URLs (for example, for redirection or links) based on endpoint information and thus avoid hard-coded URLs, which helps maintainability.

  • URL 生成是基于支持任意可扩展性的地址:URL generation is based on addresses, which support arbitrary extensibility:

备注

在 ASP.NET Core 2.2 中发布终结点路由后,终结点链接的适用范围限制为 MVC/Razor Pages 操作和页面。With the release of endpoint routing in ASP.NET Core 2.2, endpoint linking is limited to MVC/Razor Pages actions and pages. 将计划在未来发布的版本中扩展终结点链接的功能。The expansions of endpoint-linking capabilities is planned for future releases.

路由通过 RouterMiddleware 类连接到中间件管道。Routing is connected to the middleware pipeline by the RouterMiddleware class. ASP.NET Core MVC 向中间件管道添加路由,作为其配置的一部分,并处理 MVC 和 Razor Pages 应用中的路由。ASP.NET Core MVC adds routing to the middleware pipeline as part of its configuration and handles routing in MVC and Razor Pages apps. 要了解如何将路由用作独立组件,请参阅使用路由中间件部分。To learn how to use routing as a standalone component, see the Use Routing Middleware section.

URL 匹配URL matching

URL 匹配是路由向终结点调度传入请求的过程。URL matching is the process by which routing dispatches an incoming request to an endpoint. 此过程基于 URL 路径中的数据,但可以进行扩展以考虑请求中的任何数据。This process is based on data in the URL path but can be extended to consider any data in the request. 向单独的处理程序调度请求的功能是缩放应用的大小和复杂性的关键。The ability to dispatch requests to separate handlers is key to scaling the size and complexity of an app.

终结点路由中的路由系统负责所有的调度决策。The routing system in endpoint routing is responsible for all dispatching decisions. 由于中间件是基于所选终结点来应用策略,因此任何可能影响调度或安全策略应用的决策都应在路由系统内部制定,这一点很重要。Since the middleware applies policies based on the selected endpoint, it's important that any decision that can affect dispatching or the application of security policies is made inside the routing system.

执行终结点委托时,将根据迄今执行的请求处理将 RouteContext.RouteData 的属性设为适当的值。When the endpoint delegate is executed, the properties of RouteContext.RouteData are set to appropriate values based on the request processing performed thus far.

RouteData.Values 是从路由中生成的路由值的字典。RouteData.Values is a dictionary of route values produced from the route. 这些值通常通过标记 URL 来确定,可用来接受用户输入,或者在应用内作出进一步的调度决策。These values are usually determined by tokenizing the URL and can be used to accept user input or to make further dispatching decisions inside the app.

RouteData.DataTokens 是一个与匹配的路由相关的其他数据的属性包。RouteData.DataTokens is a property bag of additional data related to the matched route. 提供 DataTokens 以支持将状态数据与每个路由相关联,以便应用可根据所匹配的路由作出决策。DataTokens are provided to support associating state data with each route so that the app can make decisions based on which route matched. 这些值是开发者定义的,不会影响通过任何方式路由的行为。These values are developer-defined and do not affect the behavior of routing in any way. 此外,存储于 RouteData.DataTokens 中的值可以属于任何类型,与 RouteData.Values 相反,后者必须能够转换为字符串,或从字符串进行转换。Additionally, values stashed in RouteData.DataTokens can be of any type, in contrast to RouteData.Values, which must be convertible to and from strings.

RouteData.Routers 是参与成功匹配请求的路由的列表。RouteData.Routers is a list of the routes that took part in successfully matching the request. 路由可以相互嵌套。Routes can be nested inside of one another. Routers 属性可以通过导致匹配的逻辑路由树反映该路径。The Routers property reflects the path through the logical tree of routes that resulted in a match. 通常情况下,Routers 中的第一项是路由集合,应该用于生成 URL。Generally, the first item in Routers is the route collection and should be used for URL generation. Routers 中的最后一项是匹配的路由处理程序。The last item in Routers is the route handler that matched.

使用 LinkGenerator 的 URL 生成URL generation with LinkGenerator

URL 生成是通过其可根据一组路由值创建 URL 路径的过程。URL generation is the process by which routing can create a URL path based on a set of route values. 这需要考虑终结点与访问它们的 URL 之间的逻辑分隔。This allows for a logical separation between your endpoints and the URLs that access them.

终结点路由包含链接生成器 API (LinkGenerator)。Endpoint routing includes the Link Generator API (LinkGenerator). LinkGenerator 是可从 DI 检索的单一实例服务。LinkGenerator is a singleton service that can be retrieved from DI. 该 API 可在执行请求的上下文之外使用。The API can be used outside of the context of an executing request. MVC 的 IUrlHelper 和依赖 IUrlHelper 的方案(如 Tag Helpers、HTML Helpers 和 Action Results)使用链接生成器提供链接生成功能。MVC's IUrlHelper and scenarios that rely on IUrlHelper, such as Tag Helpers, HTML Helpers, and Action Results, use the link generator to provide link generating capabilities.

链接生成器基于“地址”和“地址方案”概念 。The link generator is backed by the concept of an address and address schemes. 地址方案是确定哪些终结点用于链接生成的方式。An address scheme is a way of determining the endpoints that should be considered for link generation. 例如,许多用户熟悉的来自 MVC/Razor Pages 的路由名称和路由值方案都是作为地址方案实现的。For example, the route name and route values scenarios many users are familiar with from MVC/Razor Pages are implemented as an address scheme.

链接生成器可以通过以下扩展方法链接到 MVC/Razor Pages 操作和页面:The link generator can link to MVC/Razor Pages actions and pages via the following extension methods:

这些方法的重载接受包含 HttpContext 的参数。An overload of these methods accepts arguments that include the HttpContext. 这些方法在功能上等同于 Url.ActionUrl.Page,但提供了更大的灵活性和更多的选项。These methods are functionally equivalent to Url.Action and Url.Page but offer additional flexibility and options.

GetPath* 方法与 Url.ActionUrl.Page 最相似,因为它们生成包含绝对路径的 URI。The GetPath* methods are most similar to Url.Action and Url.Page in that they generate a URI containing an absolute path. GetUri* 方法始终生成包含方案和主机的绝对 URI。The GetUri* methods always generate an absolute URI containing a scheme and host. 接受 HttpContext 的方法在执行请求的上下文中生成 URI。The methods that accept an HttpContext generate a URI in the context of the executing request. 除非重写,否则将使用来自执行请求的环境路由值、URL 基本路径、方案和主机。The ambient route values, URL base path, scheme, and host from the executing request are used unless overridden.

使用地址调用 LinkGeneratorLinkGenerator is called with an address. 生成 URI 的过程分两步进行:Generating a URI occurs in two steps:

  1. 将地址绑定到与地址匹配的终结点列表。An address is bound to a list of endpoints that match the address.
  2. 计算每个终结点的 RoutePattern,直到找到与提供的值匹配的路由模式。Each endpoint's RoutePattern is evaluated until a route pattern that matches the supplied values is found. 输出结果会与提供给链接生成器的其他 URI 部分进行组合并返回。The resulting output is combined with the other URI parts supplied to the link generator and returned.

对任何类型的地址,LinkGenerator 提供的方法均支持标准链接生成功能。The methods provided by LinkGenerator support standard link generation capabilities for any type of address. 使用链接生成器的最简便方法是通过扩展方法对特定地址类型执行操作。The most convenient way to use the link generator is through extension methods that perform operations for a specific address type.

扩展方法Extension Method 描述Description
GetPathByAddress 根据提供的值生成具有绝对路径的 URI。Generates a URI with an absolute path based on the provided values.
GetUriByAddress 根据提供的值生成绝对 URI。Generates an absolute URI based on the provided values.

警告

请注意有关调用 LinkGenerator 方法的下列含义:Pay attention to the following implications of calling LinkGenerator methods:

  • 对于不验证传入请求的 Host 标头的应用配置,请谨慎使用 GetUri* 扩展方法。Use GetUri* extension methods with caution in an app configuration that doesn't validate the Host header of incoming requests. 如果未验证传入请求的 Host标头,则可能以视图/页面中 URI 的形式将不受信任的请求输入发送回客户端。If the Host header of incoming requests isn't validated, untrusted request input can be sent back to the client in URIs in a view/page. 建议所有生产应用都将其服务器配置为针对已知有效值验证 Host 标头。We recommend that all production apps configure their server to validate the Host header against known valid values.

  • 在中间件中将 LinkGeneratorMapMapWhen 结合使用时,请小心谨慎。Use LinkGenerator with caution in middleware in combination with Map or MapWhen. Map* 会更改执行请求的基路径,这会影响链接生成的输出。Map* changes the base path of the executing request, which affects the output of link generation. 所有 LinkGenerator API 都允许指定基路径。All of the LinkGenerator APIs allow specifying a base path. 始终指定一个空的基路径来撤消 Map* 对链接生成的影响。Always specify an empty base path to undo Map*'s affect on link generation.

与早期版本路由的差异Differences from earlier versions of routing

ASP.NET Core 2.2 或更高版本中的终结点路由与 ASP.NET Core 中早期版本的路由之间存在一些差异:A few differences exist between endpoint routing in ASP.NET Core 2.2 or later and earlier versions of routing in ASP.NET Core:

  • 终结点路由系统不支持基于 IRouter 的可扩展性,包括从 Route 继承。The endpoint routing system doesn't support IRouter-based extensibility, including inheriting from Route.

  • 终结点路由不支持 WebApiCompatShimEndpoint routing doesn't support WebApiCompatShim. 使用 2.1 兼容性版本 (.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)) 以继续使用兼容性填充码。Use the 2.1 compatibility version (.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)) to continue using the compatibility shim.

  • 在使用传统路由时,终结点路由对生成的 URI 的大小写具有不同的行为。Endpoint Routing has different behavior for the casing of generated URIs when using conventional routes.

    请考虑以下默认路由模板:Consider the following default route template:

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

    假设使用以下路由生成某个操作的链接:Suppose you generate a link to an action using the following route:

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

    如果使用基于 IRouter 的路由,此代码生成 URI /blog/ReadPost/17,该 URI 遵循所提供路由值的大小写。With IRouter-based routing, this code generates a URI of /blog/ReadPost/17, which respects the casing of the provided route value. ASP.NET Core 2.2 或更高版本中的终结点路由生成 /Blog/ReadPost/17(“Blog”大写)。Endpoint routing in ASP.NET Core 2.2 or later produces /Blog/ReadPost/17 ("Blog" is capitalized). 终结点路由提供 IOutboundParameterTransformer 接口,可用于在全局范围自定义此行为或应用不同的约定来映射 URL。Endpoint routing provides the IOutboundParameterTransformer interface that can be used to customize this behavior globally or to apply different conventions for mapping URLs.

    有关详细信息,请参阅参数转换器参考部分。For more information, see the Parameter transformer reference section.

  • 在试图链接到不存在的控制器/操作或页面时,MVC/Razor Pages 通过传统路由执行的链接生成,其操作具有不同的行为。Link Generation used by MVC/Razor Pages with conventional routes behaves differently when attempting to link to an controller/action or page that doesn't exist.

    请考虑以下默认路由模板:Consider the following default route template:

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

    假设使用以下路由通过默认模板生成某个操作的链接:Suppose you generate a link to an action using the default template with the following:

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

    如果使用基于 IRouter 的路由,即使 BlogController 不存在或没有 ReadPost 操作方法,结果也始终为 /Blog/ReadPost/17With IRouter-based routing, the result is always /Blog/ReadPost/17, even if the BlogController doesn't exist or doesn't have a ReadPost action method. 正如所料,如果操作方法存在,ASP.NET Core 2.2 或更高版本中的终结点路由会生成 /Blog/ReadPost/17As expected, endpoint routing in ASP.NET Core 2.2 or later produces /Blog/ReadPost/17 if the action method exists. 但是,如果操作不存在,终结点路由会生成空字符串。However, endpoint routing produces an empty string if the action doesn't exist. 从概念上讲,如果操作不存在,终结点路由不会假定终结点存在。Conceptually, endpoint routing doesn't assume that the endpoint exists if the action doesn't exist.

  • 与终结点路由一起使用时,链接生成环境值失效算法的行为会有所不同。The link generation ambient value invalidation algorithm behaves differently when used with endpoint routing.

    环境值失效 是一种算法,用于决定当前正在执行的请求(环境值)中的哪些路由值可用于链接生成操作。Ambient value invalidation is the algorithm that decides which route values from the currently executing request (the ambient values) can be used in link generation operations. 链接到不同操作时,传统路由会使额外的路由值失效。Conventional routing always invalidated extra route values when linking to a different action. ASP.NET Core 2.2 之前的版本中,属性路由不具有此行为。Attribute routing didn't have this behavior prior to the release of ASP.NET Core 2.2. 在 ASP.NET Core 的早期版本中,如果有另一个操作使用同一路由参数名称,则该操作的链接会导致发生链接生成错误。In earlier versions of ASP.NET Core, links to another action that use the same route parameter names resulted in link generation errors. 在 ASP.NET Core 2.2 或更高版本中,链接到另一个操作时,这两种路由形式都会使值失效。In ASP.NET Core 2.2 or later, both forms of routing invalidate values when linking to another action.

    请考虑 ASP.NET Core2.1 或更高版本中的以下示例。Consider the following example in ASP.NET Core 2.1 or earlier. 链接到另一个操作(或另一页面)时,路由值可能会按非预期的方式被重用。When linking to another action (or another page), route values can be reused in undesirable ways.

    在 /Pages/Store/Product.cshtml 中:In /Pages/Store/Product.cshtml:

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

    在 /Pages/Login.cshtml 中:In /Pages/Login.cshtml:

    @page "{id?}"
    

    如果 ASP.NET Core 2.1 或更早版本中的 URI 为 /Store/Product/18,则由 @Url.Page("/Login") 在 Store/Info 页面上生成的链接为 /Login/18If the URI is /Store/Product/18 in ASP.NET Core 2.1 or earlier, the link generated on the Store/Info page by @Url.Page("/Login") is /Login/18. 即使链接目标完全是应用的其他部分,仍会重用 id 值 18。The id value of 18 is reused, even though the link destination is different part of the app entirely. /Login 页面上下文中的 id 路由值可能是用户 ID 值,而非存储产品 ID 值。The id route value in the context of the /Login page is probably a user ID value, not a store product ID value.

    在 ASP.NET Core 2.2 或更高版本的终结点路由中,结果为 /LoginIn endpoint routing with ASP.NET Core 2.2 or later, the result is /Login. 当链接的目标是另一个操作或页面时,不会重复使用环境值。Ambient values aren't reused when the linked destination is a different action or page.

  • 往返路由参数语法:使用双星号 (**) catch-all 参数语法时,不对正斜杠进行编码。Round-tripping route parameter syntax: Forward slashes aren't encoded when using a double-asterisk (**) catch-all parameter syntax.

    在链接生成期间,路由系统对双星号 (**) catch-all 参数(例如,{**myparametername})中捕获的除正斜杠外的值进行编码。During link generation, the routing system encodes the value captured in a double-asterisk (**) catch-all parameter (for example, {**myparametername}) except the forward slashes. 在 ASP.NET Core 2.2 或更高版本中,基于 IRouter 的路由支持双星号 catch-all 参数。The double-asterisk catch-all is supported with IRouter-based routing in ASP.NET Core 2.2 or later.

    ASP.NET Core ({*myparametername}) 早期版本中的单星号 catch-all 参数语法仍然受支持,并对正斜杠进行编码。The single asterisk catch-all parameter syntax in prior versions of ASP.NET Core ({*myparametername}) remains supported, and forward slashes are encoded.

    路由Route 链接生成方式为Link generated with
    Url.Action(new { category = "admin/products" })
    /search/{*page} /search/admin%2Fproducts(对正斜杠进行编码)/search/admin%2Fproducts (the forward slash is encoded)
    /search/{**page} /search/admin/products

中间件示例Middleware example

在以下示例中,中间件使用 LinkGenerator API 创建列出存储产品的操作方法的链接。In the following example, a middleware uses the LinkGenerator API to create link to an action method that lists store products. 应用中的任何类都可通过将链接生成器注入类并调用 GenerateLink 来使用链接生成器。Using the link generator by injecting it into a class and calling GenerateLink is available to any class in an app.

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

创建路由Create routes

大多数应用通过调用 MapRouteIRouteBuilder 上定义的一种类似的扩展方法来创建路由。Most apps create routes by calling MapRoute or one of the similar extension methods defined on IRouteBuilder. 任何 IRouteBuilder 扩展方法都会创建 Route 的实例并将其添加到路由集合中。Any of the IRouteBuilder extension methods create an instance of Route and add it to the route collection.

MapRoute 不接受路由处理程序参数。MapRoute doesn't accept a route handler parameter. MapRoute 仅添加由 DefaultHandler 处理的路由。MapRoute only adds routes that are handled by the DefaultHandler. 要了解 MVC 中的路由的详细信息,请参阅 在 ASP.NET Core 中路由到控制器操作To learn more about routing in MVC, see 在 ASP.NET Core 中路由到控制器操作.

以下代码示例是典型 ASP.NET Core MVC 路由定义使用的一个 MapRoute 调用示例:The following code example is an example of a MapRoute call used by a typical ASP.NET Core MVC route definition:

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

此模板与 URL 路径相匹配,并且提取路由值。This template matches a URL path and extracts the route values. 例如,路径 /Products/Details/17 生成以下路由值:{ controller = Products, action = Details, id = 17 }For example, the path /Products/Details/17 generates the following route values: { controller = Products, action = Details, id = 17 }.

路由值是通过将 URL 路径拆分成段,并且将每段与路由模板中的路由参数名称相匹配来确定的。Route values are determined by splitting the URL path into segments and matching each segment with the route parameter name in the route template. 路由参数已命名。Route parameters are named. 参数通过将参数名称括在大括号 { ... } 中来定义。The parameters defined by enclosing the parameter name in braces { ... }.

上述模板还可匹配 URL 路径 /,并且生成值 { controller = Home, action = Index }The preceding template could also match the URL path / and produce the values { controller = Home, action = Index }. 这是因为 {controller}{action} 路由参数具有默认值,且 id 路由参数是可选的。This occurs because the {controller} and {action} route parameters have default values and the id route parameter is optional. 路由参数名称为参数定义默认值后,等号 (=) 后将有一个值。An equals sign (=) followed by a value after the route parameter name defines a default value for the parameter. 路由参数名称后面的问号 (?) 定义了可选参数。A question mark (?) after the route parameter name defines an optional parameter.

路由匹配时,具有默认值的路由参数始终会生成路由值。Route parameters with a default value always produce a route value when the route matches. 如果没有相应的 URL 路径段,则可选参数不会生成路由值。Optional parameters don't produce a route value if there is no corresponding URL path segment. 有关路由模板方案和语法的详细说明,请参阅路由模板参考部分。See the Route template reference section for a thorough description of route template scenarios and syntax.

在以下示例中,路由参数定义 {id:int}id 路由参数定义路由约束In the following example, the route parameter definition {id:int} defines a route constraint for the id route parameter:

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

此模板与类似于 /Products/Details/17 而不是 /Products/Details/Apples 的 URL 路径相匹配。This template matches a URL path like /Products/Details/17 but not /Products/Details/Apples. 路由约束实现 IRouteConstraint 并检查路由值,以验证它们。Route constraints implement IRouteConstraint and inspect route values to verify them. 在此示例中,路由值 id 必须可转换为整数。In this example, the route value id must be convertible to an integer. 有关框架提供的路由约束的说明,请参阅路由约束参考See route-constraint-reference for an explanation of route constraints provided by the framework.

其他 MapRoute 重载接受 constraintsdataTokensdefaults 的值。Additional overloads of MapRoute accept values for constraints, dataTokens, and defaults. 这些参数的典型用法是传递匿名类型化对象,其中匿名类型的属性名匹配路由参数名称。The typical usage of these parameters is to pass an anonymously typed object, where the property names of the anonymous type match route parameter names.

以下 MapRoute 示例可创建等效路由:The following MapRoute examples create equivalent routes:

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

提示

定义约束和默认值的内联语法对于简单路由很方便。The inline syntax for defining constraints and defaults can be convenient for simple routes. 但是,存在内联语法不支持的方案,例如数据令牌。However, there are scenarios, such as data tokens, that aren't supported by inline syntax.

以下示例演示了一些其他方案:The following example demonstrates a few additional scenarios:

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

上述模板与 /Blog/All-About-Routing/Introduction 等的 URL 路径相匹配,并提取值 { controller = Blog, action = ReadArticle, article = All-About-Routing/Introduction }The preceding template matches a URL path like /Blog/All-About-Routing/Introduction and extracts the values { controller = Blog, action = ReadArticle, article = All-About-Routing/Introduction }. controlleraction 的默认路由值由路由生成,即便模板中没有对应的路由参数。The default route values for controller and action are produced by the route even though there are no corresponding route parameters in the template. 可在路由模板中指定默认值。Default values can be specified in the route template. 根据路由参数名称前的双星号 (**) 外观,article 路由参数被定义为 catch-all。The article route parameter is defined as a catch-all by the appearance of an double asterisk (**) before the route parameter name. 全方位路由参数可捕获 URL 路径的其余部分,也能匹配空白字符串。Catch-all route parameters capture the remainder of the URL path and can also match the empty string.

以下示例添加了路由约束和数据令牌:The following example adds route constraints and data tokens:

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

上述模板与 /en-US/Products/5 等 URL 路径相匹配,并且提取值 { controller = Products, action = Details, id = 5 } 和数据令牌 { locale = en-US }The preceding template matches a URL path like /en-US/Products/5 and extracts the values { controller = Products, action = Details, id = 5 } and the data tokens { locale = en-US }.

本地 Windows 令牌

路由类 URL 生成Route class URL generation

Route 类还可通过将一组路由值与其路由模板组合来生成 URL。The Route class can also perform URL generation by combining a set of route values with its route template. 从逻辑上来说,这是匹配 URL 路径的反向过程。This is logically the reverse process of matching the URL path.

提示

为更好了解 URL 生成,请想象你要生成的 URL,然后考虑路由模板将如何匹配该 URL。To better understand URL generation, imagine what URL you want to generate and then think about how a route template would match that URL. 将生成什么值?What values would be produced? 这大致相当于 URL 在 Route 类中的生成方式。This is the rough equivalent of how URL generation works in the Route class.

以下示例使用常规 ASP.NET Core MVC 默认路由:The following example uses a general ASP.NET Core MVC default route:

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

使用路由值 { controller = Products, action = List },将生成 URL /Products/ListWith the route values { controller = Products, action = List }, the URL /Products/List is generated. 路由值将替换为相应的路由参数,以形成 URL 路径。The route values are substituted for the corresponding route parameters to form the URL path. 由于 id 是可选路由参数,因此成功生成的 URL 不具有 id 的值。Since id is an optional route parameter, the URL is successfully generated without a value for id.

使用路由值 { controller = Home, action = Index },将生成 URL /With the route values { controller = Home, action = Index }, the URL / is generated. 提供的路由值与默认值匹配,并且会安全地省略与默认值对应的段。The provided route values match the default values, and the segments corresponding to the default values are safely omitted.

已生成的两个 URL 将往返以下路由定义 (/Home/Index/),并生成用于生成该 URL 的相同路由值。Both URLs generated round-trip with the following route definition (/Home/Index and /) produce the same route values that were used to generate the URL.

备注

使用 ASP.NET Core MVC 应用应该使用 UrlHelper 生成 URL,而不是直接调用到路由。An app using ASP.NET Core MVC should use UrlHelper to generate URLs instead of calling into routing directly.

有关 URL 生成的详细信息,请参阅 Url 生成参考部分。For more information on URL generation, see the Url generation reference section.

使用路由中间件Use Routing Middleware

引用应用项目文件中的 Microsoft.AspNetCore.App 元包Reference the Microsoft.AspNetCore.App metapackage in the app's project file.

将路由添加到 Startup.ConfigureServices 中的服务容器:Add routing to the service container in Startup.ConfigureServices:

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

必须在 Startup.Configure 方法中配置路由。Routes must be configured in the Startup.Configure method. 该示例应用使用以下 API:The sample app uses the following APIs:

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 的响应。The following table shows the responses with the given URIs.

URIURI 响应Response
/package/create/3 Hello!Hello! Route values: [operation, create], [id, 3]Route values: [operation, create], [id, 3]
/package/track/-3 Hello!Hello! Route values: [operation, track], [id, -3]Route values: [operation, track], [id, -3]
/package/track/-3/ Hello!Hello! Route values: [operation, track], [id, -3]Route values: [operation, track], [id, -3]
/package/track/ 请求失败,不匹配。The request falls through, no match.
GET /hello/Joe Hi, Joe!Hi, Joe!
POST /hello/Joe 请求失败,仅匹配 HTTP GET。The request falls through, matches HTTP GET only.
GET /hello/Joe/Smith 请求失败,不匹配。The request falls through, no match.

框架可提供一组用于创建路由 (RequestDelegateRouteBuilderExtensions) 的扩展方法:The framework provides a set of extension methods for creating routes (RequestDelegateRouteBuilderExtensions):

Map[Verb] 方法将使用约束来将路由限制为方法名称中的 HTTP 谓词。The Map[Verb] methods use constraints to limit the route to the HTTP Verb in the method name. 有关示例,请参阅 MapGetMapVerbFor example, see MapGet and MapVerb.

路由模板参考Route template reference

如果路由找到匹配项,大括号 ({ ... }) 内的令牌定义绑定的路由参数。Tokens within curly braces ({ ... }) define route parameters that are bound if the route is matched. 可在路由段中定义多个路由参数,但必须由文本值隔开。You can define more than one route parameter in a route segment, but they must be separated by a literal value. 例如,{controller=Home}{action=Index} 不是有效的路由,因为 {controller}{action} 之间没有文本值。For example, {controller=Home}{action=Index} isn't a valid route, since there's no literal value between {controller} and {action}. 这些路由参数必须具有名称,且可能指定了其他属性。These route parameters must have a name and may have additional attributes specified.

路由参数以外的文本(例如 {id})和路径分隔符 / 必须匹配 URL 中的文本。Literal text other than route parameters (for example, {id}) and the path separator / must match the text in the URL. 文本匹配区分大小写,并且基于 URL 路径已解码的表示形式。Text matching is case-insensitive and based on the decoded representation of the URLs path. 要匹配文字路由参数分隔符({}),请通过重复该字符({{}})来转义分隔符。To match a literal route parameter delimiter ({ or }), escape the delimiter by repeating the character ({{ or }}).

尝试捕获具有可选文件扩展名的文件名的 URL 模式还有其他注意事项。URL patterns that attempt to capture a file name with an optional file extension have additional considerations. 例如,考虑模板 files/{filename}.{ext?}For example, consider the template files/{filename}.{ext?}. filenameext 的值都存在时,将填充这两个值。When values for both filename and ext exist, both values are populated. 如果 URL 中仅存在 filename 的值,则路由匹配,因为尾随句点 (.) 是可选的。If only a value for filename exists in the URL, the route matches because the trailing period (.) is optional. 以下 URL 与此路由相匹配:The following URLs match this route:

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

可以使用星号 (*) 或双星号 (**) 作为路由参数的前缀,以绑定到 URI 的其余部分。You can use an asterisk (*) or double asterisk (**) as a prefix to a route parameter to bind to the rest of the URI. 这些称为 catch-all 参数。These are called a catch-all parameters. 例如,blog/{**slug} 将匹配以 /blog 开头且其后带有任何值(将分配给 slug 路由值)的 URI。For example, blog/{**slug} matches any URI that starts with /blog and has any value following it, which is assigned to the slug route value. 全方位参数还可以匹配空字符串。Catch-all parameters can also match the empty string.

使用路由生成 URL(包括路径分隔符 (/))时,catch-all 参数会转义相应的字符。The catch-all parameter escapes the appropriate characters when the route is used to generate a URL, including path separator (/) characters. 例如,路由值为 { path = "my/path" } 的路由 foo/{*path} 生成 foo/my%2FpathFor example, the route foo/{*path} with route values { path = "my/path" } generates foo/my%2Fpath. 请注意转义的正斜杠。Note the escaped forward slash. 要往返路径分隔符,请使用 ** 路由参数前缀。To round-trip path separator characters, use the ** route parameter prefix. { path = "my/path" } 的路由 foo/{**path} 生成 foo/my/pathThe route foo/{**path} with { path = "my/path" } generates foo/my/path.

路由参数可能具有指定的默认值,方法是在参数名称后使用等号 (=) 隔开以指定默认值。Route parameters may have default values designated by specifying the default value after the parameter name separated by an equals sign (=). 例如,{controller=Home}Home 定义为 controller 的默认值。For example, {controller=Home} defines Home as the default value for controller. 如果参数的 URL 中不存在任何值,则使用默认值。The default value is used if no value is present in the URL for the parameter. 通过在参数名称的末尾附加问号 (?) 可使路由参数成为可选项,如 id? 中所示。Route parameters are made optional by appending a question mark (?) to the end of the parameter name, as in id?. 可选值和默认路径参数的区别在于具有默认值的路由参数始终会生成值 —,而可选参数仅当请求 URL 提供值时才会具有值。The difference between optional values and default route parameters is that a route parameter with a default value always produces a value—an optional parameter has a value only when a value is provided by the request URL.

路由参数可能具有必须与从 URL 中绑定的路由值匹配的约束。Route parameters may have constraints that must match the route value bound from the URL. 在路由参数后面添加一个冒号 (:) 和约束名称可指定路由参数上的内联约束。Adding a colon (:) and constraint name after the route parameter name specifies an inline constraint on a route parameter. 如果约束需要参数,将以在约束名称后括在括号 ((...)) 中的形式提供。If the constraint requires arguments, they're enclosed in parentheses ((...)) after the constraint name. 通过追加另一个冒号 (:) 和约束名称,可指定多个内联约束。Multiple inline constraints can be specified by appending another colon (:) and constraint name.

约束名称和参数将传递给 IInlineConstraintResolver 服务,以创建 IRouteConstraint 的实例,用于处理 URL。The constraint name and arguments are passed to the IInlineConstraintResolver service to create an instance of IRouteConstraint to use in URL processing. 例如,路由模板 blog/{article:minlength(10)} 使用参数 10 指定 minlength 约束。For example, the route template blog/{article:minlength(10)} specifies a minlength constraint with the argument 10. 有关路由约束详情以及框架提供的约束列表,请参阅路由约束引用部分。For more information on route constraints and a list of the constraints provided by the framework, see the Route constraint reference section.

路由参数还可以具有参数转换器,用于在生成链接以及将操作和页面匹配到 URI 时转换参数的值。Route parameters may also have parameter transformers, which transform a parameter's value when generating links and matching actions and pages to URLs. 与约束类似,可以通过在路由参数名称后面添加冒号 (:) 和转换器名称,将参数变换器内联添加到路径参数。Like constraints, parameter transformers can be added inline to a route parameter by adding a colon (:) and transformer name after the route parameter name. 例如,路由模板 blog/{article:slugify} 指定 slugify 转换器。For example, the route template blog/{article:slugify} specifies a slugify transformer. 有关参数转换的详细信息,请参阅参数转换器参考部分。For more information on parameter transformers, see the Parameter transformer reference section.

下表演示了示例路由模板及其行为。The following table demonstrates example route templates and their behavior.

路由模板Route Template 示例匹配 URIExample Matching URI 请求 URI…The request URI…
hello /hello 仅匹配单个路径 /helloOnly matches the single path /hello.
{Page=Home} / 匹配并将 Page 设置为 HomeMatches and sets Page to Home.
{Page=Home} /Contact 匹配并将 Page 设置为 ContactMatches and sets Page to Contact.
{controller}/{action}/{id?} /Products/List 映射到 Products 控制器和 List 操作。Maps to the Products controller and List action.
{controller}/{action}/{id?} /Products/Details/123 映射到 Products 控制器和 Details 操作(id 设置为 123)。Maps to the Products controller and Details action (id set to 123).
{controller=Home}/{action=Index}/{id?} / 映射到 Home 控制器和 Index 方法(忽略 id)。Maps to the Home controller and Index method (id is ignored).

使用模板通常是进行路由最简单的方法。Using a template is generally the simplest approach to routing. 还可在路由模板外指定约束和默认值。Constraints and defaults can also be specified outside the route template.

提示

启用日志记录以查看内置路由实现(如 Route)如何匹配请求。Enable Logging to see how the built-in routing implementations, such as Route, match requests.

保留的路由名称Reserved routing names

以下关键字是保留的名称,它们不能用作路由名称或参数:The following keywords are reserved names and can't be used as route names or parameters:

  • action
  • area
  • controller
  • handler
  • page

路由约束参考Route constraint reference

路由约束在传入 URL 发生匹配时执行,URL 路径标记为路由值。Route constraints execute when a match has occurred to the incoming URL and the URL path is tokenized into route values. 路径约束通常检查通过路径模板关联的路径值,并对该值是否可接受做出是/否决定。Route constraints generally inspect the route value associated via the route template and make a yes/no decision about whether or not the value is acceptable. 某些路由约束使用路由值以外的数据来考虑是否可以路由请求。Some route constraints use data outside the route value to consider whether the request can be routed. 例如,HttpMethodRouteConstraint 可以根据其 HTTP 谓词接受或拒绝请求。For example, the HttpMethodRouteConstraint can accept or reject a request based on its HTTP verb. 约束用于路由请求和链接生成。Constraints are used in routing requests and link generation.

警告

请勿将约束用于“输入验证”。Don't use constraints for input validation. 如果将约束用于“输入约束”,那么无效输入将导致“404 - 未找到”响应,而不是含相应错误消息的“400 - 错误请求” 。If constraints are used for input validation, invalid input results in a 404 - Not Found response instead of a 400 - Bad Request with an appropriate error message. 路由约束用于消除类似路由的歧义,而不是验证特定路由的输入。Route constraints are used to disambiguate similar routes, not to validate the inputs for a particular route.

下表演示示例路由约束及其预期行为。The following table demonstrates example route constraints and their expected behavior.

约束Constraint 示例Example 匹配项示例Example matches 说明Notes
int {id:int} 123456789, -123456789123456789, -123456789 匹配任何整数。Matches any integer.
bool {active:bool} true, FALSEtrue, FALSE 匹配 truefalseMatches true or false. 不区分大小写。Case-insensitive.
datetime {dob:datetime} 2016-12-31, 2016-12-31 7:32pm2016-12-31, 2016-12-31 7:32pm 在固定区域性中匹配有效的 DateTime 值。Matches a valid DateTime value in the invariant culture. 请参阅前面的警告。See preceding warning.
decimal {price:decimal} 49.99, -1,000.0149.99, -1,000.01 在固定区域性中匹配有效的 decimal 值。Matches a valid decimal value in the invariant culture. 请参阅前面的警告。See preceding warning.
double {weight:double} 1.234, -1,001.01e81.234, -1,001.01e8 在固定区域性中匹配有效的 double 值。Matches a valid double value in the invariant culture. 请参阅前面的警告。See preceding warning.
float {weight:float} 1.234, -1,001.01e81.234, -1,001.01e8 在固定区域性中匹配有效的 float 值。Matches a valid float value in the invariant culture. 请参阅前面的警告。See preceding warning.
guid {id:guid} CD2C1638-1638-72D5-1638-DEADBEEF1638, {CD2C1638-1638-72D5-1638-DEADBEEF1638}CD2C1638-1638-72D5-1638-DEADBEEF1638, {CD2C1638-1638-72D5-1638-DEADBEEF1638} 匹配有效的 Guid 值。Matches a valid Guid value.
long {ticks:long} 123456789, -123456789123456789, -123456789 匹配有效的 long 值。Matches a valid long value.
minlength(value) {username:minlength(4)} Rick 字符串必须至少为 4 个字符。String must be at least 4 characters.
maxlength(value) {filename:maxlength(8)} MyFile 字符串最多包含 8 个字符。String has maximum of 8 characters.
length(length) {filename:length(12)} somefile.txt 字符串必须正好为 12 个字符。String must be exactly 12 characters long.
length(min,max) {filename:length(8,16)} somefile.txt 字符串必须至少为 8 个字符,且最多包含 16 个字符。String must be at least 8 and has maximum of 16 characters.
min(value) {age:min(18)} 19 整数值必须至少为 18 个字符。Integer value must be at least 18.
max(value) {age:max(120)} 91 整数值最多包含 120 个字符。Integer value maximum of 120.
range(min,max) {age:range(18,120)} 91 整数值必须至少为 18 个字符,且最多包含 120 个字符。Integer value must be at least 18 and maximum of 120.
alpha {name:alpha} Rick 字符串必须由一个或多个字母字符组成,a-zString must consist of one or more alphabetical characters a-z. 不区分大小写。Case-insensitive.
regex(expression) {ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} 123-45-6789 字符串必须与正则表达式匹配。String must match the regular expression. 请参阅有关定义正则表达式的提示。See tips about defining a regular expression.
required {name:required} Rick 用于强制在 URL 生成过程中存在非参数值。Used to enforce that a non-parameter value is present during URL generation.

可向单个参数应用多个由冒号分隔的约束。Multiple, colon-delimited constraints can be applied to a single parameter. 例如,以下约束将参数限制为大于或等于 1 的整数值:For example, the following constraint restricts a parameter to an integer value of 1 or greater:

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

警告

验证 URL 的路由约束并将转换为始终使用固定区域性的 CLR 类型(例如 intDateTime)。Route constraints that verify the URL and are converted to a CLR type (such as int or DateTime) always use the invariant culture. 这些约束假定 URL 不可本地化。These constraints assume that the URL is non-localizable. 框架提供的路由约束不会修改存储于路由值中的值。The framework-provided route constraints don't modify the values stored in route values. 从 URL 中分析的所有路由值都将存储为字符串。All route values parsed from the URL are stored as strings. 例如,float 约束会尝试将路由值转换为浮点数,但转换后的值仅用来验证其是否可转换为浮点数。For example, the float constraint attempts to convert the route value to a float, but the converted value is used only to verify it can be converted to a float.

正则表达式Regular expressions

ASP.NET Core 框架将向正则表达式构造函数添加 RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariantThe ASP.NET Core framework adds RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant to the regular expression constructor. 有关这些成员的说明,请参阅 RegexOptionsSee RegexOptions for a description of these members.

正则表达式与路由和 C# 语言使用的分隔符和令牌相似。Regular expressions use delimiters and tokens similar to those used by routing and the C# language. 必须对正则表达式令牌进行转义。Regular expression tokens must be escaped. 在路由中使用正则表达式 ^\d{3}-\d{2}-\d{4}$To use the regular expression ^\d{3}-\d{2}-\d{4}$ in routing:

  • 表达式必须将字符串中提供的单反斜杠 \ 字符作为源代码中的双反斜杠 \\ 字符。The expression must have the single backslash \ characters provided in the string as double backslash \\ characters in the source code.
  • 正则表达式必须使用 \\\ 字符串转义字符进行转义。The regular expression must us \\ in order to escape the \ string escape character.
  • 使用逐字字符串文本时,正则表达式不需要 \\The regular expression doesn't require \\ when using verbatim string literals.

要对路由参数分隔符 {}[] 进行转义,请将表达式 {{}[[]] 中的字符数加倍。To escape routing parameter delimiter characters {, }, [, ], double the characters in the expression {{, }, [[, ]]. 下表展示了正则表达式和转义版本:The following table shows a regular expression and the escaped version:

正则表达式Regular Expression 转义后的正则表达式Escaped Regular Expression
^\d{3}-\d{2}-\d{4}$ ^\\d{{3}}-\\d{{2}}-\\d{{4}}$
^[a-z]{2}$ ^[[a-z]]{{2}}$

路由中使用的正则表达式通常以脱字号 ^ 开头,并匹配字符串的起始位置。Regular expressions used in routing often start with the caret ^ character and match starting position of the string. 表达式通常以美元符号 $ 字符结尾,并匹配字符串的结尾。The expressions often end with the dollar sign $ character and match end of the string. ^$ 字符可确保正则表达式匹配整个路由参数值。The ^ and $ characters ensure that the regular expression match the entire route parameter value. 如果没有 ^$ 字符,正则表达式将匹配字符串内的所有子字符串,而这通常是不需要的。Without the ^ and $ characters, the regular expression match any substring within the string, which is often undesirable. 下表提供了示例并说明了它们匹配或匹配失败的原因。The following table provides examples and explains why they match or fail to match.

表达式Expression StringString 匹配Match 注释Comment
[a-z]{2} hellohello Yes 子字符串匹配Substring matches
[a-z]{2} 123abc456123abc456 Yes 子字符串匹配Substring matches
[a-z]{2} mzmz Yes 匹配表达式Matches expression
[a-z]{2} MZMZ Yes 不区分大小写Not case sensitive
^[a-z]{2}$ hellohello No 参阅上述 ^$See ^ and $ above
^[a-z]{2}$ 123abc456123abc456 No 参阅上述 ^$See ^ and $ above

有关正则表达式语法的详细信息,请参阅 .NET Framework 正则表达式For more information on regular expression syntax, see .NET Framework Regular Expressions.

若要将参数限制为一组已知的可能值,可使用正则表达式。To constrain a parameter to a known set of possible values, use a regular expression. 例如,{action:regex(^(list|get|create)$)} 仅将 action 路由值匹配到 listgetcreateFor example, {action:regex(^(list|get|create)$)} only matches the action route value to list, get, or create. 如果传递到约束字典中,字符串 ^(list|get|create)$ 将等效。If passed into the constraints dictionary, the string ^(list|get|create)$ is equivalent. 已传递到约束字典(不与模板内联)且不匹配任何已知约束的约束还将被视为正则表达式。Constraints that are passed in the constraints dictionary (not inline within a template) that don't match one of the known constraints are also treated as regular expressions.

自定义路由约束Custom route constraints

除了内置路由约束以外,还可以通过实现 IRouteConstraint 接口来创建自定义路由约束。In addition to the built-in route constraints, custom route constraints can be created by implementing the IRouteConstraint interface. IRouteConstraint 接口包含一个方法 Match,当满足约束时,它返回 true,否则返回 falseThe IRouteConstraint interface contains a single method, Match, which returns true if the constraint is satisfied and false otherwise.

若要使用自定义 IRouteConstraint,必须在应用的服务容器中使用应用的 ConstraintMap 注册路由约束类型。To use a custom IRouteConstraint, the route constraint type must be registered with the app's ConstraintMap in the app's service container. ConstraintMap 是将路由约束键映射到验证这些约束的 IRouteConstraint 实现的目录。A ConstraintMap is a dictionary that maps route constraint keys to IRouteConstraint implementations that validate those constraints. 应用的 ConstraintMap 可作为 services.AddRouting 调用的一部分在 Startup.ConfigureServices 中进行更新,也可以通过使用 services.Configure<RouteOptions> 直接配置 RouteOptions 进行更新。An app's ConstraintMap can be updated in Startup.ConfigureServices either as part of a services.AddRouting call or by configuring RouteOptions directly with services.Configure<RouteOptions>. 例如:For example:

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

然后,可以使用在注册约束类型时指定的名称,以常规方式将约束应用于路由。The constraint can then be applied to routes in the usual manner, using the name specified when registering the constraint type. 例如:For example:

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

参数转换器参考Parameter transformer reference

参数转换器:Parameter transformers:

  • 在为 Route 生成链接时执行。Execute when generating a link for a Route.
  • 实现 Microsoft.AspNetCore.Routing.IOutboundParameterTransformerImplement Microsoft.AspNetCore.Routing.IOutboundParameterTransformer.
  • 使用 ConstraintMap 进行配置。Are configured using ConstraintMap.
  • 获取参数的路由值并将其转换为新的字符串值。Take the parameter's route value and transform it to a new string value.
  • 在生成的链接中使用转换后的值的结果。Result in using the transformed value in the generated link.

例如,路由模式 blog\{article:slugify}(具有 Url.Action(new { article = "MyTestArticle" }))中的自定义 slugify 参数转换器生成 blog\my-test-articleFor example, a custom slugify parameter transformer in route pattern blog\{article:slugify} with Url.Action(new { article = "MyTestArticle" }) generates blog\my-test-article.

若要在路由模式中使用参数转换器,请先在 Startup.ConfigureServices 中使用 ConstraintMap 对其进行配置:To use a parameter transformer in a route pattern, configure it first using ConstraintMap in 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。Parameter transformers are used by the framework to transform the URI where an endpoint resolves. 例如,ASP.NET Core MVC 使用参数转换器来转换用于匹配 area``controller``actionpage 的路由值。For example, ASP.NET Core MVC uses parameter transformers to transform the route value used to match an area, controller, action, and page.

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

使用上述路由,操作 SubscriptionManagementController.GetAll 与 URI /subscription-management/get-all 匹配。With the preceding route, the action SubscriptionManagementController.GetAll is matched with the URI /subscription-management/get-all. 参数转换器不会更改用于生成链接的路由值。A parameter transformer doesn't change the route values used to generate a link. 例如,Url.Action("GetAll", "SubscriptionManagement") 输出 /subscription-management/get-allFor example, Url.Action("GetAll", "SubscriptionManagement") outputs /subscription-management/get-all.

对于结合使用参数转换器和所生成的路由,ASP.NET Core 提供了 API 约定:ASP.NET Core provides API conventions for using a parameter transformers with generated routes:

  • ASP.NET Core MVC 还具有 Microsoft.AspNetCore.Mvc.ApplicationModels.RouteTokenTransformerConvention API 约定。ASP.NET Core MVC has the Microsoft.AspNetCore.Mvc.ApplicationModels.RouteTokenTransformerConvention API convention. 该约定将指定的参数转换器应用于应用中的所有属性路由。This convention applies a specified parameter transformer to all attribute routes in the app. 在替换属性路径令牌时,参数转换器将转换这些令牌。The parameter transformer transforms attribute route tokens as they are replaced. 有关详细信息,请参阅使用参数转换器自定义标记替换For more information, see Use a parameter transformer to customize token replacement.
  • Razor Pages 具有 Microsoft.AspNetCore.Mvc.ApplicationModels.PageRouteTransformerConvention API 约定。Razor Pages has the Microsoft.AspNetCore.Mvc.ApplicationModels.PageRouteTransformerConvention API convention. 此约定将指定的参数转换器应用于所有自动发现的 Razor Pages。This convention applies a specified parameter transformer to all automatically discovered Razor Pages. 参数转换器转换 Razor Pages 路由的文件夹和文件名段。The parameter transformer transforms the folder and file name segments of Razor Pages routes. 有关详细信息,请参阅使用参数转换器自定义页面路由For more information, see Use a parameter transformer to customize page routes.

URL 生成参考URL generation reference

以下示例演示如何在给定路由值字典和 RouteCollection 的情况下生成路由链接。The following example shows how to generate a link to a route given a dictionary of route values and a 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/123The VirtualPath generated at the end of the preceding sample is /package/create/123. 字典提供“跟踪包路由”模板 package/{operation}/{id}operationid 路由值。The dictionary supplies the operation and id route values of the "Track Package Route" template, package/{operation}/{id}. 有关详细信息,请参阅使用路由中间件部分或示例应用中的示例代码。For details, see the sample code in the Use Routing Middleware section or the sample app.

VirtualPathContext 构造函数的第二个参数是环境值的集合。The second parameter to the VirtualPathContext constructor is a collection of ambient values. 由于环境值限制了开发人员在请求上下文中必须指定的值数,因此环境值使用起来很方便。Ambient values are convenient to use because they limit the number of values a developer must specify within a request context. 当前请求的当前路由值被视为链接生成的环境值。The current route values of the current request are considered ambient values for link generation. 在 ASP.NET Core MVC 应用 HomeControllerAbout 操作中,无需指定控制器路由值即可链接到使用 Home 环境值的 Index 操作—。In an ASP.NET Core MVC app's About action of the HomeController, you don't need to specify the controller route value to link to the Index action—the ambient value of Home is used.

忽略与参数不匹配的环境值。Ambient values that don't match a parameter are ignored. 在显式提供的值会覆盖环境值的情况下,也会忽略环境值。Ambient values are also ignored when an explicitly provided value overrides the ambient value. 在 URL 中将从左到右进行匹配。Matching occurs from left to right in the URL.

显式提供但与路由片段不匹配的值将添加到查询字符串中。Values explicitly provided but that don't match a segment of the route are added to the query string. 下表显示使用路由模板 {controller}/{action}/{id?} 时的结果。The following table shows the result when using the route template {controller}/{action}/{id?}.

环境值Ambient Values 显式值Explicit Values 结果Result
控制器 =“Home”controller = "Home" 操作 =“About”action = "About" /Home/About
控制器 =“Home”controller = "Home" 控制器 =“Order”,操作 =“About”controller = "Order", action = "About" /Order/About
控制器 = “Home”,颜色 = “Red”controller = "Home", color = "Red" 操作 =“About”action = "About" /Home/About
控制器 =“Home”controller = "Home" 操作 =“About”,颜色 =“Red”action = "About", color = "Red" /Home/About?color=Red

如果路由具有不对应于参数的默认值,且该值以显式方式提供,则它必须与默认值相匹配:If a route has a default value that doesn't correspond to a parameter and that value is explicitly provided, it must match the default value:

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

当提供 controlleraction 的匹配值时,链接生成仅为此路由生成链接。Link generation only generates a link for this route when the matching values for controller and action are provided.

复杂段Complex segments

复杂段(例如,[Route("/x{token}y")])通过非贪婪的方式从右到左匹配文字进行处理。Complex segments (for example [Route("/x{token}y")]) are processed by matching up literals from right to left in a non-greedy way. 请参阅此代码以了解有关如何匹配复杂段的详细说明。See this code for a detailed explanation of how complex segments are matched. ASP.NET Core 无法使用代码示例,但它提供了对复杂段的合理说明。The code sample is not used by ASP.NET Core, but it provides a good explanation of complex segments.

路由负责将请求 URI 映射到路由处理程序和调度传入的请求。Routing is responsible for mapping request URIs to route handlers and dispatching an incoming requests. 路由在应用中定义,并在应用启动时进行配置。Routes are defined in the app and configured when the app starts. 路由可以选择从请求包含的 URL 中提取值,然后这些值便可用于处理请求。A route can optionally extract values from the URL contained in the request, and these values can then be used for request processing. 如果使用应用中配置的路由,路由能生成映射到路由处理程序的 URL。Using configured routes from the app, routing is able to generate URLs that map to route handlers.

要在 ASP.NET Core 2.1 中使用最新路由方案,请在 Startup.ConfigureServices 中为 MVC 服务注册指定兼容性版本To use the latest routing scenarios in ASP.NET Core 2.1, specify the compatibility version to the MVC services registration in Startup.ConfigureServices:

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

重要

本文档介绍较低级别的 ASP.NET Core 路由。This document covers low-level ASP.NET Core routing. 有关 ASP.NET Core MVC 路由的信息,请参阅 在 ASP.NET Core 中路由到控制器操作For information on ASP.NET Core MVC routing, see 在 ASP.NET Core 中路由到控制器操作. 有关 Razor Pages 中路由约定的信息,请参阅 ASP.NET Core 中的 Razor Pages 路由和应用约定For information on routing conventions in Razor Pages, see ASP.NET Core 中的 Razor Pages 路由和应用约定.

查看或下载示例代码如何下载View or download sample code (how to download)

路由基础知识Routing basics

大多数应用应选择基本的描述性路由方案,让 URL 有可读性和意义。Most apps should choose a basic and descriptive routing scheme so that URLs are readable and meaningful. 默认传统路由 {controller=Home}/{action=Index}/{id?}The default conventional route {controller=Home}/{action=Index}/{id?}:

  • 支持基本的描述性路由方案。Supports a basic and descriptive routing scheme.
  • 是基于 UI 的应用的有用起点。Is a useful starting point for UI-based apps.

开发人员通常在专业情况下(例如,博客和电子商务终结点)使用属性路由或专用传统路由向应用的高流量区域添加其他简洁路由。Developers commonly add additional terse routes to high-traffic areas of an app in specialized situations (for example, blog and ecommerce endpoints) using attribute routing or dedicated conventional routes.

Web API 应使用属性路由,将应用功能建模为一组资源,其中操作是由 HTTP 谓词表示。Web APIs should use attribute routing to model the app's functionality as a set of resources where operations are represented by HTTP verbs. 也就是说,对同一逻辑资源执行的许多操作(例如,GET、POST)都将使用相同 URL。This means that many operations (for example, GET, POST) on the same logical resource will use the same URL. 属性路由提供了精心设计 API 的公共终结点布局所需的控制级别。Attribute routing provides a level of control that's needed to carefully design an API's public endpoint layout.

Razor Pages 应用使用默认的传统路由从应用的“页面”文件夹中提供命名资源。Razor Pages apps use default conventional routing to serve named resources in the Pages folder of an app. 还可以使用其他约定来自定义 Razor Pages 路由行为。Additional conventions are available that allow you to customize Razor Pages routing behavior. 有关详细信息,请参阅 ASP.NET Core 中的 Razor Pages 介绍ASP.NET Core 中的 Razor Pages 路由和应用约定For more information, see ASP.NET Core 中的 Razor Pages 介绍 and ASP.NET Core 中的 Razor Pages 路由和应用约定.

借助 URL 生成支持,无需通过硬编码 URL 将应用关联到一起,即可开发应用。URL generation support allows the app to be developed without hard-coding URLs to link the app together. 此支持允许从基本路由配置入手,并在确定应用的资源布局后修改路由。This support allows for starting with a basic routing configuration and modifying the routes after the app's resource layout is determined.

路由使用 IRouter 的路由实现来执行以下操作:Routing uses routes implementations of IRouter to:

  • 将传入请求映射到路由处理程序。Map incoming requests to route handlers.
  • 生成响应中使用的 URL。Generate the URLs used in responses.

默认情况下,应用只有一个路由集合。By default, an app has a single collection of routes. 当请求到达时,将按照路由在集合中的存在顺序来处理路由。When a request arrives, the routes in the collection are processed in the order that they exist in the collection. 框架试图通过在集合中的每个路由上调用 RouteAsync 方法,将传入请求的 URL 与集合中的路由进行匹配。The framework attempts to match an incoming request URL to a route in the collection by calling the RouteAsync method on each route in the collection. 响应可根据路由信息使用路由生成 URL(例如,用于重定向或链接),从而避免硬编码 URL,这有助于可维护性。A response can use routing to generate URLs (for example, for redirection or links) based on route information and thus avoid hard-coded URLs, which helps maintainability.

路由系统具有以下特征:The routing system has the following characteristics:

  • 路由模板语法用于通过标记化路由参数来定义路由。Route template syntax is used to define routes with tokenized route parameters.
  • 可以使用常规样式和属性样式终结点配置。Conventional-style and attribute-style endpoint configuration is permitted.
  • IRouteConstraint 用于确定 URL 参数是否包含给定的终结点约束的有效值。IRouteConstraint is used to determine whether a URL parameter contains a valid value for a given endpoint constraint.
  • 应用模型(如 MVC/Razor Pages)注册了其所有具有可预测的路由方案实现的路由。App models, such as MVC/Razor Pages, register all of their routes, which have a predictable implementation of routing scenarios.
  • 响应可根据路由信息使用路由生成 URL(例如,用于重定向或链接),从而避免硬编码 URL,这有助于可维护性。A response can use routing to generate URLs (for example, for redirection or links) based on route information and thus avoid hard-coded URLs, which helps maintainability.
  • URL 生成基于支持任意可扩展性的路由。URL generation is based on routes, which support arbitrary extensibility. IUrlHelper 提供生成 URL 的方法。IUrlHelper offers methods to build URLs.

路由通过 RouterMiddleware 类连接到中间件管道。Routing is connected to the middleware pipeline by the RouterMiddleware class. ASP.NET Core MVC 向中间件管道添加路由,作为其配置的一部分,并处理 MVC 和 Razor Pages 应用中的路由。ASP.NET Core MVC adds routing to the middleware pipeline as part of its configuration and handles routing in MVC and Razor Pages apps. 要了解如何将路由用作独立组件,请参阅使用路由中间件部分。To learn how to use routing as a standalone component, see the Use Routing Middleware section.

URL 匹配URL matching

URL 匹配是一个过程,通过该过程,路由可向处理程序调度传入请求。URL matching is the process by which routing dispatches an incoming request to a handler. 此过程基于 URL 路径中的数据,但可以进行扩展以考虑请求中的任何数据。This process is based on data in the URL path but can be extended to consider any data in the request. 向单独的处理程序调度请求的功能是缩放应用的大小和复杂性的关键。The ability to dispatch requests to separate handlers is key to scaling the size and complexity of an app.

传入请求将进入 RouterMiddleware,后者将对序列中的每个路由调用 RouteAsync 方法。Incoming requests enter the RouterMiddleware, which calls the RouteAsync method on each route in sequence. IRouter 实例将选择是否通过将 RouteContext.Handler 设置为非 NULL RequestDelegate 来处理请求。The IRouter instance chooses whether to handle the request by setting the RouteContext.Handler to a non-null RequestDelegate. 如果路由为请求设置处理程序,将停止路由处理,并调用处理程序来处理该请求。If a route sets a handler for the request, route processing stops, and the handler is invoked to process the request. 如果未找到用于处理请求的路由处理程序,中间件会将请求传递给请求管道中的下一个中间件。If no route handler is found to process the request, the middleware hands the request off to the next middleware in the request pipeline.

RouteAsync 的主要输入是与当前请求关联的 RouteContext.HttpContextThe primary input to RouteAsync is the RouteContext.HttpContext associated with the current request. RouteContext.HandlerRouteContext.RouteData 是路由匹配后设置的输出。The RouteContext.Handler and RouteContext.RouteData are outputs set after a route is matched.

调用 RouteAsync 的匹配还可根据迄今执行的请求处理将 RouteContext.RouteData 的属性设为适当的值。A match that calls RouteAsync also sets the properties of the RouteContext.RouteData to appropriate values based on the request processing performed thus far.

RouteData.Values 是从路由中生成的路由值的字典。RouteData.Values is a dictionary of route values produced from the route. 这些值通常通过标记 URL 来确定,可用来接受用户输入,或者在应用内作出进一步的调度决策。These values are usually determined by tokenizing the URL and can be used to accept user input or to make further dispatching decisions inside the app.

RouteData.DataTokens 是一个与匹配的路由相关的其他数据的属性包。RouteData.DataTokens is a property bag of additional data related to the matched route. 提供 DataTokens 以支持将状态数据与每个路由相关联,以便应用可根据所匹配的路由作出决策。DataTokens are provided to support associating state data with each route so that the app can make decisions based on which route matched. 这些值是开发者定义的,不会影响通过任何方式路由的行为。These values are developer-defined and do not affect the behavior of routing in any way. 此外,存储于 RouteData.DataTokens 中的值可以属于任何类型,与 RouteData.Values 相反,后者必须能够转换为字符串,或从字符串进行转换。Additionally, values stashed in RouteData.DataTokens can be of any type, in contrast to RouteData.Values, which must be convertible to and from strings.

RouteData.Routers 是参与成功匹配请求的路由的列表。RouteData.Routers is a list of the routes that took part in successfully matching the request. 路由可以相互嵌套。Routes can be nested inside of one another. Routers 属性可以通过导致匹配的逻辑路由树反映该路径。The Routers property reflects the path through the logical tree of routes that resulted in a match. 通常情况下,Routers 中的第一项是路由集合,应该用于生成 URL。Generally, the first item in Routers is the route collection and should be used for URL generation. Routers 中的最后一项是匹配的路由处理程序。The last item in Routers is the route handler that matched.

URL 生成URL generation

URL 生成是通过其可根据一组路由值创建 URL 路径的过程。URL generation is the process by which routing can create a URL path based on a set of route values. 这需要考虑处理程序与访问它们的 URL 之间的逻辑分隔。This allows for a logical separation between route handlers and the URLs that access them.

URL 遵循类似的迭代过程,但开头是将用户或框架代码调用到路由集合的 GetVirtualPath 方法中。URL generation follows a similar iterative process, but it starts with user or framework code calling into the GetVirtualPath method of the route collection. 每个路由按顺序调用其 GetVirtualPath 方法,直到返回非 NULL 的 VirtualPathDataEach route has its GetVirtualPath method called in sequence until a non-null VirtualPathData is returned.

GetVirtualPath 的主输入有:The primary inputs to GetVirtualPath are:

路由主要使用 ValuesAmbientValues 提供的路由值来确定是否可能生成 URL 以及要包括哪些值。Routes primarily use the route values provided by Values and AmbientValues to decide whether it's possible to generate a URL and what values to include. AmbientValues 是通过匹配当前请求而生成的路由值集。The AmbientValues are the set of route values that were produced from matching the current request. 与此相反,Values 是指定如何为当前操作生成所需 URL 的路由值。In contrast, Values are the route values that specify how to generate the desired URL for the current operation. 如果路由应获取与当前上下文关联的服务或其他数据时,则提供 HttpContextThe HttpContext is provided in case a route should obtain services or additional data associated with the current context.

提示

VirtualPathContext.Values 视为 VirtualPathContext.AmbientValues 的一组替代。Think of VirtualPathContext.Values as a set of overrides for the VirtualPathContext.AmbientValues. URL 生成尝试重复使用当前请求中的路由值,以便使用相同路由或路由值生成链接的 URL。URL generation attempts to reuse route values from the current request to generate URLs for links using the same route or route values.

GetVirtualPath 的输出是 VirtualPathDataThe output of GetVirtualPath is a VirtualPathData. VirtualPathDataRouteData 的并行值。VirtualPathData is a parallel of RouteData. VirtualPathData 包含输出 URL 的 VirtualPath 以及路由应该设置的某些其他属性。VirtualPathData contains the VirtualPath for the output URL and some additional properties that should be set by the route.

VirtualPathData.VirtualPath 属性包含路由生成的虚拟路径。The VirtualPathData.VirtualPath property contains the virtual path produced by the route. 你可能需要进一步处理路径,具体取决于你的需求。Depending on your needs, you may need to process the path further. 如果要在 HTML 中呈现生成的 URL,请预置应用的基路径。If you want to render the generated URL in HTML, prepend the base path of the app.

VirtualPathData.Router 是对已成功生成 URL 的路由的引用。The VirtualPathData.Router is a reference to the route that successfully generated the URL.

VirtualPathData.DataTokens 属性是生成 URL 的路由的其他相关数据的字典。The VirtualPathData.DataTokens properties is a dictionary of additional data related to the route that generated the URL. 这是 RouteData.DataTokens 的并性值。This is the parallel of RouteData.DataTokens.

创建路由Create routes

路由提供 Route 类,作为 IRouter 的标准实现。Routing provides the Route class as the standard implementation of IRouter. Route 使用 route template 语法来定义模式,以便在调用 RouteAsync 时匹配 URL 路径。Route uses the route template syntax to define patterns to match against the URL path when RouteAsync is called. 调用 GetVirtualPath 时,Route 使用同一路由模板生成 URL。Route uses the same route template to generate a URL when GetVirtualPath is called.

大多数应用通过调用 MapRouteIRouteBuilder 上定义的一种类似的扩展方法来创建路由。Most apps create routes by calling MapRoute or one of the similar extension methods defined on IRouteBuilder. 任何 IRouteBuilder 扩展方法都会创建 Route 的实例并将其添加到路由集合中。Any of the IRouteBuilder extension methods create an instance of Route and add it to the route collection.

MapRoute 不接受路由处理程序参数。MapRoute doesn't accept a route handler parameter. MapRoute 仅添加由 DefaultHandler 处理的路由。MapRoute only adds routes that are handled by the DefaultHandler. 默认处理程序是 IRouter,该处理程序可能无法处理该请求。The default handler is an IRouter, and the handler might not handle the request. 例如,ASP.NET Core MVC 通常被配置为默认处理程序,仅处理与可用控制器和操作匹配的请求。For example, ASP.NET Core MVC is typically configured as a default handler that only handles requests that match an available controller and action. 要了解 MVC 中的路由的详细信息,请参阅 在 ASP.NET Core 中路由到控制器操作To learn more about routing in MVC, see 在 ASP.NET Core 中路由到控制器操作.

以下代码示例是典型 ASP.NET Core MVC 路由定义使用的一个 MapRoute 调用示例:The following code example is an example of a MapRoute call used by a typical ASP.NET Core MVC route definition:

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

此模板与 URL 路径相匹配,并且提取路由值。This template matches a URL path and extracts the route values. 例如,路径 /Products/Details/17 生成以下路由值:{ controller = Products, action = Details, id = 17 }For example, the path /Products/Details/17 generates the following route values: { controller = Products, action = Details, id = 17 }.

路由值是通过将 URL 路径拆分成段,并且将每段与路由模板中的路由参数名称相匹配来确定的。Route values are determined by splitting the URL path into segments and matching each segment with the route parameter name in the route template. 路由参数已命名。Route parameters are named. 参数通过将参数名称括在大括号 { ... } 中来定义。The parameters defined by enclosing the parameter name in braces { ... }.

上述模板还可匹配 URL 路径 /,并且生成值 { controller = Home, action = Index }The preceding template could also match the URL path / and produce the values { controller = Home, action = Index }. 这是因为 {controller}{action} 路由参数具有默认值,且 id 路由参数是可选的。This occurs because the {controller} and {action} route parameters have default values and the id route parameter is optional. 路由参数名称为参数定义默认值后,等号 (=) 后将有一个值。An equals sign (=) followed by a value after the route parameter name defines a default value for the parameter. 路由参数名称后面的问号 (?) 定义了可选参数。A question mark (?) after the route parameter name defines an optional parameter.

路由匹配时,具有默认值的路由参数始终会生成路由值。Route parameters with a default value always produce a route value when the route matches. 如果没有相应的 URL 路径段,则可选参数不会生成路由值。Optional parameters don't produce a route value if there is no corresponding URL path segment. 有关路由模板方案和语法的详细说明,请参阅路由模板参考部分。See the Route template reference section for a thorough description of route template scenarios and syntax.

在以下示例中,路由参数定义 {id:int}id 路由参数定义路由约束In the following example, the route parameter definition {id:int} defines a route constraint for the id route parameter:

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

此模板与类似于 /Products/Details/17 而不是 /Products/Details/Apples 的 URL 路径相匹配。This template matches a URL path like /Products/Details/17 but not /Products/Details/Apples. 路由约束实现 IRouteConstraint 并检查路由值,以验证它们。Route constraints implement IRouteConstraint and inspect route values to verify them. 在此示例中,路由值 id 必须可转换为整数。In this example, the route value id must be convertible to an integer. 有关框架提供的路由约束的说明,请参阅路由约束参考See route-constraint-reference for an explanation of route constraints provided by the framework.

其他 MapRoute 重载接受 constraintsdataTokensdefaults 的值。Additional overloads of MapRoute accept values for constraints, dataTokens, and defaults. 这些参数的典型用法是传递匿名类型化对象,其中匿名类型的属性名匹配路由参数名称。The typical usage of these parameters is to pass an anonymously typed object, where the property names of the anonymous type match route parameter names.

以下 MapRoute 示例可创建等效路由:The following MapRoute examples create equivalent routes:

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

提示

定义约束和默认值的内联语法对于简单路由很方便。The inline syntax for defining constraints and defaults can be convenient for simple routes. 但是,存在内联语法不支持的方案,例如数据令牌。However, there are scenarios, such as data tokens, that aren't supported by inline syntax.

以下示例演示了一些其他方案:The following example demonstrates a few additional scenarios:

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

上述模板与 /Blog/All-About-Routing/Introduction 等的 URL 路径相匹配,并提取值 { controller = Blog, action = ReadArticle, article = All-About-Routing/Introduction }The preceding template matches a URL path like /Blog/All-About-Routing/Introduction and extracts the values { controller = Blog, action = ReadArticle, article = All-About-Routing/Introduction }. controlleraction 的默认路由值由路由生成,即便模板中没有对应的路由参数。The default route values for controller and action are produced by the route even though there are no corresponding route parameters in the template. 可在路由模板中指定默认值。Default values can be specified in the route template. 根据路由参数名称前的星号 (*) 外观,article 路由参数被定义为 catch-all。The article route parameter is defined as a catch-all by the appearance of an asterisk (*) before the route parameter name. 全方位路由参数可捕获 URL 路径的其余部分,也能匹配空白字符串。Catch-all route parameters capture the remainder of the URL path and can also match the empty string.

以下示例添加了路由约束和数据令牌:The following example adds route constraints and data tokens:

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

上述模板与 /en-US/Products/5 等 URL 路径相匹配,并且提取值 { controller = Products, action = Details, id = 5 } 和数据令牌 { locale = en-US }The preceding template matches a URL path like /en-US/Products/5 and extracts the values { controller = Products, action = Details, id = 5 } and the data tokens { locale = en-US }.

本地 Windows 令牌

路由类 URL 生成Route class URL generation

Route 类还可通过将一组路由值与其路由模板组合来生成 URL。The Route class can also perform URL generation by combining a set of route values with its route template. 从逻辑上来说,这是匹配 URL 路径的反向过程。This is logically the reverse process of matching the URL path.

提示

为更好了解 URL 生成,请想象你要生成的 URL,然后考虑路由模板将如何匹配该 URL。To better understand URL generation, imagine what URL you want to generate and then think about how a route template would match that URL. 将生成什么值?What values would be produced? 这大致相当于 URL 在 Route 类中的生成方式。This is the rough equivalent of how URL generation works in the Route class.

以下示例使用常规 ASP.NET Core MVC 默认路由:The following example uses a general ASP.NET Core MVC default route:

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

使用路由值 { controller = Products, action = List },将生成 URL /Products/ListWith the route values { controller = Products, action = List }, the URL /Products/List is generated. 路由值将替换为相应的路由参数,以形成 URL 路径。The route values are substituted for the corresponding route parameters to form the URL path. 由于 id 是可选路由参数,因此成功生成的 URL 不具有 id 的值。Since id is an optional route parameter, the URL is successfully generated without a value for id.

使用路由值 { controller = Home, action = Index },将生成 URL /With the route values { controller = Home, action = Index }, the URL / is generated. 提供的路由值与默认值匹配,并且会安全地省略与默认值对应的段。The provided route values match the default values, and the segments corresponding to the default values are safely omitted.

已生成的两个 URL 将往返以下路由定义 (/Home/Index/),并生成用于生成该 URL 的相同路由值。Both URLs generated round-trip with the following route definition (/Home/Index and /) produce the same route values that were used to generate the URL.

备注

使用 ASP.NET Core MVC 应用应该使用 UrlHelper 生成 URL,而不是直接调用到路由。An app using ASP.NET Core MVC should use UrlHelper to generate URLs instead of calling into routing directly.

有关 URL 生成的详细信息,请参阅 Url 生成参考部分。For more information on URL generation, see the Url generation reference section.

使用路由中间件Use routing middleware

引用应用项目文件中的 Microsoft.AspNetCore.App 元包Reference the Microsoft.AspNetCore.App metapackage in the app's project file.

将路由添加到 Startup.ConfigureServices 中的服务容器:Add routing to the service container in Startup.ConfigureServices:

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

必须在 Startup.Configure 方法中配置路由。Routes must be configured in the Startup.Configure method. 该示例应用使用以下 API:The sample app uses the following APIs:

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 的响应。The following table shows the responses with the given URIs.

URIURI 响应Response
/package/create/3 Hello!Hello! Route values: [operation, create], [id, 3]Route values: [operation, create], [id, 3]
/package/track/-3 Hello!Hello! Route values: [operation, track], [id, -3]Route values: [operation, track], [id, -3]
/package/track/-3/ Hello!Hello! Route values: [operation, track], [id, -3]Route values: [operation, track], [id, -3]
/package/track/ 请求失败,不匹配。The request falls through, no match.
GET /hello/Joe Hi, Joe!Hi, Joe!
POST /hello/Joe 请求失败,仅匹配 HTTP GET。The request falls through, matches HTTP GET only.
GET /hello/Joe/Smith 请求失败,不匹配。The request falls through, no match.

如果要配置单个路由,请在 IRouter 实例中调用 UseRouter 传入。If you're configuring a single route, call UseRouter passing in an IRouter instance. 无需使用 RouteBuilderYou won't need to use RouteBuilder.

框架可提供一组用于创建路由 (RequestDelegateRouteBuilderExtensions) 的扩展方法:The framework provides a set of extension methods for creating routes (RequestDelegateRouteBuilderExtensions):

一些列出的方法(如 MapGet)需要 RequestDelegateSome of listed methods, such as MapGet, require a RequestDelegate. 路由匹配时,RequestDelegate 会用作路由处理程序。The RequestDelegate is used as the route handler when the route matches. 此系列中的其他方法允许配置中间件管道,将其用作路由处理程序。Other methods in this family allow configuring a middleware pipeline for use as the route handler. 如果 Map* 方法不接受处理程序(例如 MapRoute),则它将使用 DefaultHandlerIf the Map* method doesn't accept a handler, such as MapRoute, it uses the DefaultHandler.

Map[Verb] 方法将使用约束来将路由限制为方法名称中的 HTTP 谓词。The Map[Verb] methods use constraints to limit the route to the HTTP Verb in the method name. 有关示例,请参阅 MapGetMapVerbFor example, see MapGet and MapVerb.

路由模板参考Route template reference

如果路由找到匹配项,大括号 ({ ... }) 内的令牌定义绑定的路由参数。Tokens within curly braces ({ ... }) define route parameters that are bound if the route is matched. 可在路由段中定义多个路由参数,但必须由文本值隔开。You can define more than one route parameter in a route segment, but they must be separated by a literal value. 例如,{controller=Home}{action=Index} 不是有效的路由,因为 {controller}{action} 之间没有文本值。For example, {controller=Home}{action=Index} isn't a valid route, since there's no literal value between {controller} and {action}. 这些路由参数必须具有名称,且可能指定了其他属性。These route parameters must have a name and may have additional attributes specified.

路由参数以外的文本(例如 {id})和路径分隔符 / 必须匹配 URL 中的文本。Literal text other than route parameters (for example, {id}) and the path separator / must match the text in the URL. 文本匹配区分大小写,并且基于 URL 路径已解码的表示形式。Text matching is case-insensitive and based on the decoded representation of the URLs path. 要匹配文字路由参数分隔符({}),请通过重复该字符({{}})来转义分隔符。To match a literal route parameter delimiter ({ or }), escape the delimiter by repeating the character ({{ or }}).

尝试捕获具有可选文件扩展名的文件名的 URL 模式还有其他注意事项。URL patterns that attempt to capture a file name with an optional file extension have additional considerations. 例如,考虑模板 files/{filename}.{ext?}For example, consider the template files/{filename}.{ext?}. filenameext 的值都存在时,将填充这两个值。When values for both filename and ext exist, both values are populated. 如果 URL 中仅存在 filename 的值,则路由匹配,因为尾随句点 (.) 是可选的。If only a value for filename exists in the URL, the route matches because the trailing period (.) is optional. 以下 URL 与此路由相匹配:The following URLs match this route:

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

可以使用星号 (*) 作为路由参数的前缀,以绑定到 URI 的其余部分。You can use the asterisk (*) as a prefix to a route parameter to bind to the rest of the URI. 这称为 catch-all 参数。This is called a catch-all parameter. 例如,blog/{*slug} 将匹配以 /blog 开头且其后带有任何值(将分配给 slug 路由值)的 URI。For example, blog/{*slug} matches any URI that starts with /blog and has any value following it, which is assigned to the slug route value. 全方位参数还可以匹配空字符串。Catch-all parameters can also match the empty string.

使用路由生成 URL(包括路径分隔符 (/))时,catch-all 参数会转义相应的字符。The catch-all parameter escapes the appropriate characters when the route is used to generate a URL, including path separator (/) characters. 例如,路由值为 { path = "my/path" } 的路由 foo/{*path} 生成 foo/my%2FpathFor example, the route foo/{*path} with route values { path = "my/path" } generates foo/my%2Fpath. 请注意转义的正斜杠。Note the escaped forward slash.

路由参数可能具有指定的默认值,方法是在参数名称后使用等号 (=) 隔开以指定默认值。Route parameters may have default values designated by specifying the default value after the parameter name separated by an equals sign (=). 例如,{controller=Home}Home 定义为 controller 的默认值。For example, {controller=Home} defines Home as the default value for controller. 如果参数的 URL 中不存在任何值,则使用默认值。The default value is used if no value is present in the URL for the parameter. 通过在参数名称的末尾附加问号 (?) 可使路由参数成为可选项,如 id? 中所示。Route parameters are made optional by appending a question mark (?) to the end of the parameter name, as in id?. 可选值和默认路径参数的区别在于具有默认值的路由参数始终会生成值 —,而可选参数仅当请求 URL 提供值时才会具有值。The difference between optional values and default route parameters is that a route parameter with a default value always produces a value—an optional parameter has a value only when a value is provided by the request URL.

路由参数可能具有必须与从 URL 中绑定的路由值匹配的约束。Route parameters may have constraints that must match the route value bound from the URL. 在路由参数后面添加一个冒号 (:) 和约束名称可指定路由参数上的内联约束。Adding a colon (:) and constraint name after the route parameter name specifies an inline constraint on a route parameter. 如果约束需要参数,将以在约束名称后括在括号 ((...)) 中的形式提供。If the constraint requires arguments, they're enclosed in parentheses ((...)) after the constraint name. 通过追加另一个冒号 (:) 和约束名称,可指定多个内联约束。Multiple inline constraints can be specified by appending another colon (:) and constraint name.

约束名称和参数将传递给 IInlineConstraintResolver 服务,以创建 IRouteConstraint 的实例,用于处理 URL。The constraint name and arguments are passed to the IInlineConstraintResolver service to create an instance of IRouteConstraint to use in URL processing. 例如,路由模板 blog/{article:minlength(10)} 使用参数 10 指定 minlength 约束。For example, the route template blog/{article:minlength(10)} specifies a minlength constraint with the argument 10. 有关路由约束详情以及框架提供的约束列表,请参阅路由约束引用部分。For more information on route constraints and a list of the constraints provided by the framework, see the Route constraint reference section.

下表演示了示例路由模板及其行为。The following table demonstrates example route templates and their behavior.

路由模板Route Template 示例匹配 URIExample Matching URI 请求 URI…The request URI…
hello /hello 仅匹配单个路径 /helloOnly matches the single path /hello.
{Page=Home} / 匹配并将 Page 设置为 HomeMatches and sets Page to Home.
{Page=Home} /Contact 匹配并将 Page 设置为 ContactMatches and sets Page to Contact.
{controller}/{action}/{id?} /Products/List 映射到 Products 控制器和 List 操作。Maps to the Products controller and List action.
{controller}/{action}/{id?} /Products/Details/123 映射到 Products 控制器和 Details 操作(id 设置为 123)。Maps to the Products controller and Details action (id set to 123).
{controller=Home}/{action=Index}/{id?} / 映射到 Home 控制器和 Index 方法(忽略 id)。Maps to the Home controller and Index method (id is ignored).

使用模板通常是进行路由最简单的方法。Using a template is generally the simplest approach to routing. 还可在路由模板外指定约束和默认值。Constraints and defaults can also be specified outside the route template.

提示

启用日志记录以查看内置路由实现(如 Route)如何匹配请求。Enable Logging to see how the built-in routing implementations, such as Route, match requests.

路由约束参考Route constraint reference

路由约束在传入 URL 发生匹配时执行,URL 路径标记为路由值。Route constraints execute when a match has occurred to the incoming URL and the URL path is tokenized into route values. 路径约束通常检查通过路径模板关联的路径值,并对该值是否可接受做出是/否决定。Route constraints generally inspect the route value associated via the route template and make a yes/no decision about whether or not the value is acceptable. 某些路由约束使用路由值以外的数据来考虑是否可以路由请求。Some route constraints use data outside the route value to consider whether the request can be routed. 例如,HttpMethodRouteConstraint 可以根据其 HTTP 谓词接受或拒绝请求。For example, the HttpMethodRouteConstraint can accept or reject a request based on its HTTP verb. 约束用于路由请求和链接生成。Constraints are used in routing requests and link generation.

警告

请勿将约束用于“输入验证”。Don't use constraints for input validation. 如果将约束用于“输入约束”,那么无效输入将导致“404 - 未找到”响应,而不是含相应错误消息的“400 - 错误请求” 。If constraints are used for input validation, invalid input results in a 404 - Not Found response instead of a 400 - Bad Request with an appropriate error message. 路由约束用于消除类似路由的歧义,而不是验证特定路由的输入。Route constraints are used to disambiguate similar routes, not to validate the inputs for a particular route.

下表演示示例路由约束及其预期行为。The following table demonstrates example route constraints and their expected behavior.

约束constraint 示例Example 匹配项示例Example Matches 说明Notes
int {id:int} 123456789, -123456789123456789, -123456789 匹配任何整数Matches any integer
bool {active:bool} true, FALSEtrue, FALSE 匹配 truefalse(区分大小写)Matches true or false (case-insensitive)
datetime {dob:datetime} 2016-12-31, 2016-12-31 7:32pm2016-12-31, 2016-12-31 7:32pm 在固定区域性中匹配有效的 DateTime 值。Matches a valid DateTime value in the invariant culture. 请参阅前面的警告。See preceding warning.
decimal {price:decimal} 49.99, -1,000.0149.99, -1,000.01 在固定区域性中匹配有效的 decimal 值。Matches a valid decimal value in the invariant culture. 请参阅前面的警告。See preceding warning.
double {weight:double} 1.234, -1,001.01e81.234, -1,001.01e8 在固定区域性中匹配有效的 double 值。Matches a valid double value in the invariant culture. 请参阅前面的警告。See preceding warning.
float {weight:float} 1.234, -1,001.01e81.234, -1,001.01e8 在固定区域性中匹配有效的 float 值。Matches a valid float value in the invariant culture. 请参阅前面的警告。See preceding warning.
guid {id:guid} CD2C1638-1638-72D5-1638-DEADBEEF1638, {CD2C1638-1638-72D5-1638-DEADBEEF1638}CD2C1638-1638-72D5-1638-DEADBEEF1638, {CD2C1638-1638-72D5-1638-DEADBEEF1638} 匹配有效的 GuidMatches a valid Guid value
long {ticks:long} 123456789, -123456789123456789, -123456789 匹配有效的 longMatches a valid long value
minlength(value) {username:minlength(4)} Rick 字符串必须至少为 4 个字符String must be at least 4 characters
maxlength(value) {filename:maxlength(8)} Richard 字符串不得超过 8 个字符String must be no more than 8 characters
length(length) {filename:length(12)} somefile.txt 字符串必须正好为 12 个字符String must be exactly 12 characters long
length(min,max) {filename:length(8,16)} somefile.txt 字符串必须至少为 8 个字符,且不得超过 16 个字符String must be at least 8 and no more than 16 characters long
min(value) {age:min(18)} 19 整数值必须至少为 18Integer value must be at least 18
max(value) {age:max(120)} 91 整数值不得超过 120Integer value must be no more than 120
range(min,max) {age:range(18,120)} 91 整数值必须至少为 18,且不得超过 120Integer value must be at least 18 but no more than 120
alpha {name:alpha} Rick 字符串必须由一个或多个字母字符(a-z,区分大小写)组成String must consist of one or more alphabetical characters (a-z, case-insensitive)
regex(expression) {ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} 123-45-6789 字符串必须匹配正则表达式(参见有关定义正则表达式的提示)String must match the regular expression (see tips about defining a regular expression)
required {name:required} Rick 用于强制在 URL 生成过程中存在非参数值Used to enforce that a non-parameter value is present during URL generation

可向单个参数应用多个由冒号分隔的约束。Multiple, colon-delimited constraints can be applied to a single parameter. 例如,以下约束将参数限制为大于或等于 1 的整数值:For example, the following constraint restricts a parameter to an integer value of 1 or greater:

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

警告

验证 URL 的路由约束并将转换为始终使用固定区域性的 CLR 类型(例如 intDateTime)。Route constraints that verify the URL and are converted to a CLR type (such as int or DateTime) always use the invariant culture. 这些约束假定 URL 不可本地化。These constraints assume that the URL is non-localizable. 框架提供的路由约束不会修改存储于路由值中的值。The framework-provided route constraints don't modify the values stored in route values. 从 URL 中分析的所有路由值都将存储为字符串。All route values parsed from the URL are stored as strings. 例如,float 约束会尝试将路由值转换为浮点数,但转换后的值仅用来验证其是否可转换为浮点数。For example, the float constraint attempts to convert the route value to a float, but the converted value is used only to verify it can be converted to a float.

正则表达式Regular expressions

ASP.NET Core 框架将向正则表达式构造函数添加 RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariantThe ASP.NET Core framework adds RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant to the regular expression constructor. 有关这些成员的说明,请参阅 RegexOptionsSee RegexOptions for a description of these members.

正则表达式与路由和 C# 计算机语言使用的分隔符和令牌相似。Regular expressions use delimiters and tokens similar to those used by Routing and the C# language. 必须对正则表达式令牌进行转义。Regular expression tokens must be escaped. 要在路由中使用正则表达式 ^\d{3}-\d{2}-\d{4}$,表达式必须在字符串中提供 \(单反斜杠)字符,正如 C# 源文件中的 \\(双反斜杠)字符一样,以便对 \ 字符串转义字符进行转义(除非使用字符串文本)。To use the regular expression ^\d{3}-\d{2}-\d{4}$ in routing, the expression must have the \ (single backslash) characters provided in the string as \\ (double backslash) characters in the C# source file in order to escape the \ string escape character (unless using verbatim string literals). 要对路由参数分隔符进行转义({}[]),请将表达式({{}[[]])中的字符数加倍。To escape routing parameter delimiter characters ({, }, [, ]), double the characters in the expression ({{, }, [[, ]]). 下表展示了正则表达式和转义版本。The following table shows a regular expression and the escaped version.

正则表达式Regular Expression 转义后的正则表达式Escaped Regular Expression
^\d{3}-\d{2}-\d{4}$ ^\\d{{3}}-\\d{{2}}-\\d{{4}}$
^[a-z]{2}$ ^[[a-z]]{{2}}$

路由中使用的正则表达式通常以脱字号 (^) 开头,并匹配字符串的起始位置。Regular expressions used in routing often start with the caret (^) character and match starting position of the string. 表达式通常以美元符号 ($) 字符结尾,并匹配字符串的结尾。The expressions often end with the dollar sign ($) character and match end of the string. ^$ 字符可确保正则表达式匹配整个路由参数值。The ^ and $ characters ensure that the regular expression match the entire route parameter value. 如果没有 ^$ 字符,正则表达式将匹配字符串内的所有子字符串,而这通常是不需要的。Without the ^ and $ characters, the regular expression match any substring within the string, which is often undesirable. 下表提供了示例并说明了它们匹配或匹配失败的原因。The following table provides examples and explains why they match or fail to match.

表达式Expression StringString 匹配Match 注释Comment
[a-z]{2} hellohello Yes 子字符串匹配Substring matches
[a-z]{2} 123abc456123abc456 Yes 子字符串匹配Substring matches
[a-z]{2} mzmz Yes 匹配表达式Matches expression
[a-z]{2} MZMZ Yes 不区分大小写Not case sensitive
^[a-z]{2}$ hellohello No 参阅上述 ^$See ^ and $ above
^[a-z]{2}$ 123abc456123abc456 No 参阅上述 ^$See ^ and $ above

有关正则表达式语法的详细信息,请参阅 .NET Framework 正则表达式For more information on regular expression syntax, see .NET Framework Regular Expressions.

若要将参数限制为一组已知的可能值,可使用正则表达式。To constrain a parameter to a known set of possible values, use a regular expression. 例如,{action:regex(^(list|get|create)$)} 仅将 action 路由值匹配到 listgetcreateFor example, {action:regex(^(list|get|create)$)} only matches the action route value to list, get, or create. 如果传递到约束字典中,字符串 ^(list|get|create)$ 将等效。If passed into the constraints dictionary, the string ^(list|get|create)$ is equivalent. 已传递到约束字典(不与模板内联)且不匹配任何已知约束的约束还将被视为正则表达式。Constraints that are passed in the constraints dictionary (not inline within a template) that don't match one of the known constraints are also treated as regular expressions.

自定义路由约束Custom Route Constraints

除了内置路由约束以外,还可以通过实现 IRouteConstraint 接口来创建自定义路由约束。In addition to the built-in route constraints, custom route constraints can be created by implementing the IRouteConstraint interface. IRouteConstraint 接口包含一个方法 Match,当满足约束时,它返回 true,否则返回 falseThe IRouteConstraint interface contains a single method, Match, which returns true if the constraint is satisfied and false otherwise.

若要使用自定义 IRouteConstraint,必须在应用的服务容器中使用应用的 ConstraintMap 注册路由约束类型。To use a custom IRouteConstraint, the route constraint type must be registered with the app's ConstraintMap in the app's service container. ConstraintMap 是将路由约束键映射到验证这些约束的 IRouteConstraint 实现的目录。A ConstraintMap is a dictionary that maps route constraint keys to IRouteConstraint implementations that validate those constraints. 应用的 ConstraintMap 可作为 services.AddRouting 调用的一部分在 Startup.ConfigureServices 中进行更新,也可以通过使用 services.Configure<RouteOptions> 直接配置 RouteOptions 进行更新。An app's ConstraintMap can be updated in Startup.ConfigureServices either as part of a services.AddRouting call or by configuring RouteOptions directly with services.Configure<RouteOptions>. 例如:For example:

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

然后,可以使用在注册约束类型时指定的名称,以常规方式将约束应用于路由。The constraint can then be applied to routes in the usual manner, using the name specified when registering the constraint type. 例如:For example:

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

URL 生成参考URL generation reference

以下示例演示如何在给定路由值字典和 RouteCollection 的情况下生成路由链接。The following example shows how to generate a link to a route given a dictionary of route values and a 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/123The VirtualPath generated at the end of the preceding sample is /package/create/123. 字典提供“跟踪包路由”模板 package/{operation}/{id}operationid 路由值。The dictionary supplies the operation and id route values of the "Track Package Route" template, package/{operation}/{id}. 有关详细信息,请参阅使用路由中间件部分或示例应用中的示例代码。For details, see the sample code in the Use Routing Middleware section or the sample app.

VirtualPathContext 构造函数的第二个参数是环境值的集合。The second parameter to the VirtualPathContext constructor is a collection of ambient values. 由于环境值限制了开发人员在请求上下文中必须指定的值数,因此环境值使用起来很方便。Ambient values are convenient to use because they limit the number of values a developer must specify within a request context. 当前请求的当前路由值被视为链接生成的环境值。The current route values of the current request are considered ambient values for link generation. 在 ASP.NET Core MVC 应用 HomeControllerAbout 操作中,无需指定控制器路由值即可链接到使用 Home 环境值的 Index 操作—。In an ASP.NET Core MVC app's About action of the HomeController, you don't need to specify the controller route value to link to the Index action—the ambient value of Home is used.

忽略与参数不匹配的环境值。Ambient values that don't match a parameter are ignored. 在显式提供的值会覆盖环境值的情况下,也会忽略环境值。Ambient values are also ignored when an explicitly provided value overrides the ambient value. 在 URL 中将从左到右进行匹配。Matching occurs from left to right in the URL.

显式提供但与路由片段不匹配的值将添加到查询字符串中。Values explicitly provided but that don't match a segment of the route are added to the query string. 下表显示使用路由模板 {controller}/{action}/{id?} 时的结果。The following table shows the result when using the route template {controller}/{action}/{id?}.

环境值Ambient Values 显式值Explicit Values 结果Result
控制器 =“Home”controller = "Home" 操作 =“About”action = "About" /Home/About
控制器 =“Home”controller = "Home" 控制器 =“Order”,操作 =“About”controller = "Order", action = "About" /Order/About
控制器 = “Home”,颜色 = “Red”controller = "Home", color = "Red" 操作 =“About”action = "About" /Home/About
控制器 =“Home”controller = "Home" 操作 =“About”,颜色 =“Red”action = "About", color = "Red" /Home/About?color=Red

如果路由具有不对应于参数的默认值,且该值以显式方式提供,则它必须与默认值相匹配:If a route has a default value that doesn't correspond to a parameter and that value is explicitly provided, it must match the default value:

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

当提供 controlleraction 的匹配值时,链接生成仅为此路由生成链接。Link generation only generates a link for this route when the matching values for controller and action are provided.

复杂段Complex segments

复杂段(例如,[Route("/x{token}y")])通过非贪婪的方式从右到左匹配文字进行处理。Complex segments (for example [Route("/x{token}y")]) are processed by matching up literals from right to left in a non-greedy way. 请参阅此代码以了解有关如何匹配复杂段的详细说明。See this code for a detailed explanation of how complex segments are matched. ASP.NET Core 无法使用代码示例,但它提供了对复杂段的合理说明。The code sample is not used by ASP.NET Core, but it provides a good explanation of complex segments.