ASP.NET Core in Action 6 Mapping URLs to endpoints using routing

6 Mapping URLs to endpoints using routing
6 使用路由将 URL 映射到端点

This chapter covers

本章涵盖

  • Mapping URLs to endpoint handlers
    将 URL 映射到端点处理程序
  • Using constraints and default values to match URLs
    使用约束和默认值来匹配 URL
  • Generating URLs from route parameters
    从路由参数生成 URL

In chapter 5 you learned how to define minimal APIs, how to return responses, and how to work with filters and route groups. One crucial aspect of minimal APIs that we touched on only lightly is how ASP.NET Core selects a specific endpoint from all the handlers defined, based on the incoming request URL. This process, called routing, is the focus of this chapter.

在第 5 章中,您学习了如何定义最小 API、如何返回响应以及如何使用筛选条件和路由组。我们仅略微涉及的最小 API 的一个关键方面是 ASP.NET Core 如何根据传入请求 URL 从定义的所有处理程序中选择特定端点。此过程称为 routing,是本章的重点。

This chapter begins by identifying the need for routing and why it’s useful. You’ll learn about the endpoint routing system introduced in ASP.NET Core 3.0 and why it was introduced, and explore the flexibility routing can bring to the URLs you expose.

本章首先确定 routing 的需求以及它为什么有用。您将了解 ASP.NET Core 3.0 中引入的端点路由系统及其引入原因,并探索路由可以为您公开的 URL 带来的灵活性。

The bulk of this chapter focuses on the route template syntax and how it can be used with minimal APIs. You’ll learn about features such as optional parameters, default parameters, and constraints, as well as how to extract values from the URL automatically. Although we’re focusing on minimal APIs in this chapter, the same routing system is used with Razor Pages and Model-View-Controller (MVC), as you’ll see in chapter 14.

本章的大部分内容重点介绍路由模板语法以及如何将其与最少的 API 一起使用。您将了解可选参数、默认参数和约束等功能,以及如何自动从 URL 中提取值。尽管我们在本章中重点介绍了最少的 API,但 Razor Pages 和模型视图控制器 (MVC) 使用相同的路由系统,如第 14 章所示。

In section 6.4 I describe how to use the routing system to generate URLs, which you can use to create links and redirect requests for your application. One benefit of using a routing system is that it decouples your handlers from the underlying URLs they’re associated with. You can use URL generation to avoid littering your code with hardcoded URLs like /product/view/3. Instead, you can generate the URLs at runtime, based on the routing system. This approach makes changing the URL for a given endpoint easier: instead of your having to hunt down every place where you used the endpoint’s URL, the URLs are updated for you automatically, with no other changes required.

在 6.4 节中,我将介绍如何使用路由系统生成 URL,您可以使用这些 URL 为您的应用程序创建链接和重定向请求。使用路由系统的一个好处是,它将处理程序与它们关联的底层 URL 分离。您可以使用 URL 生成来避免将代码与硬编码的 URL (如 /product/view/3)混在一起。相反,您可以在运行时根据路由系统生成 URL。这种方法使更改给定终端节点的 URL 变得更加容易:您不必寻找使用终端节点 URL 的每个位置,URL 会自动为您更新,无需进行其他更改。

By the end of this chapter, you should have a much clearer understanding of how an ASP.NET Core application works. You can think of routing as being the glue that ties the middleware pipeline to endpoints. With middleware, endpoints, and routing under your belt, you’ll be writing web apps in no time!

在本章结束时,您应该对 ASP.NET Core 应用程序的工作原理有了更清晰的了解。您可以将路由视为将中间件管道与终端节点联系起来的粘合剂。有了中间件、终端节点和路由,您将立即编写 Web 应用程序!

6.1 What is routing?

6.1 什么是路由?

Routing is the process of mapping an incoming request to a method that will handle it. You can use routing to control the URLs you expose in your application. You can also use routing to enable powerful features such as mapping multiple URLs to the same handler and automatically extracting data from a request’s URL.

路由是将传入请求映射到将处理该请求的方法的过程。您可以使用路由来控制在应用程序中公开的 URL。您还可以使用路由来启用强大的功能,例如映射多个 URL 指向同一处理程序,并自动从请求的 URL 中提取数据。

In chapter 4 you saw that an ASP.NET Core application contains a middleware pipeline, which defines the behavior of your application. Middleware is well suited to handling both cross-cutting concerns, such as logging and error handling, and narrowly focused requests, such as requests for images and CSS files.

在第 4 章中,您看到 ASP.NET Core 应用程序包含一个中间件管道,它定义了应用程序的行为。中间件非常适合处理横切关注点(如日志记录和错误处理)和范围狭窄的请求(如图像和 CSS 文件请求)。

To handle more complex application logic, you’ll typically use the EndpointMiddleware at the end of your middleware pipeline. This middleware can handle an appropriate request by invoking a method known as a handler and using the result to generate a response. Previous chapters described using minimal API endpoint handlers, but there are other types of handlers, such as MVC Action methods and Razor Pages, as you’ll learn in part 2 of this book.

为了处理更复杂的应用程序逻辑,您通常会在中间件管道的末尾使用 EndpointMiddleware。此中间件可以通过调用称为处理程序的方法并使用结果生成响应来处理适当的请求。前面的章节介绍了使用最少的 API 端点处理程序,但还有其他类型的处理程序,例如 MVC作方法和 Razor Pages,您将在本书的第 2 部分中学习。

One aspect that I’ve glossed over so far is how the EndpointMiddleware selects which handler executes when you receive a request. What makes a request appropriate for a given handler? The process of mapping a request to a handler is routing.

到目前为止,我所讨论的一个方面是 EndpointMiddleware 如何选择在您收到请求时执行哪个处理程序。什么使请求适合给定的处理程序?将请求映射到处理程序的过程是路由。

Definition Routing in ASP.NET Core is the process of selecting a specific handler for an incoming HTTP request. In minimal APIs, the handler is the endpoint handler associated with a route. In Razor Pages, the handler is a page handler method defined in a Razor Page. In MVC, the handler is an action method in a controller.
定义 ASP.NET Core 中的路由是为传入的 HTTP 请求选择特定处理程序的过程。在最小 API 中,处理程序是与路由关联的端点处理程序。在 Razor Pages 中,处理程序是在 Razor Page 中定义的页面处理程序方法。在 MVC 中,处理程序是控制器中的作方法。

In chapters 3 to 5, you saw several simple applications built with minimal APIs. In chapter 5, you learned the basics of routing for minimal APIs, but it’s worth exploring why routing is useful as well as how to use it. Even a simple URL path such as /person uses routing to determine which handler should be executed, as shown in figure 6.1.

在第 3 章到第 5 章中,您看到了几个使用最少 API 构建的简单应用程序。在第 5 章中,您学习了最小 API 的路由基础知识,但值得探索为什么路由很有用以及如何使用它。即使是简单的 URL 路径,如 /person,也使用路由来确定应该执行哪个处理程序,如图 6.1 所示。

Figure 6.1 The router compares the request URL with a list of configured route templates to determine which handler to execute.
图 6.1 路由器将请求 URL 与已配置的路由模板列表进行比较,以确定要执行哪个处理程序。

On the face of it, that seems pretty simple. You may wonder why I need a whole chapter to explain that obvious mapping. The simplicity of the mapping in this case belies how powerful routing can be. If this approach, using a direct comparison with static strings, were the only one available, you’d be severely limited in the applications you could feasibly build.

从表面上看,这似乎很简单。你可能会想为什么我需要一整章来解释这个明显的映射。在这种情况下,映射的简单性掩盖了路由的强大功能。如果这种方法(使用与静态字符串的直接比较)是唯一可用的方法,那么您可以构建的应用程序将受到严重限制。

Consider an e-commerce application that sells multiple products. Each product needs to have its own URL, so if you were using a purely static routing system, you’d have only two options:

考虑一个销售多种产品的电子商务应用程序。每个产品都需要有自己的 URL,因此如果您使用的是纯静态路由系统,则只有两个选项:

  • Use a different handler for every product in your product range. That approach would be unfeasible for almost any realistically sized product range.
    为您的产品系列中的每个产品使用不同的处理程序。这种方法对于几乎任何实际规模的产品系列都是不可行的。

  • Use a single handler, and use the query string to differentiate among products. This approach is much more practical, but you’d end up with somewhat-ugly URLs, such as "/product?name=big-widget" or "/product?id=12".
    使用单个处理程序,并使用查询字符串来区分产品。 这种方法要实用得多,但您最终会得到一些难看的 URL,例如“/product?name=big-widget“ 或 ”/product?id=12“ 的 API 版本。

Definition The query string is part of a URL containing additional data that doesn’t fit in the path. It isn’t used by the routing infrastructure to identify which handler to execute, but ASP.NET Core can extract values from the query string automatically in a process called model binding, as you’ll see in chapter 7. The query string in the preceding example is id=12.
定义 查询字符串是 URL 的一部分,其中包含不适合路径的其他数据。路由基础设施不使用它来标识要执行的处理程序,但 ASP.NET Core 可以在称为模型绑定的过程中自动从查询字符串中提取值,如第 7 章所示。前面示例中的查询字符串为 id=12。

With routing, you can have a single endpoint handler that can handle multiple URLs without having to resort to ugly query strings. From the point of the view of the endpoint handler, the query string and routing approaches are similar; the handler returns the results for the correct product dynamically as appropriate. The difference is that with routing, you can completely customize the URLs, as shown in figure 6.2. This feature gives you much more flexibility and can be important in real-life applications for search engine optimization (SEO).

通过路由,您可以拥有一个可以处理多个 URL 的终端节点处理程序,而不必求助于难懂的查询字符串。从端点的视角handler,则查询字符串和路由方法类似;处理程序会根据需要动态返回正确产品的结果。区别在于,使用路由,您可以完全自定义 URL,如图 6.2 所示。此功能为您提供了更大的灵活性,并且在搜索引擎优化 (SEO) 的实际应用程序中可能很重要。

Note With the flexibility of routing, you can encode the hierarchy of your site properly in your URLs, as described in Google’s SEO starter guide at http://mng.bz/EQ2J.
注意 借助路由的灵活性,您可以在 URL 中正确编码网站的层次结构,如 http://mng.bz/EQ2J 的 Google SEO 入门指南中所述。

Figure 6.2 If you use static URL-based mapping, you need a different handler for every product in your product range. With a query string, you can use a single handler, and the query string contains the data. With routing, multiple URLs map to a single handler, and a dynamic parameter captures the difference in the URL.
图 6.2 如果使用基于静态 URL 的映射,则产品范围中的每个产品都需要不同的处理程序。使用查询字符串,您可以使用单个处理程序,并且查询字符串包含数据。使用路由时,多个 URL 映射到单个处理程序,并且动态参数捕获 URL 中的差异。

As well as enabling dynamic URLs, routing fundamentally decouples the URLs in your application from the definition of your handlers.

除了启用动态 URL 之外,路由还从根本上将应用程序中的 URL 与处理程序的定义分离。

File-system based routing
基于文件系统的路由
In one alternative to routing, the location of a handler on disk dictates the URL you use to invoke it. The downside of this approach is that if you want to change an exposed URL, you also need to change the location of the handler on disk.
在路由的另一种替代方法中,处理程序在磁盘上的位置决定了用于调用它的 URL。这种方法的缺点是,如果要更改公开的 URL,还需要更改处理程序在磁盘上的位置。
This file-based approach may sound like a strange choice, but it has many advantages for some apps, primarily in terms of simplicity. As you’ll see in part 2, Razor Pages is partially file-based but also uses routing to get the best of both worlds!
这种基于文件的方法听起来可能很奇怪,但对于某些应用程序来说,它有很多优点,主要是在简单性方面。正如您将在第 2 部分中看到的那样,Razor Pages 部分基于文件,但也使用路由来获得两全其美的效果!

With routing it’s easy to modify your exposed URLs without changing any filenames or locations. You can also use routing to create friendlier URLs for users, which can improve discovery and “hackability.” All of the following routes could point to the same handler:

通过路由,可以轻松修改公开的 URL,而无需更改任何文件名或位置。您还可以使用路由为用户创建更友好的 URL,这可以提高发现和“可黑客攻击性”。以下所有路由都可以指向同一个处理程序:

  • /rates/view/1
  • /rates/view/USD
  • /rates/current-exchange-rate/USD
  • /current-exchange-rate-for-USD

This level of customization isn’t often necessary, but it’s quite useful to have the capability to customize your app’s URLs when you need it. In the next section we’ll look at how routing works in practice in ASP.NET Core.

这种级别的自定义通常不是必需的,但能够在需要时自定义应用程序的 URL 非常有用。在下一节中,我们将了解 ASP.NET Core 中的路由实际工作原理。

6.2 Endpoint routing in ASP.NET Core

6.2 ASP.NET Core 中的终端节点路由

In this section I describe how endpoint routing works in ASP.NET Core, specifically with respect to minimal APIs and the middleware pipeline. In chapter 14 you’ll learn how routing is used with Razor Pages and the ASP.NET Core MVC framework.

在本节中,我将介绍终端节点路由在 ASP.NET Core 中的工作原理,特别是关于最小 API 和中间件管道。在第 14 章中,您将了解如何将路由与 Razor Pages 和 ASP.NET Core MVC 框架一起使用。

Routing has been part of ASP.NET Core since its inception, but it has been through some big changes. In ASP.NET Core 2.0 and 2.1, routing was restricted to Razor Pages and the ASP.NET Core MVC framework. There was no dedicated routing middleware in the middleware pipeline; routing happened only within Razor Pages or MVC components.

路由自成立以来一直是 ASP.NET Core 的一部分,但它经历了一些重大变化。在 ASP.NET Core 中2.0 和 2.1 中,路由仅限于 Razor Pages 和 ASP.NET Core MVC 框架。中间件管道中没有专用的路由中间件;路由仅在 Razor Pages 或 MVC 组件中发生。

Unfortunately, restricting routing to the MVC and Razor Pages infrastructure made some things a bit messy. Some cross-cutting concerns, such as authorization, were restricted to the MVC infrastructure and were hard to use from other middleware in your application. That restriction caused inevitable duplication, which wasn’t ideal.

遗憾的是,将路由到 MVC 和 Razor Pages 基础结构会使某些事情变得有点混乱。一些横切关注点(如授权)仅限于 MVC 基础设施,并且很难从应用程序中的其他中间件中使用。这种限制导致了不可避免的重复,这并不理想。

ASP.NET Core 3.0 introduced a new routing system: endpoint routing. Endpoint routing makes the routing system a more fundamental feature of ASP.NET Core and no longer ties it to the MVC infrastructure. Now Razor Pages, MVC, and other middleware can all use the same routing system. .NET 7 continues to use the same endpoint routing system, which is integral to the minimal API functionality that was introduced in .NET 6.

ASP.NET Core 3.0 引入了一个新的路由系统:端点路由。端点路由使路由系统成为 ASP.NET Core 的更基本功能,不再将其绑定到 MVC 基础设施。现在,Razor Pages、MVC 和其他中间件都可以使用相同的路由系统。.NET 7 继续使用相同的终结点路由系统,这是 .NET 6 中引入的最小 API 功能不可或缺的一部分。

Endpoint routing is fundamental to all but the simplest ASP.NET Core apps. It’s implemented with two pieces of middleware, which you’ve already seen:

端点路由是除最简单的 ASP.NET Core 应用程序之外的所有应用程序的基础。它是通过两个中间件实现的,您已经看到了:

  • EndpointRoutingMiddleware—This middleware chooses which registered endpoints execute for a given request at runtime. To make it easier to distinguish between the two types of middleware, I’ll be referring to this middleware as the RoutingMiddleware throughout this book.
    EndpointRoutingMiddleware — 此中间件选择在运行时为给定请求执行哪些已注册的终端节点。为了更容易区分这两种类型的中间件,我在本书中将此中间件称为 RoutingMiddleware。

  • EndpointMiddleware—This middleware is typically placed at the end of your middleware pipeline. The middleware executes the endpoint selected by the RoutingMiddleware at runtime.
    EndpointMiddleware — 此中间件通常位于中间件管道的末尾。中间件在运行时执行 RoutingMiddleware 选择的端点。

You register the endpoints in your application by calling Map* functions on an IEndpointRouteBuilder instance. In .NET 7 apps, this instance typically is a WebApplication instance but doesn’t have to be, as you’ll see in chapter 30.

您可以通过在 IEndpointRouteBuilder 实例上调用 Map* 函数在应用程序中注册终端节点。在 .NET 7 应用程序中,此实例通常是 Web- Application 实例,但并非必须如此,如第 30 章所示。

Definition An endpoint in ASP.NET Core is a handler that returns a response. Each endpoint is associated with a URL pattern. Depending on the type of application you’re building, minimal API handlers, Razor Page handlers, or MVC controller action methods typically make up the bulk of the endpoints in an application. You can also use simple middleware as an endpoint or a health-check endpoint, for example.
定义 ASP.NET Core 中的终端节点是返回响应的处理程序。每个端点都与一个 URL 模式相关联。根据要生成的应用程序类型,最少的 API 处理程序、Razor Page 处理程序或 MVC 控制器作方法通常构成应用程序中的大部分终结点。例如,您还可以使用简单的中间件作为终端节点,或者您可以使用运行状况检查终端节点。

WebApplication implements IEndpointRouteBuilder, so you can register endpoints on it directly. Listing 6.1 shows how you’d register several endpoints:

WebApplication 实现 IEndpointRouteBuilder,因此您可以直接在其上注册端点。清单 6.1 展示了如何注册多个端点:

  • A minimal API handler using MapGet(), as you’ve seen in previous chapters.
    使用 MapGet() 的最小 API 处理程序,如前几章所示。

  • A health-check endpoint using MapHealthChecks(). You can read more about health checks at http://mng.bz/N2YD.
    使用 MapHealthChecks() 的运行状况检查终端节点。您可以在 http://mng.bz/N2YD 上阅读有关运行状况检查的更多信息。

  • All Razor Pages endpoints in the application using MapRazorPages(). You’ll learn more about routing with Razor Pages in chapter 14.
    使用 MapRazorPages() 的应用程序中的所有 Razor Pages 终结点。您将在第 14 章中了解有关使用 Razor Pages 进行路由的更多信息。

Listing 6.1 Registering multiple endpoints with WebApplication
清单 6.1 使用 注册多个端点Web应用程序

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddHealthChecks(); ❶
builder.Services.AddRazorPages(); ❶

WebApplication app = builder.Build();

app.MapGet("/test", () => "Hello world!"); ❷
app.MapHealthChecks("/healthz"); ❸
app.MapRazorPages(); ❹

app.Run();

❶ Adds the services required by the health-check middleware and Razor Pages
添加运行状况检查中间件和 Razor Pages 所需的服务

❷ Registers a minimal API endpoint that returns “Hello World!” at the route /test
注册一个最小的 API 端点,该端点在路由 /test 中返回 “Hello World!”

❸ Registers a health-check endpoint at the route /healthz
在路由 /healthz 中注册一个健康检查端点

❹ Registers all the Razor Pages in your application as endpoints
将应用程序中的所有 Razor Pages 注册为端点

Each endpoint is associated with a route template that defines which URLs the endpoint should match. You can see two route templates, "/healthz" and "/test", in listing 6.1.

每个终端节点都与一个路由模板相关联,该模板定义终端节点应匹配的 URL。在清单 6.1 中可以看到两个路由模板,“/healthz” 和 “/test”。

Definition A route template is a URL pattern that is used to match against request URLs, which are strings of fixed values, such as "/test" in the previous listing. They can also contain placeholders for variables, as you’ll see in section 6.3.
定义 路由模板是一种 URL 模式,用于匹配请求 URL,请求 URL 是固定值的字符串,如上一个列表中的 “/test”。 它们还可以包含变量的占位符,如 6.3 节所示。

The WebApplication stores the registered routes and endpoints in a dictionary that’s shared by the RoutingMiddleware and the EndpointMiddleware.

WebApplication 将注册的路由和终端节点存储在 RoutingMiddleware 和 EndpointMiddleware 共享的字典中。

Tip By default, WebApplication automatically adds the RoutingMiddleware to the start of the middleware and EndpointMiddleware to the end of the middleware pipeline, though you can override the location in the pipeline by calling UseRouting() or UseEndpoints(). See section 4.2.3 for more details about automatically added middleware.
提示 默认情况下,WebApplication 会自动将 RoutingMiddleware 添加到中间件的开头,将 EndpointMiddleware 添加到中间件管道的末尾,但您可以通过调用 UseRouting() 或 UseEndpoints() 来覆盖管道中的位置。有关自动添加的中间件的更多详细信息,请参见 Section 4.2.3。

At runtime, the RoutingMiddleware compares an incoming request with the routes registered in the dictionary. If the RoutingMiddleware finds a matching endpoint, it makes a note of which endpoint was selected and attaches that to the request’s HttpContext object. Then it calls the next middleware in the pipeline. When the request reaches the EndpointMiddleware, the middleware checks to see which endpoint was selected and executes the endpoint (and any associated endpoint filters), as shown in figure 6.3.

在运行时,RoutingMiddleware 将传入请求与字典中注册的路由进行比较。如果 RoutingMiddleware 找到匹配的端点,它会记下选择了哪个端点,并将其附加到请求的 HttpContext 对象。然后,它会调用管道中的下一个中间件。当请求到达 EndpointMiddleware 时,中间件会检查选择哪个端点并执行该端点(以及任何关联的端点过滤器),如图 6.3 所示。

Figure 6.3 Endpoint routing uses a two-step process. The RoutingMiddleware selects which endpoint to execute, and the EndpointMiddleware executes it. If the request URL doesn’t match a route template, the endpoint middleware won’t generate a response.
图 6.3 终端节点路由使用两步过程。RoutingMiddleware 选择要执行的端点,EndpointMiddleware 执行它。如果请求 URL 与路由模板不匹配,则终端节点中间件不会生成响应。

If the request URL doesn’t match a route template, the RoutingMiddleware doesn’t select an endpoint, but the request still continues down the middleware pipeline. As no endpoint is selected, the EndpointMiddleware silently ignores the request and passes it to the next middleware in the pipeline. The EndpointMiddleware is typically the final middleware in the pipeline, so the “next” middleware is normally the dummy middleware that always returns a 404 Not Found response, as you saw in chapter 4.

如果请求 URL 与路由模板不匹配,则 RoutingMiddleware 不会选择终端节点,但请求仍会继续沿中间件管道向下移动。由于未选择任何终端节点,因此 EndpointMiddleware 会静默忽略该请求并将其传递给管道中的下一个中间件。EndpointMiddleware 通常是管道中的最后一个中间件,因此 “next” 中间件通常是始终返回 404 Not Found 响应的虚拟中间件,如第 4 章所示。

Tip If the request URL doesn’t match a route template, no endpoint is selected or executed. The whole middleware pipeline is still executed, but typically a 404 response is returned when the request reaches the dummy 404 middleware.
提示 如果请求 URL 与路由模板不匹配,则不会选择或执行任何终端节点。整个中间件管道仍然会执行,但当请求到达虚拟 404 中间件时,通常会返回 404 响应。

The advantage of having two separate pieces of middleware to handle this process may not be obvious at first blush. Figure 6.3 hinted at the main benefit: all middleware placed after the RoutingMiddleware can see which endpoint is going to be executed before it is.

使用两个单独的中间件来处理此过程的优势乍一看可能并不明显。图 6.3 暗示了主要的好处:所有放在 RoutingMiddleware 后面的中间件都可以在执行之前看到哪个端点将被执行。

Note Only middleware placed after the RoutingMiddleware can detect which endpoint is going to be executed.
注意 只有放在 RoutingMiddleware 之后的 middleware 才能检测到将要执行的端点。

Figure 6.4 shows a more realistic middleware pipeline in which middleware is placed both before the RoutingMiddleware and between the RoutingMiddleware and the EndpointMiddleware.

图 6.4 显示了一个更真实的中间件管道,其中中间件放置在 RoutingMiddleware 之前以及 RoutingMiddleware 和 EndpointMiddleware 之间。

Figure 6.4 Middleware placed before the routing middleware doesn’t know which endpoint the routing middleware will select. Middleware placed between the routing middleware and the endpoint middleware can see the selected endpoint.
图 6.4 放置在路由中间件前面的中间件不知道路由中间件将选择哪个端点。放置在路由中间件和终端节点中间件之间的中间件可以看到所选终端节点。

The StaticFileMiddleware in figure 6.4 is placed before the RoutingMiddleware, so it executes before an endpoint is selected. Conversely, the AuthorizationMiddleware is placed after the RoutingMiddleware, so it can tell which minimal API endpoint will be executed eventually. In addition, it can access certain metadata about the endpoint, such as its name and the permissions required to access it.

图 6.4 中的 StaticFileMiddleware 位于 RoutingMiddleware 之前,因此它在选择端点之前执行。相反,AuthorizationMiddleware 位于 RoutingMiddleware 之后,因此它可以判断最终将执行哪个最小的 API 端点。此外,它还可以访问有关终端节点的某些元数据,例如其名称和访问终端节点所需的权限。

Tip The AuthorizationMiddleware needs to know which endpoint will be executed, so it must be placed after the RoutingMiddleware and before the EndpointMiddleware in your middleware pipeline. I discuss authorization in more detail in chapter 24.
提示 AuthorizationMiddleware 需要知道将执行哪个端点,因此它必须放在中间件管道中的 RoutingMiddleware 之后和 EndpointMiddleware 之前。我在第 24 章中更详细地讨论了授权。

It’s important to remember the different roles of the two types of routing middleware when building your application. If you have a piece of middleware that needs to know which endpoint (if any) a given request will execute, you need to make sure to place it after the RoutingMiddleware and before the EndpointMiddleware.

在构建应用程序时,请务必记住这两种类型的路由中间件的不同角色。如果你有一个中间件需要知道给定请求将执行哪个端点(如果有的话),你需要确保将其放在 RoutingMiddleware 之后和 EndpointMiddleware 之前。

Tip If you want to place middleware before the RoutingMiddleware, such as the StaticFileMiddleware in figure 6.4, you need to override the automatic middleware added by WebApplication by calling UseRouting() at the appropriate point in your middleware pipeline. See listing 4.3 in chapter 4 for an example.
提示 如果你想把中间件放在 RoutingMiddleware 之前,比如图 6.4 中的 StaticFileMiddleware,你需要在中间件管道中的适当位置调用 UseRouting() 来覆盖 WebApplication 添加的自动中间件。有关示例,请参见第 4 章中的清单 4.3。

I’ve covered how the RoutingMiddleware and EndpointMiddleware interact to provide routing capabilities in ASP.NET Core, but we’ve looked at only simple route templates so far. In the next section we’ll look at some of the many features available with route templates.

我已经介绍了 RoutingMiddleware 和 EndpointMiddleware 如何交互以在 ASP.NET Core 中提供路由功能,但到目前为止,我们只了解了简单的路由模板。在下一节中,我们将了解路由模板提供的众多功能中的一些。

6.3 Exploring the route template syntax

6.3 探索路由模板语法

So far in this book we’ve looked at simple route templates consisting of fixed values, such as /person and /test, as well as using a basic route parameter such as /fruit/{id}. In this section we explore the full range of features available in route templates, such as default values, optional segments, and constraints.

到目前为止,在本书中,我们已经了解了由固定值组成的简单路由模板,例如 /person 和 /test,以及使用基本路由参数,例如/fruit/{id} 中。在本节中,我们将探讨路由模板中提供的全部功能,例如默认值、可选分段和约束。

6.3.1 Working with parameters and literal segments

6.3.1 使用参数和文本段

Route templates have a rich, flexible syntax. Figure 6.5, however, shows a simple example, similar to ones you’ve already seen.

路由模板具有丰富、灵活的语法。然而,图 6.5 显示了一个简单的例子,类似于你已经看到的那些。

Figure 6.5 A simple route template showing a literal segment and two required route parameters
图 6.5 一个简单的路由模板,其中显示了一个文本段和两个必需的路由参数

The routing middleware parses a route template by splitting it into segments. A segment is typically separated by the / character, but it can be any valid character.

路由中间件通过将路由模板拆分为段来解析路由模板。段通常由字符分隔,但它可以是任何有效字符。/

Definition Segments that use a character other than / are called complex segments. I generally recommend that you avoid them and stick to using / as a separator. Complex segments have some peculiarities that make them hard to use, so be sure to check the documentation at http://mng.bz/D4RE before you use them.
定义 使用其他字符的线段称为复杂线段。我通常建议您避免使用它们并坚持用作分隔符。复杂区段具有一些特性,使其难以使用,因此请务必在使用 http://mng.bz/D4RE 之前查看文档。

Each segment is either
每个段是

  • A literal value such as product in figure 6.5
    如图 6.5 所示的 Literal 值product

  • A route parameter such as {category} and {name} in figure 6.5
    路由参数,如图 6.5 中的 和{category}{name}

The request URL must match literal values exactly (ignoring case). If you need to match a particular URL exactly, you can use a template consisting only of literals.

请求 URL 必须与文字值完全匹配(忽略大小写)。如果需要精确匹配特定 URL,可以使用仅包含文本的模板。

Tip Literal segments in ASP.NET Core aren’t case-sensitive.
提示 ASP.NET Core 中的文本段不区分大小写。

Imagine that you have a minimal API in your application defined using
假设您在应用程序中有一个使用

app.MapGet("/About/Contact", () => {/* */})

This route template, “/About/Contact", consists only of literal values, so it matches only the exact URL (ignoring case). None of the following URLs would match this route template:
此路由模板 “/About/Contact” 仅包含文本值,因此它仅匹配确切的 URL(忽略大小写)。以下 URL 都不匹配此路由模板:

  • /about
  • /about-us/contact
  • /about/contact/email
  • /about/contact-us

Route parameters are sections of a URL that may vary but are still a match for the template. You define them by giving them a name and placing them in braces, such as {category} or {name}. When used in this way, the parameters are required, so the request URL must have a segment that they correspond to, but the value can vary.

路由参数是 URL 的部分,这些部分可能会有所不同,但仍与模板匹配。您可以通过为它们命名并将它们放在大括号中来定义它们,例如{类别}或 {name} 的以这种方式使用时,参数是必需的,因此请求 URL 必须具有它们对应的区段,但该值可能会有所不同。

The ability to use route parameters gives you great flexibility. The simple route template "/{category}/{name}" could be used to match all the product-page URLs in an e-commerce application:
使用路由参数的能力为您提供了极大的灵活性。简单的路由模板 “/{category}/{name}” 可用于匹配电子商务应用程序中的所有产品页面 URL:

  • /bags/rucksack-a—Where category=bags and name=rucksack-a

  • /shoes/black-size9—Where category=shoes and name=black-size9

But note that this template would not map the following URLs:
但请注意,此模板不会映射以下 URL:

  • /socks/—No name parameter specified

  • /trousers/mens/formal—Extra URL segment, formal, not found in route template

When a route template defines a route parameter and the route matches a URL, the value associated with the parameter is captured and stored in a dictionary of values associated with the request. These route values typically drive other behavior in the endpoint and can be injected into the handlers (as you saw briefly in chapter 5) in a process called model binding.

当路由模板定义路由参数并且路由与 URL 匹配时,将捕获与该参数关联的值并将其存储在与请求关联的值字典中。这些路由值通常驱动端点中的其他行为,并且可以在称为模型绑定的过程中注入处理程序(如您在第 5 章中简要看到的那样)。

Definition Route values are the values extracted from a URL based on a given route template. Each route parameter in a template has an associated route value, and the values are stored as a string pair in a dictionary. They can be used during model binding, as you’ll see in chapter 7.
定义 路由值 是根据给定路由模板从 URL 中提取的值。模板中的每个路由参数都有一个关联的路由值,这些值作为字符串对存储在字典中。它们可以在模型绑定期间使用,如第 7 章所示。

Literal segments and route parameters are the two cornerstones of ASP.NET Core route templates. With these two concepts, it’s possible to build all manner of URLs for your application. In the remainder of section 6.3 we’ll look at additional features that let you have optional URL segments, provide default values when a segment isn’t specified, and place additional constraints on the values that are valid for a given route parameter.

文字段和路由参数是 ASP.NET Core 路由模板的两个基石。有了这两个概念,就可以为您的应用程序构建各种 URL。在第 6.3 节的其余部分中,我们将介绍其他功能,这些功能允许您使用可选的 URL 段,在未指定段时提供默认值,以及对给定路由参数有效的值设置其他约束。

6.3.2 Using optional and default values

6.3.2 使用可选值和默认值

In section 6.3.1 you saw a simple route template with a literal segment and two required routing parameters. Figure 6.6 shows a more complex route that uses several additional features.

在 6.3.1 节中,您看到了一个简单的路由模板,其中包含一个文字段和两个必需的路由参数。图6.6 显示了一个更复杂的路由,它使用了几个附加功能。

Figure 6.6 A more complex route template showing literal segments, named route parameters, optional parameters, and default values.
图 6.6 一个更复杂的路由模板,显示文本段、命名路由参数、可选参数和默认值。

The literal product segment and the required {category} parameter are the same as those in in figure 6.6. The {name} parameter looks similar, but it has a default value specified for it by =all. If the URL doesn’t contain a segment corresponding to the {name} parameter, the router will use the all value instead.

文本 product segment 和所需的 {category}参数与图 6.6 中的相同。这{name} 参数看起来类似,但它的默认值由 =all 指定。如果 URL 不包含与 {name} 参数对应的段,则路由器将改用 all 值。

The final segment of figure 6.6, {id?}, defines an optional route parameter called id. This segment of the URL is optional. If this segment is present, the router captures the value for the {id} parameter; if the segment isn’t there, the router doesn’t create a route value for id.

图 6.6 的最后一段 {id?} 定义了一个名为 id 的可选路由参数。URL 的此段是可选的。如果存在此段,则路由器将捕获 {id} 参数的值;如果 segment 不存在,则路由器不会为 id 创建 route 值。

You can specify any number of route parameters in your templates, and these values will be available to you for model binding. The complex route template shown in figure 6.6 allows you to match a greater variety of URLs by making {name} and {id} optional and by providing a default for {name}. Table 6.1 shows some of the URLs that this template would match and the corresponding route values that the router would set.

您可以在模板中指定任意数量的路由参数,这些值将可用于模型绑定。图中所示的复杂路由模板6.6 允许您通过以下方式匹配更多种类的 URL{姓名}和 {id} 可选,并通过为{姓名}。表 6.1 显示了此模板将匹配的一些 URL 以及路由器将设置的相应路由值。

Table 6.1 URLs that would match the template of figure 6.7 and their corresponding route values
表 6.1 与图 6.7 中的模板匹配的 URL 及其相应的路由值

URL Route values
/product/shoes/formal/3 category=shoes, name=formal, id=3
/product/shoes/formal category=shoes, name=formal
/product/shoes category=shoes, name=all
/product/bags/satchels category=bags, name=satchels
/product/phones category=phones, name=all
/product/computers/laptops/ABC-123 category=computers, name=laptops,id=ABC-123

Note that there’s no way to specify a value for the optional {id} parameter without also specifying the {category} and {name} parameters. You can put an optional parameter (that doesn’t have a default) only at the end of a route template.
注意 无法为可选的{id} 参数,但未指定 {category}和 {name} 参数。您可以放置一个可选参数(没有默认值)仅在路由模板的末尾。

Using default values allows you to have multiple ways to call the same URL, which may be desirable in some cases. Given the route template in figure 6.6, the following two URLs are equivalent:

使用默认值允许您通过多种方式调用同一 URL,这在某些情况下可能是可取的。给定图 6.6 中的路由模板,以下两个 URL 是等效的:

  • /product/shoes
  • /product/shoes/all

Both URLs will execute the same endpoint handler, with the same route values of category=shoes and name=all. Using default values allows you to use shorter, more memorable URLs in your application for common URLs but still gives you the flexibility to match a variety of routes in a single template.

这两个 URL 将执行相同的端点处理程序,具有相同的路由值 category=shoes 和 name=all。使用默认值允许您在应用程序中为常见 URL 使用更短、更易记的 URL,但您仍然可以灵活地在单个模板中匹配各种路由。

6.3.3 Adding additional constraints to route parameters

6.3.3 向路由参数添加其他约束

By defining whether a route parameter is required or optional and whether it has a default value, you can match a broad range of URLs with terse template syntax. Unfortunately, in some cases this approach ends up being a little too broad. Routing only matches URL segments to route parameters; it doesn’t know anything about the data you’re expecting those route parameters to contain. If you consider a template similar to the one in figure 6.6, "/{category}/{name=all}/{id?}", all of the following URLs would match:

通过定义路由参数是必需的还是可选的,以及它是否具有默认值,您可以使用简洁的模板语法匹配各种 URL。不幸的是,在某些情况下,这种方法最终有点过于宽泛。路由仅将 URL 段与路由参数匹配;它不知道您期望这些路由参数包含的数据。如果您考虑类似于图 6.6 中的模板,“/{category}/{name=all}/{id?}”,则以下所有 URL 都将匹配:

  • /shoes/sneakers/test
  • /shoes/sneakers/123
  • /Account/ChangePassword
  • /ShoppingCart/Checkout/Start
  • /1/2/3

These URLs are perfectly valid given the template’s syntax, but some might cause problems for your application. These URLs have two or three segments, so the router happily assigns route values and matches the template when you might not want it to! These are the route values assigned:

根据模板的语法,这些 URL 是完全有效的,但有些 URL 可能会给您的应用程序带来问题。这些 URL 有两到三个段,因此路由器会很高兴地分配路由值,并在您可能不希望的时候匹配模板!以下是分配的路由值:

  • /shoes/sneakers/test has route values category=shoes, name=sneakers, and id=test.

  • /shoes/sneakers/123 has route values category=shoes, name=sneakers, and id=123.

  • /Account/ChangePassword has route values category=Account, and name=ChangePassword.

  • /Cart/Checkout/Start has route values category=Cart, name=Checkout, and id=Start.

  • /1/2/3 has route values category=1, name=2, and id=3.

Typically, the router passes route values to handlers through model binding, which you saw briefly in chapter 5 (and which chapter 7 discusses in detail). A minimal API endpoint defined as

通常,路由器通过模型绑定将 route 值传递给处理程序,您在第 5 章中简要看到过(第 7 章详细讨论了)。定义为

app.MapGet("/fruit/{id}", (int id) => "Hello world!");

would obtain the id argument from the id route value. If the id route parameter ends up assigned a noninteger value from the URL, you’ll get an exception when it’s bound to the integer id parameter.

将从 route 值中获取id参数。如果 route 参数最终从 URL 分配了一个非整数值,则当它绑定到 integer 参数时,您将收到异常。

To avoid this problem, it’s possible to add more constraints to a route template that must be satisfied for a URL to be considered a match. You can define constraints in a route template for a given route parameter by using : (colon). {id:int}, for example, would add the IntRouteConstraint to the id parameter. For a given URL to be considered a match, the value assigned to the id route value must be convertible to an integer.

为避免此问题,可以向路由模板添加更多约束,必须满足这些约束才能将 URL 视为匹配项。您可以使用 :(冒号)在路由模板中为给定路由参数定义约束。{id:int},则会添加IntRouteConstraint 设置为 id 参数。要使给定 URL 被视为匹配项,分配给 id 路由值的值必须可转换为整数。

You can apply a large number of route constraints to route templates to ensure that route values are convertible to appropriate types. You can also check more advanced constraints, such as that an integer value has a particular minimum value, that a string value has a maximum length, and that a value matches a given regular expression. Table 6.2 describes some of the available constraints. You can find a more complete list online in Microsoft’s documentation at http://mng.bz/BmRJ.

您可以将大量路由约束应用于路由模板,以确保路由值可转换为适当的类型。您还可以检查更高级的约束,例如整数值是否具有特定的最小值、字符串值是否具有最大长度或值是否与给定的正则表达式匹配。表 6.2 描述了一些可用的约束。您可以找到更完整的在线列表,请参阅 Microsoft 的文档,网址为 http://mng.bz/BmRJ

Table 6.2 A few route constraints and their behavior when applied
Constraint
表 6.2 一些路由约束及其应用时的行为

Constraint Example Description Match examples
int {qty:int} Matches any integer 123, -123, 0
Guid {id:guid} Matches any Guid d071b70c-a812-4b54-87d2-7769528e2814
decimal {cost:decimal} Matches any decimal value 29.99, 52,-1.01
min(value) {age:min(18)} Matches integer values of 18 or greater 18, 20
length(value) {name:length(6)} Matches string values with a length of 6 Andrew,123456
optional int {qty:int?} Optionally matches any integer 123, -123,0,null
optional int max(value) {qty:int:max(10)?} Optionally matches any integer of 10 or less 3, -123, 0,null

Tip As you can see from table 6.2, you can also combine multiple constraints by separating the constraints with colons.
提示 从表 6.2 中可以看出,您还可以通过用冒号分隔约束来组合多个约束。

Using constraints allows you to narrow down the URLs that a given route template will match. When the routing middleware matches a URL to a route template, it interrogates the constraints to check that they’re all valid. If they aren’t valid, the route template isn’t considered a match, and the endpoint won’t be executed.

使用 constraints 可以缩小给定路由模板将匹配的 URL 的范围。当路由中间件将 URL 与路由模板匹配时,它会询问约束以检查它们是否都有效。如果它们无效,则路由模板不被视为匹配项,并且不会执行终端节点。

Warning Don’t use route constraints to validate general input, such as to check that an email address is valid. Doing so will result in 404 “Page not found” errors, which will be confusing for the user. You should also be aware that all these built-in constraints assume invariant culture, which may prove to be problematic if your application uses URLs localized for other languages.
警告 不要使用路由约束来验证常规输入,例如检查电子邮件地址是否有效。这样做将导致 404 “Page not found” 错误,这将使用户感到困惑。您还应该知道,所有这些内置约束都假定固定区域性,如果您的应用程序使用针对其他语言本地化的 URL,这可能会带来问题。

Constraints are best used sparingly, but they can be useful when you have strict requirements on the URLs used by the application, as they can allow you to work around some otherwise-tricky combinations. You can even create custom constraints, as described in the documentation at http://mng.bz/d14Q.

约束最好谨慎使用,但当您对应用程序使用的 URL 有严格要求时,它们可能很有用,因为它们可以让您解决一些其他棘手的组合。您甚至可以创建自定义约束,如 http://mng.bz/d14Q 中的文档中所述。

Constraints and overlapping routes
约束和重叠路由
If you have a well-designed set of URLs for your application, you’ll probably find that you don’t need to use route constraints. Route constraints are most useful when you have overlapping route templates.
如果您的应用程序有一组设计良好的 URL,您可能会发现不需要使用路由约束。当您有重叠的路由模板时,路由约束最有用。
Suppose that you have an endpoint with the route template "/{number}/{name}" and another with the template "/{product}/{id}". When a request with the URL /shoes/123 arrives, which template is chosen? Both match, so the routing middleware panics and throws an exception—not ideal.
假设您有一个路由模板为 “/{number}/{name}” 的终端节点,另一个终端节点的模板为 “/{product}/{id}”。当 URL 为 /shoes/123 的请求到达时,选择哪个模板?两者都匹配,因此路由中间件会 panic 并引发异常 — 这并不理想。
Using constraints can fix this problem. If you update the first template to "/{number:int}/{name}", the integer constraint means that the URL is no longer a match, and the routing middleware can choose correctly. Note, however, that the URL /123/shoes still matches both route templates, so you’re not out of the woods.
使用约束可以解决此问题。如果将第一个模板更新为 “/{number:int}/{name}”,则整数约束表示 URL 不再匹配,路由中间件可以正确选择。但请注意,URL /123/shoes 仍然与两个路由模板匹配,因此您不会陷入困境。
Generally, you should avoid overlapping route templates like these, as they’re often confusing and more trouble than they’re worth. If your route templates are well defined so that each URL maps to a single template, ASP.NET Core routing will work without any difficulties. Sticking to the built-in conventions as far as possible is the best way to stay on the happy path!
通常,您应该避免像这样的重叠路由模板,因为它们通常会令人困惑并且比它们的价值更麻烦。如果您的路由模板定义明确,以便每个 URL 都映射到单个模板,则 ASP.NET Core 路由将毫无困难地工作。尽可能坚持内在的约定俗成是保持快乐道路的最佳方式!

We’re coming to the end of our look at route templates, but before we move on, there’s one more type of parameter to think about: the catch-all parameter.

我们即将结束对路由模板的介绍,但在我们继续之前,还有一种类型的参数需要考虑:catch-all 参数。

6.3.4 Matching arbitrary URLs with the catch-all parameter

6.3.4 使用 catch-all 参数匹配任意 URL

You’ve seen how route templates take URL segments and attempt to match them to parameters or literal strings. These segments normally split around the slash character, /, so the route parameters themselves won’t contain a slash. What do you do if you need them to contain a slash or don’t know how many segments you’re going to have?

您已经了解了路由模板如何获取 URL 段并尝试将它们与参数或文本字符串匹配。这些段通常围绕斜杠字符 / 进行分割,因此,路由参数本身不会包含斜杠。如果您需要它们包含斜杠或不知道您将拥有多少个段,该怎么办?

Imagine that you’re building a currency-converter application that shows the exchange rate from one currency to one or more other currencies. You’re told that the URLs for this page should contain all the currencies as separate segments. Here are some examples:

假设您正在构建一个货币转换器应用程序,该应用程序显示从一种货币到一种或多种其他货币的汇率。您被告知此页面的 URL 应包含所有货币作为单独的段。以下是一些示例:

  • /USD/convert/GBP—Show USD with exchange rate to GBP.

  • /USD/convert/GBP/EUR—Show USD with exchange rates to GBP and EUR.

  • /USD/convert/GBP/EUR/CAD—Show USD with exchange rates for GBP, EUR, and CAD.

If you want to support showing any number of currencies, as these URLs do, you need a way to capture everything after the convert segment. You could achieve this goal by using a catch-all parameter in the route template, as shown in figure 6.7.

如果要像这些 URL 一样支持显示任意数量的货币,则需要一种方法来捕获 convert 区段之后的所有内容。您可以通过在路由模板中使用 catch-all 参数来实现此目标,如图 6.7 所示。

Figure 6.7 You can use catch-all parameters to match the remainder of a URL. Catch-all parameters may include the / character or may be an empty string.
图 6.7 您可以使用 catch-all 参数来匹配 URL 的其余部分。Catch-all 参数可以包含 / 字符,也可以是空字符串。

You can declare catch-all parameters by using either one or two asterisks inside the parameter definition, as in {*others} and {**others}. These parameters match the remaining unmatched portion of a URL, including any slashes or other characters that aren’t part of earlier parameters. They can also match an empty string. For the USD/convert/GBP/EUR URL, the value of the route value others would be the single string "GBP/EUR".

您可以通过在参数定义中使用一个或两个星号来声明 catch-all 参数,如 和 。这些参数与 URL 的剩余不匹配部分匹配,包括任何斜杠或不属于早期参数的其他字符。它们还可以匹配空字符串。对于 URL,route 值的值将是单个字符串 。{*others}{**others}USD/convert/GBP/EURothers"GBP/EUR"

Tip Catch-all parameters are greedy and will capture the whole unmatched portion of a URL. Where possible, to avoid confusion, avoid defining route templates with catch-all parameters that overlap other route templates.
提示 Catch-all 参数是贪婪的,将捕获 URL 的整个不匹配部分。为避免混淆,请避免使用与其他路由模板重叠的 catch-all 参数定义路由模板。

The one- and two-asterisk versions of the catch-all parameter behave identically when routing an incoming request to an endpoint. The difference occurs only when you’re generating URLs (which we’ll cover in the next section): the one-asterisk version URL encodes forward slashes, and the two-asterisk version doesn’t. Typically, the round-trip behavior of the two-asterisk version is what you want.

在将传入请求路由到终端节点时,catch-all 参数的一个星号和两个星号版本的行为相同。仅在生成 URL 时(我们将在下一节中介绍)才会出现差异:一个星号版本的 URL 对正斜杠进行编码,而两个星号的 URL 则不编码。通常,两个星号版本的往返行为是您想要的。

Note For examples and a comparison between the one and two-asterisk catch-all versions, see the documentation at http://mng.bz/rWyX.
注意 有关示例以及 1 个星号和 2 个星号 catch-all 版本之间的比较,请参阅 http://mng.bz/rWyX 中的文档。

You read that last paragraph correctly: mapping URLs to endpoints is only half of the responsibilities of the routing system in ASP.NET Core. It’s also used to generate URLs so that you can reference your endpoints easily from other parts of your application.

您正确地阅读了最后一段:将 URL 映射到终端节点只是 ASP.NET Core 中路由系统职责的一半。它还用于生成 URL,以便您可以轻松地从应用程序的其他部分引用终端节点。

6.4 Generating URLs from route parameters

6.4 从路由参数生成 URL

In this section we’ll look at the other half of routing: generating URLs. You’ll learn how to generate URLs as a string you can use in your code and how to send redirect URLs automatically as a response from your endpoints.

在本节中,我们将了解路由的另一半:生成 URL。您将学习如何将 URL 生成为可在代码中使用的字符串,以及如何自动发送重定向 URL 作为来自终端节点的响应。

One of the benefits and byproducts of using the routing infrastructure in ASP.NET Core is that your URLs can be somewhat fluid. You can change route templates however you like in your application—by renaming /cart to /basket, for example—and won’t get any compilation errors.

在 ASP.NET Core 中使用路由基础设施的好处和副产品之一是您的 URL 可能有些不稳定。您可以在应用程序中根据需要更改路由模板 - 通过将 /cart 重命名为/basket 的 URL,并且不会收到任何编译错误。

Endpoints aren’t isolated, of course; inevitably, you’ll want to include a link to one endpoint in another. Trying to manage these links within your app manually would be a recipe for heartache, broken links, and 404 errors. If your URLs were hardcoded, you’d have to remember to do a find-and-replace operation with every rename!

当然,终端节点不是孤立的;不可避免地,您需要在另一个终端节点中包含指向一个终端节点的链接。尝试在你的应用程序中手动管理这些链接将导致心痛、链接断开和 404 错误。如果您的 URL 是硬编码的,则必须记住在每次重命名时执行查找和替换作!

Luckily, you can use the routing infrastructure to generate appropriate URLs dynamically at runtime instead, freeing you from the burden. Conceptually, this process is almost the exact reverse of the process of mapping a URL to an endpoint, as shown in figure 6.8. In the routing case, the routing middleware takes a URL, matches it to a route template, and splits it into route values. In the URL generation case, the generator takes in the route values and combines them with a route template to build a URL.

幸运的是,您可以使用路由基础设施在运行时动态生成适当的 URL,从而减轻您的负担。从概念上讲,此过程几乎与将 URL 映射到端点的过程完全相反,如图 6.8 所示。在路由情况下,路由中间件采用 URL,将其与路由模板匹配,并将其拆分为路由值。在 URL生成的情况下,生成器会接收路由值并将它们与路由模板组合在一起以构建 URL。

Figure 6.8 A comparison between routing and URL generation. Routing takes in a URL and generates route values, but URL generation uses route values to generate a URL.
图 6.8 路由和 URL 生成之间的比较。路由接收 URL 并生成route 值,但 URL 生成使用路由值来生成 URL。

You can use the LinkGenerator class to generate URLs for your minimal APIs. You can use it in any part of your application, so you can use it in middleware and any other services too. LinkGenerator has various methods for generating URLs, such as GetPathByPage and GetPathByAction, which are used specifically for routing to Razor Pages and MVC actions, so we’ll look at those in chapter 14. We’re interested in the methods related to named routes.

您可以使用 LinkGenerator 类为您的最小 API 生成 URL。您可以在应用程序的任何部分使用它,因此您也可以在中间件和任何其他服务中使用它。LinkGenerator 具有各种生成 URL 的方法,例如 GetPathByPage 和 GetPathByAction,它们专门用于路由到 Razor Pages 和 MVC作,因此我们将在第 14 章中介绍这些方法。我们对与命名路由相关的方法感兴趣。

6.4.1 Generating URLs for a minimal API endpoint with LinkGenerator

6.4.1 使用 LinkGenerator 为最小 API 端点生成 URL

You’ll need to generate URLs in various places in your application, and one common location is your minimal API endpoints. The following listing shows how you could generate a link to one endpoint from another by annotating the target endpoint with a name and using the LinkGenerator class.

您需要在应用程序的不同位置生成 URL,一个常见位置是最小 API 终端节点。下面的清单显示了如何通过使用名称注释目标终端节点并使用 LinkGenerator 类来生成从另一个终端节点到另一个终端节点的链接 。

Listing 6.2 Generating a URL LinkGenerator and a named endpoint
清单 6.2 生成一个 URL LinkGenerator 和一个命名的端点

app.MapGet("/product/{name}", (string name) => $"The product is {name}") ❶
    .WithName("product"); ❷

app.MapGet("/links", (LinkGenerator links) => ❸
{
    string link = links.GetPathByName("product", ❹
        new { name = "big-widget"}); ❹
    return $"View the product at {link}"; ❺
});

❶ The endpoint echoes the name it receives in the route template.
端点回显它在路由模板中接收的名称。

❷ Gives the endpoint a name by adding metadata to it
通过向终端节点添加元数据来为终端节点命名

❸ References the LinkGenerator class in the endpoint handler
在端点处理程序中引用 LinkGenerator 类

❹ Creates a link using the route name “product” and provides a value for the route parameter
使用路由名称 “product” 创建链接,并为路由参数提供值

❺ Returns the value “View the product at /product/big-widget”
返回值 “View the product at /product/big-widget”

The WithName() method adds metadata to your endpoints so that they can be referenced by other parts of your application. In this case, we’re adding a name to the endpoint so we can refer to it later. You’ll learn more about metadata in chapter 11.

WithName() 方法将元数据添加到端点,以便应用程序的其他部分可以引用它们。在本例中,我们将向终端节点添加一个名称,以便稍后可以引用它。您将在第 11 章中了解有关元数据的更多信息。

Note Endpoint names are case-sensitive (unlike the route templates themselves) and must be globally unique. Duplicate names cause exceptions at runtime.
注意: 终端节点名称区分大小写(与路由模板本身不同),并且必须全局唯一。重复的名称会导致运行时出现异常

The LinkGenerator is a service available anywhere in ASP.NET Core. You can access it from your endpoints by including it as a parameter in the handler.

LinkGenerator 是一项在 ASP.NET Core 中的任何位置都可用的服务。您可以通过将其作为参数包含在处理程序中,从终端节点访问它。

Note You can reference the LinkGenerator in your handler because it’s registered with the dependency injection container automatically. You’ll learn about dependency injection in chapters 8 and 9.
注意 您可以在处理程序中引用 LinkGenerator,因为它会自动注册到依赖项注入容器中。您将在第 8 章和第 9 章中了解依赖关系注入。

The GetPathByName() method takes the name of a route and, optionally, route data. The route data is packaged as key-value pairs into a single C# anonymous object. If you need to pass more than one route value, you can add more properties to the anonymous object. Then the helper will generate a path based on the referenced endpoint’s route template.

GetPathByName() 方法采用路由的名称,也可以选择获取路由数据。路由数据作为键值对打包到单个 C# 匿名对象中。如果需要传递多个路由值,可以添加更多properties 添加到匿名对象。然后,帮助程序将根据引用的端点的路由模板生成路径。

Listing 6.2 shows how to generate a path. But you can also generate a complete URL by using the GetUriByName() method and providing values for the host and scheme, as in this example:

清单 6.2 展示了如何生成路径。但是,您也可以通过使用 GetUriByName() 方法并为 host 和 scheme 提供值来生成完整的 URL,如下例所示:

links.GetUriByName("product", new { Name = "super-fancy-widget"},
    "https", new HostString("localhost"));

Also, some methods available on LinkGenerator take an HttpContext. These methods are often easier to use in an endpoint handler, as they extract ambient values such as the scheme and hostname from the incoming request and reuse them for URL generation.

此外,LinkGenerator 上可用的一些方法采用 HttpContext。这些方法通常更容易在端点处理程序中使用,因为它们从传入请求中提取环境值(如 scheme 和 hostname),并将它们重新用于 URL 生成。

Warning Be careful when using the GetUriByName method. It’s possible to expose vulnerabilities in your app if you use unvalidated host values. For more information on host filtering and why it’s important, see this post: http://mng.bz/V1d5.
警告 使用 GetUriByName 方法时要小心。如果您使用未经验证的 host 值,则可能会暴露应用程序中的漏洞。有关主机筛选及其重要性的更多信息,请参阅此博文:http://mng.bz/V1d5

In listing 6.2, as well as providing the route name, I passed in an anonymous object to GetPathByName:
在清单 6.2 中,除了提供路由名称外,我还向 GetPathByName 传入了一个匿名对象:

string link = links.GetPathByName("product", new { name = "big-widget"});

This object provides additional route values when generating the URL, in this case setting the name parameter to "big-widget":

此对象在生成 URL 时提供额外的路由值,在本例中,将 name 参数设置为 “big- widget”。

If a selected route explicitly includes the defined route value in its definition, such as in the "/product/{name}" route template, the route value will be used in the URL path, resulting in /product/big-widget. If a route doesn’t contain the route value explicitly, as in the "/product" template, the route value is appended to the query string as additional data. as in /product?name=big-widget.

如果所选路由在其定义中显式包含定义的路由值,例如在“/product/{name}”路由模板中,则路由值将在 URL 路径中使用,从而生成 /product/big-widget。如果路由没有显式包含路由值(如“/product”模板中所示),则路由值将作为附加数据附加到查询字符串中。如 /product?name=big-widget 中所示。

6.4.2 Generating URLs with IResults

6.4.2 使用 IResults 生成 URL

Generating URLs that link to other endpoints is common when you’re creating a REST API, for example. But you don’t always need to display URLs. Sometimes, you want to redirect a user to a URL automatically. In that situation you can use Results.RedirectToRoute() to handle the URL generation instead.

例如,在创建 REST API 时,生成链接到其他终端节点的 URL 很常见。但您并不总是需要显示 URL。有时,您希望自动将用户重定向到 URL。在这种情况下,您可以使用 Results.RedirectToRoute() 来处理 URL 生成。

Note Redirects are more common with server-rendered applications such as Razor Pages, but they’re perfectly valid for API applications too.
注意 重定向在服务器呈现的应用程序(如 Razor Pages)中更为常见,但它们对 API 应用程序也完全有效。

Listing 6.3 shows how you can return a response from an endpoint that automatically redirects a user to a different named endpoint. The RedirectToRoute() method takes the name of the endpoint and any required route parameters, and generates a URL in a similar way to LinkGenerator. The minimal API framework automatically sends the generated URL as the response, so you never see the URL in your code. Then the user’s browser reads the URL from the response and automatically redirects to the new page.

清单 6.3 展示了如何从端点返回响应,该响应会自动将用户重定向到不同的命名端点。RedirectToRoute() 方法采用端点的名称和任何必需的路由参数,并以与 LinkGenerator 类似的方式生成 URL。最小 API 框架会自动将生成的 URL 作为响应发送,因此您永远不会在代码中看到 URL。然后,用户的浏览器读取 URL并自动重定向到新页面。

Listing 6.3 Generating a redirect URL using Results.RedirectToRoute()
清单 6.3 使用 Result 生成重定向 URLs.RedirectToRoute()

app.MapGet("/test", () => "Hello world!")
    .WithName("hello"); ❶

app.MapGet("/redirect-me",
    () => Results.RedirectToRoute("hello")) ❷

❶ Annotates the route with the name “hello”
使用名称 “hello” 注释路由

❷ Generates a response that sends a redirect to the “hello” endpoint
生成一个响应,将重定向发送到 “hello” 端点

By default, RedirectToRoute() generates a 302 Found response and includes the generated URL in the Location response header. You can control the status code used by setting the optional parameters preserveMethod and permanent as follows:
默认情况下,RedirectToRoute() 会生成 302 Found 响应,并将生成的 URL 包含在 Location 响应标头中。您可以通过设置可选参数 preserveMethod 和 permanent 来控制使用的状态码,如下所示:

  • permanent=false, preserveMethod=false—302 Found

  • permanent=true, preserveMethod=false—301 Moved Permanently

  • permanent=false, preserveMethod=true—307 Temporary Redirect

  • permanent=true, preserveMethod=true—308 Permanent Redirect

Note Each of the redirect status codes has a slightly different semantic meaning, though in practice, many sites simply use 302. Be careful with the permanent move status codes; they’ll cause browsers to never call the original URL, always favoring the redirect location. For a good explanation of these codes (and the useful 303 See Other status code), see the Mozilla documentation at http://mng.bz/x4GB.
注意 每个重定向状态代码的语义含义略有不同,但实际上,许多站点只需使用 302。小心永久移动状态代码;它们将导致浏览器从不调用原始 URL,始终优先使用重定向位置。有关这些代码(以及有用的 303 See Other 状态代码)的良好解释,请参阅 Mozilla 文档 http://mng.bz/x4GB

As well as redirecting to a specific endpoint, you can redirect to an arbitrary URL by using the Results.Redirect() method. This method works in the same way as RedirectToRoute() but takes a URL instead of a route name and can be useful for redirecting to external URLs.

除了重定向到特定端点外,您还可以使用 Results.Redirect() 方法重定向到任意 URL。此方法的工作方式与 RedirectToRoute() 相同,但采用 URL 而不是路由名称,可用于重定向到外部 URL。

Whether you’re generating URLs by using LinkGenerator or RedirectToRoute(), you need to be careful in these route generation methods. Make sure to provide the correct endpoint name and any necessary route parameters. If you get something wrong—if you have a typo in your endpoint name or forget to include a required route parameter, for example—the URL generated will be null. Sometimes it’s worth checking the generated URL for null explicitly to make sure that there are no problems.

无论您是使用 LinkGenerator 还是 RedirectToRoute() 生成 URL,都需要小心使用这些路由生成方法。确保提供正确的终端节点名称和任何必要的路由参数。如果出现错误(例如,如果终端节点名称中有拼写错误或忘记包含必需的路由参数),生成的 URL 将为 null。有时,值得显式检查生成的 URL 是否为 null,以确保没有问题。

6.4.3 Controlling your generated URLs with RouteOptions

6.4.3 使用 RouteOptions 控制生成的 URL

Your endpoint routes are the public surface of your APIs, so you may well have opinions on how they should look. By default, LinkGenerator does its best to generate routes the same way you define them; if you define an endpoint with the route template /MyRoute, LinkGenerator generates the path /MyRoute. But what if that path isn’t what you want? What if you’d rather have LinkGenerator produce prettier paths, such as /myroute or /myroute/? In this section you’ll learn how to configure URL generation both globally and on a case-by-case basis.

您的终端节点路由是 API 的公共表面,因此您可能对它们的外观有自己的想法。默认情况下,LinkGenerator 会尽最大努力以与定义路由相同的方式生成路由;如果您定义了一个端点使用路由模板 /MyRoute,LinkGenerator 会生成路径 /MyRoute。但是,如果这条路不是您想要的呢?如果您希望 LinkGenerator 生成更漂亮的路径,例如 /myroute 或 /myroute/,该怎么办?在本节中,您将学习如何全局和逐个配置 URL 生成。

Note Whether to add a trailing slash to your URLs is largely a question of taste, but the choice has some implications in terms of both usability and search results. I typically choose to add trailing slashes for Razor Pages applications but not for APIs. For details, see http://mng.bz/Ao1W.
注意 是否向 URL 添加尾部斜杠在很大程度上是一个品味问题,但这种选择在可用性和搜索结果方面都有一些影响。我通常选择为 Razor Pages 应用程序添加尾部斜杠,但不为 API 添加。有关详细信息,请参阅 http://mng.bz/Ao1W

When ASP.NET Core matches an incoming URL against your route templates by using routing, it uses a case-insensitive comparison, as you saw in chapter 5. So if you have a route template /MyRoute, requests to /myroute, /MYROUTE, and even /myROUTE match. But when generating URLs, LinkGenerator needs to choose a single version to use. By default, it uses the same casing that you defined in your route templates. So if you write

当 ASP.NET Core 使用路由将传入 URL 与路由模板匹配时,它会使用不区分大小写的比较,如第 5 章所示。因此,如果您有路由模板 /MyRoute,则对 /myroute、/MYROUTE 甚至 /myROUTE 的请求都会匹配。但是在生成 URL 时,LinkGenerator 需要选择一个版本来使用。默认情况下,它使用您在路由模板中定义的相同大小写。所以如果你写

app.MapGet("/MyRoute", () => "Hello world!").WithName("route1");

LinkGenerator.GetPathByName("route1") returns/MyRoute.

LinkGenerator.GetPathByName(“route1”) 返回/MyRoute 的 Route。

Although that’s a good default, you’d probably prefer that all the links generated by your app be consistent. I like all my links to be lowercase, regardless of whether I accidentally failed to make my route template lowercase.

尽管这是一个很好的默认值,但你可能希望你的应用程序生成的所有链接都是一致的。我喜欢我的所有链接都是小写的,无论我是否意外地未能将我的路由模板设置为小写。

You can control the route generation rules by using RouteOptions. You configure the RouteOptions for your app using the Configure extension method on WebApplicationBuilder.Services, which updates the RouteOptions instance for the app using the configuration system.

您可以使用 RouteOptions 控制路由生成规则。您可以使用 WebApplicationBuilder.Services 上的 Configure 扩展方法为应用程序配置 RouteOptions,该方法使用配置系统更新应用程序的 RouteOptions 实例。

Note You’ll learn all about the configuration system and the Configure method in chapter 10.
注意 您将在第 10 章中了解有关配置系统和Configure方法的所有信息。

RouteOptions contains several configuration options, as shown in listing 6.4. These settings control whether the URLs your app generates are forced to be lowercase, whether the query string should also be lowercase, and whether a trailing slash (/) should be appended to the final URLs. In the listing, I set the URL to be lowercased, for the trailing slash to be added, and for the query string to remain unchanged.

RouteOptions 包含几个配置选项,如清单 6.4 所示。这些设置控制是否强制应用程序生成的 URL 为小写,查询字符串是否也应为小写,以及是否应将尾部斜杠 (/) 附加到最终 URL。在清单中,我将 URL 设置为小写,以便添加尾部斜杠,并使查询字符串保持不变。

Note In listing 6.4 the whole path is lowercased, including any route parameter segments such as {name}. Only the query string retains its original casing.
注意 在清单 6.4 中,整个路径都是小写的,包括任何路由参数段,比如 {name}。只有查询字符串保留其原始大小写。

Listing 6.4 Configuring link generation using RouteOptions
清单 6.4 使用路由选项

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<RouteOptions>(o => ❶
{ ❶
    o.LowercaseUrls = true; ❶
    o.AppendTrailingSlash = true; ❶
    o.LowercaseQueryStrings = false; ❷
});

WebApplication app = builder.Build();

app.MapGet("/HealthCheck", () => Results.Ok()).WithName("healthcheck");
app.MapGet("/{name}", (string name) => name).WithName("product");

app.MapGet("/", (LinkGenerator links) =>
new []
{
    links.GetPathByName("healthcheck"), ❸
    links.GetPathByName("product", ❹
        new { Name = "Big-Widget", Q = "Test"}) ❹
});

app.Run();

❶ Configures the RouteOptions used for link generation
配置用于链接生成的 RouteOptions

❷ All the settings default to false.
所有设置都默认为 false。

❸ Returns /healthcheck/
返回 /healthcheck/

❹ Returns /big-widget/?Q=Test
返回 /big-widget/?Q=Test

Whatever default options you choose, you should try to use them throughout your whole app, but in some cases that may not be possible. You might have a legacy API that you need to emulate, for example, and can’t use lowercase URLs. In these cases, you can override the defaults by passing an optional LinkOptions parameter to LinkGenerator methods. The values you set in LinkOptions override the default values set in RouteOptions. Generating a link for the app in listing 6.4 by using

无论您选择什么默认选项,您都应该尝试在整个应用程序中使用它们,但在某些情况下,这可能无法实现。例如,您可能有一个需要模拟的旧 API,并且不能使用小写 URL。在这些情况下,您可以通过将可选的 LinkOptions 参数传递给 LinkGenerator 方法来覆盖默认值。在 LinkOptions 中设置的值将覆盖在 RouteOptions 中设置的默认值。

links.GetPathByName("healthcheck",
options: new LinkOptions
{
    LowercaseUrls = false,
    AppendTrailingSlash = false,
});

would return the value /HealthCheck. Without the LinkOptions parameter, GetPathByName would return /healthcheck/.

将返回值 /HealthCheck。如果没有LinkOptions 参数,GetPathByName 将返回
/healthcheck/ 中。

Congratulations—you’ve made it all the way through this detailed discussion of routing! Routing is one of those topics that people often get stuck on when they come to building an application, which can be frustrating. We’ll revisit routing when we look at Razor Pages in chapter 14 and web API controllers in chapter 20, but rest assured that this chapter has covered all the tricky details!

恭喜 — 您已经完成了这个关于路由的详细讨论!路由是人们在构建应用程序时经常陷入困境的话题之一,这可能会令人沮丧。当我们在第 14 章中查看 Razor 页面,在第 20 章中查看 Web API 控制器时,我们将重新讨论路由,但请放心,本章已经涵盖了所有棘手的细节!

In chapter 7 we’ll dive into model binding. You’ll see how the route values generated during routing are bound to your endpoint handler parameters and, perhaps more important, how to validate the values you’re provided.

在第 7 章中,我们将深入探讨模型绑定。您将看到路由期间生成的路由值如何绑定到终端节点处理程序参数,也许更重要的是,如何验证您提供的值。

6.5 Summary

6.5 总结

  • Routing is the process of mapping an incoming request URL to an endpoint that executes to generate a response. Routing provides flexibility to your API implementations, enabling you to map multiple URLs to a single endpoint, for example.
    路由是将传入请求 URL 映射到终端节点的过程,该终端节点执行以生成响应。路由为您的 API 实施提供了灵活性,例如,使您能够将多个 URL 映射到单个终端节点。

  • ASP.NET Core uses two pieces of middleware for routing. The EndpointRoutingMiddleware and the EndpointMiddleware. WebApplication adds both pieces of middleware to your pipeline by default, so typically, you don’t add them to your application manually.
    ASP.NET Core 使用两个中间件进行路由。EndpointRoutingMiddleware 和 EndpointMiddleware.默认情况下,WebApplication 会将这两个中间件添加到您的管道中,因此通常不会手动将它们添加到您的应用程序中。

  • The EndpointRoutingMiddleware selects which endpoint should be executed by using routing to match the request URL. The EndpointMiddleware executes the endpoint. Having two separate middleware components means that middleware placed between them can react based on the endpoint that will execute when it reaches the end of the pipeline.
    EndpointRoutingMiddleware 通过使用路由来匹配请求 URL,从而选择应该执行的端点。EndpointMiddleware 执行 endpoint。拥有两个单独的中间件组件意味着放置在它们之间的中间件可以根据到达管道末尾时将执行的端点做出反应。

  • Route templates define the structure of known URLs in your application. They’re strings with placeholders for variables that can contain optional values and map to endpoint handlers. You should think about your routes carefully, as they’re the public surface of your application.
    路由模板定义应用程序中已知 URL 的结构。它们是带有变量占位符的字符串,这些变量可以包含可选值并映射到终端节点处理程序。您应该仔细考虑路由,因为它们是应用程序的公共表面。

  • Route parameters are variable values extracted from a request’s URL. You can use route parameters to map multiple URLs to the same endpoint and to extract the variable value from the URL automatically.
    路由参数是从请求的 URL 中提取的变量值。您可以使用路由参数将多个 URL 映射到同一终端节点,并自动从 URL 中提取变量值。

  • Route parameters can be optional and can use default values when a value is missing. You should use optional and default parameters sparingly, as they can make your APIs harder to understand, but they can be useful in some cases. Optional parameters must be the last segment of a route.
    路由参数可以是可选的,并且在缺少值时可以使用默认值。您应该谨慎使用可选参数和默认参数,因为它们会使您的 API 更难理解,但在某些情况下它们可能很有用。可选参数必须是路由的最后一段。

  • Route parameters can have constraints that restrict the possible values allowed. If a route parameter doesn’t match its constraints, the route isn’t considered to be a match. This approach can help you disambiguate between two similar routes, but you shouldn’t use constraints for validation.
    路由参数可以具有限制允许的可能值的约束。如果路由参数与其约束条件不匹配,则不会将该路由视为匹配项。此方法可以帮助您消除两个相似路由之间的歧义,但您不应使用 constraints 进行验证。

  • Use a catch-all parameter to capture the remainder of a URL into a route value. Unlike standard route parameters, catch-all parameters can include slashes (/) in the captured values.
    使用 catch-all 参数将 URL 的其余部分捕获到路由值中。与标准路由参数不同,catch-all 参数可以在捕获的值中包含斜杠 (/)。

  • You can use the routing infrastructure to generate URLs for your application. This approach ensures that all your links remain correct if you change your endpoint’s route templates.
    您可以使用路由基础设施为您的应用程序生成 URL。此方法可确保在更改终端节点的路由模板时所有链接都保持正确。

  • The LinkGenerator can be used to generate URLs from minimal API endpoints. Provide the name of the endpoint to link to and any required route values to generate an appropriate URL.
    LinkGenerator 可用于从最小的 API 端点生成 URL。提供要链接到的端点的名称以及生成相应 URL 所需的任何路由值。

  • You can use the RedirectToRoute method to generate URLs while also generating a redirect response. This approach is useful when you don’t need to reference the URL in code.
    您可以使用 RedirectToRoute 方法生成 URL,同时生成重定向响应。当您不需要在代码中引用 URL 时,此方法非常有用。

  • By default, URLs are generated using the same casing as the route template and any supplied route parameters. Instead, you can force lowercase URLs, lowercase query strings, and trailing slashes by customizing RouteOptions, calling builder.Services.Configure().
    默认情况下,URL 是使用与路由模板相同的大小写和提供的任何路由参数生成的。相反,您可以通过自定义 RouteOptions、调用 builder 来强制使用小写 URL、小写查询字符串和尾部斜杠。Services.Configure() 的 RouteOptio 中。

  • You can change the settings for a single URL generation by passing a LinkOptions object to the LinkGenerator methods. These methods can be useful when you need to differ from the defaults for a single endpoint, such as when you’re trying to match an existing legacy route.
    您可以通过将 LinkOptions 对象传递给 LinkGenerator 方法来更改单个 URL 生成的设置。当您需要与单个终端节点的默认值不同时,例如当您尝试匹配现有的旧路由时,这些方法可能很有用。

Leave a Reply

Your email address will not be published. Required fields are marked *