三、路由

本章讨论路由,即 ASP.NET Core 将用户请求转换为 MVC 控制器和操作的过程。这可能是一个复杂的过程,因为请求中的细微更改可能导致调用不同的端点(控制器/操作对)。需要考虑几个方面:协议(HTTP 或 HTTPS)、发出请求的用户是否经过身份验证、HTTP 谓词、请求路径、查询字符串以及路径和查询字符串参数值的实际类型。

路由还定义了当一条路由匹配时会发生什么情况,即 catch-all 路由,它可以用于需要定义自定义路由约束的复杂情况。

ASP.NET Core 提供了配置路由的不同方法,可分为基于约定的配置和显式配置。

在本章结束时,您将能够定义路由表并以 ASP.NET Core 为 MVC 应用提供的所有不同方式应用路由配置。

本章的目标如下所示:

  • 理解端点路由
  • 配置路由
  • 理解路由表
  • 使用管线模板
  • 匹配路由参数
  • 使用动态路由
  • 基于属性的学习路径选择
  • 从属性强制主机选择
  • 设置路由默认值
  • 路由到内联处理程序
  • 应用路线约束

  • 使用路由数据令牌

  • 路由到区域
  • 使用属性进行路由
  • 使用路由进行错误处理

技术要求

为了实现本章中介绍的示例,您需要.NET Core 3 SDK 和文本编辑器。当然,VisualStudio2019(任何版本)满足所有要求,但您也可以使用 VisualStudio 代码。

源代码可以在这里从 GitHub 检索:https://github.com/PacktPublishing/Modern-Web-Development-with-ASP.NET-Core-3-Second-Edition

开始

在旧的 web 应用时代,事情很简单,如果你想要一个页面,你必须有一个物理页面。然而,事情已经发生了变化,ASP.NET Core 现在是一个 MVC 框架。这是什么意思?嗯,在 MVC 中,没有物理页面(尽管这并不完全正确);相反,它使用路由将请求定向到路由处理程序。MVC 中最常见的路由处理程序是控制器动作。在本章之后,您将学习如何使用路由访问控制器操作。

请求只是一些相对 URL,例如:

/Search/Mastering%ASP.NET%Core
/Admin/Books
/Book/1

这会产生更可读的 URL,并且对谷歌等搜索引擎也有好处。为搜索引擎优化网站(包括其公共 URL)的主题称为搜索引擎优化SEO

当 ASP.NET Core 收到请求时,可能会发生以下两种情况之一:

  • 存在与请求匹配的物理文件。
  • 有一个接受请求的路由。

为了让 ASP.NET Core 服务物理文件,需要对此进行配置,我们使用Configure中的UseStaticFiles扩展方法,将静态文件、处理中间件添加到管道中;对UseStaticFiles的调用包含在 ASP.NET Core web 应用的 Visual Studio 模板中。如果我们不启用静态文件服务,或者如果不存在文件,则需要由路由处理程序处理请求。MVC 中最常见的路由处理程序是控制器动作

控制器是一个公开动作的类,该动作知道如何处理请求。动作是一种可以获取参数并返回动作结果的方法。我们使用路由表请求引导至控制器动作

我们可以使用两个 API 来注册路由:

  • Fluent API(代码)
  • 属性

在以前的版本中,我们必须显式地添加对路由属性的支持,但它们现在是.NETCore 的一流公民。让我们从路由表的概念开始,来看看它们。

端点路由

端点路由是在 ASP.NET Core 2.2 中引入的,从 3.0 开始现在是默认机制。其主要优点是,它支持许多不同的机制,尽管这些机制利用了路由和中间件,但它们与 MVC、Razor Pages、gRPC、Blazor、SignalR 等机制截然不同。您仍然可以在ConfigureServices中注册所需的服务,然后使用Configure方法中的扩展方法将中间件添加到管道中。端点路由使框架更加灵活,因为它将路由匹配和解析与端点调度分离,而端点调度过去是 MVC 功能的一部分。

有三个必需的方法调用:

  • AddRouting:我们注册所需的服务,并可选择配置其部分选项(ConfigureServices
  • UseRouting:实际添加路由中间件的地方(Configure;这会将请求匹配到端点
  • UseEndpoints:我们将端点配置为可用(Configure;这将执行匹配的端点

现在,在 Razor 视图(或页面)上,如果您想动态生成指向可寻址资源的超链接,不管它是什么(操作方法、Razor 页面或其他任何内容),您可以使用Url.RouteUrl重载方法:

<!-- a Razor page -->
<a href="@Url.RouteUrl(new { page = "Admin" })">Admin</a>

<!-- an action method on a controller -->
<a href="@Url.RouteUrl(new { action = "Contact", controller = "Home" })">Contact</a>

如果出于任何原因,您需要在中间件组件上生成链接,那么您可以注入一个LinkGenerator类。它公开了允许您检索多种不同类型 URL 信息的离散方法:

  • Get{Path,Uri}ByAction:返回控制器动作方法的完整路径(URL)
  • Get{Path,Uri}ByAddress:返回基本路径的完整路径(URL)和指定的路由值
  • Get{Path,Uri}ByName:返回端点名称和指定路由值的完整路径(URL)
  • Get{Path,Uri}ByPage:返回页面名称的完整路径(URL)
  • Get{Path,Uri}ByRouteValues:返回指定端点路由的完整路径(URL)和路由值

*Path版本与*Uri版本的区别在于前者返回绝对路径(如/controller/action),后者返回协议限定的完整路径(如http://host:8080/controller/action)。

如果您需要获取当前端点,有一个新的扩展方法,HttpContext之上的GetEndpoint,您可以使用它:

var endpoint = this.HttpContext.GetEndpoint();
var displayName = endpoint.DisplayName;
var metadata = endpoint.Metadata.ToArray();

除了DisplayNameMetadata集合之外,端点没有提供太多的功能。DisplayName是操作方法的完全限定名,包括类和程序集,除非设置了显示名,Metadata集合包含应用于当前操作方法的所有元数据,包括属性和约定。

您可以使用Get<T>通用方法请求特定的元数据接口;元数据特定接口如下所示:

  • IDataTokensMetadata:这用于获取数据令牌的访问权限(有关更多信息,请参阅下一节)。
  • IEndpointNameMetadata:用于获取可选的端点名称。
  • IHostMetadata:用于获取端点的主机限制。
  • IHttpMethodMetadata:用于获取端点的方法限制。
  • IRouteNameMetadata:获取定义路由表时指定的路由名称。
  • ISuppressLinkGenerationMetadata:如果该接口的SuppressLinkGeneration属性设置为true,则在使用LinkGenerator类生成链接时不考虑该端点。
  • ISuppressMatchingMetadata:如果该接口的SuppressMatching属性为true,则不考虑该端点的 URL 进行 URL 匹配。

例如,假设我们要获取当前路由名称:

var routeName = HttpContext.GetEndpoint().Metadata.Get<IRouteNameMetadata>();

Keep in mind that Get<> returns the first occurrence of any registered metadata that implements the passed type.

我们可以添加自定义元数据,并在构建时在端点上设置显示名称,如下所示:

app.UseEndpoints(endpoints =>
{
    endpoints
        .MapControllerRoute
        (
            name: "Default",                        
            pattern: "{controller=Home}/{action=Index}/{id?}",
        )
        .WithDisplayName("Foo")
        .WithMetadata(new MyMetadata1(), new MyMetadata2());
});

此示例显示了一个典型的控制器路由,该路由具有显示名称集(WithDisplayName)和自定义元数据(MyMetadata1MyMetadata2);这些类仅用于演示目的。

在了解了端点路由的工作原理之后,现在让我们看看如何配置路由表。

路由配置

我们可以为路由生成配置几个选项,所有这些选项都是通过AddRouting扩展方法在服务定义上配置的:

services.AddRouting(options =>
{
    options.LowercaseUrls = true;
    options.AppendTrailingSlash = true;
    options.ConstraintMap.Add("evenint", typeof(EvenIntRouteConstraint));
});

RouteOptions类支持以下属性:

  • AppendTrailingSlash:确定是否应在所有生成的 URL 中追加尾随斜杠(/);默认值为false(表示不应该)
  • LowercaseUrls:确定生成的 URL 是否为小写;默认为false
  • ConstraintMap:确定约束映射的位置;当我们讨论路由约束时,将对此进行更多介绍

但是路由配置并没有到此结束,下一节实际上是最重要的一节:创建路由表

创建路由表

第一章中,从 ASP.NET Core开始,我们讨论了 OWIN 管道,解释了我们使用中间件构建这个管道。事实证明,有一个 MVC 中间件负责解释请求并将其转换为控制器操作。为此,我们需要一个路由表

只有一个路由表,如本例中默认 Visual Studio 模板所示:

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

我们在这里看到了什么?IApplicationBuilderUseEndpoints扩展方法有一个参数是IEndpointRouteBuilder的一个实例,我们可以在其中添加路由。路线基本上包括以下组成部分:

  • 名称(default
  • 模板模式({controller=Home}/{action=Index}/{id?}
  • 每个路由参数的可选默认值(HomeIndex

此外,我们还有一些默认设置:

  • 如果没有为 URL 提供控制器,则使用Home作为默认值。
  • 如果没有提供任何操作,对于任何控制器,则使用Index作为默认值。

本例中未显示一些可选参数:

  • 可选路由参数约束
  • 可选数据令牌
  • 路径处理程序
  • 路由约束解析器

我们将在本章中介绍所有这些内容。这是默认的 MVC 模板,此调用与以下调用相同:

endpoints.MapDefaultControllerRoute();

至于实际的路线,这个名字只是对我们有意义的东西,它没有以任何方式被使用。更有趣的是模板,稍后我们将看到它。

作为记录,如果希望仅映射控制器,则应包括以下调用:

endpoints.MapControllers();

这不包括对 Razor 页面的支持;为此,您需要:

endpoints.MapRazorPages();

话虽如此,我们可以定义多条路线:

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

    endpoints.MapControllerRoute(
        name: "admin",
        pattern: "admin/{controller}/{action=Index}");
});

在本例中,我们有两个路由:第二个路由映射一个以admin开头的请求,它需要一个显式的控制器名称,因为它没有默认名称。该操作确实有一个(Index)。

Routes are searched in order; the first one that matches the request is used.

这里我们看到了如何将请求映射到确实存在的资源。下一节解释了当请求的资源不存在时该怎么做!

回退端点

要定义回退路由(如果没有其他路由匹配,则匹配的路由),我们可以回退到页面(任何相对 URL),包括或不包括区域:

endpoints.MapFallbackToPage("/Admin");
endpoints.MapFallbackToAreaPage("/", "Admin");

或者,我们可以有一个包含以下文件的回退页面:

endpoints.MapFallbackToFile("index.html");

我们可以有控制器动作,带或不带区域:

endpoints.MapFallbackToController("Index", "Home");
endpoints.MapFallbackToAreaController("Index", "Home", "Admin");

或者,最后,我们可以有一个委托,它接收作为其唯一参数的请求上下文(HttpContext,您可以从中做出决定:

endpoints.MapFallback(ctx =>
{
    ctx.Response.Redirect("/Login");
    return Task.CompletedTask;
});

这些MapFallback*扩展方法中的每一个都有一个重载,其第一个参数类型为string,称为pattern。如果使用此重载,pattern参数可用于将回退限制为与此模式匹配的请求。例如,请参见:

endpoints.MapFallbackToPage("/spa/{**path:nonfile}", "/Missing");

回退路由应该是端点路由表上的最后一个条目。

现在让我们看看如何通过在路由模板中使用特殊令牌来增强路由。

使用管线模板

模板是相对 URL,因此不能以斜杠(/开头)。在它中,您定义了站点的结构,或者更准确地说,定义了您打算提供的结构。由于 ASP.NET Core 是一个 MVC 框架,模板应该描述如何将请求映射到控制器中的操作方法。以下是模板:

{controller=Home}/{action=Index}/{id?}

它由斜线分隔的部分组成,每个部分都有一些标记(在花括号内)。

另一个例子是:

sample/page

这里不清楚我们想要什么,因为没有提到controlleraction。但是,这是一个完全有效的模板,所需的信息需要来自其他地方。

模板可以包含以下元素:

  • 字母数字文字
  • 大括号({}中的字符串片段,它们是命名的标记,可以映射到操作方法参数
  • 如果 URL 中未提供令牌,则具有相等分配的命名令牌(=)具有默认值;让一个带有默认值的令牌后跟一个不带默认值的必需令牌是没有意义的
  • 以问号(?结尾的标记,可选,表示不需要;可选标记后面不能跟有必需的标记
  • 以星星(*开头的代币,完全是可选的,可以匹配任何东西;它们必须是模板中的最后一个元素

令牌始终是字母数字字符段,可以用分隔符符号(/?-()等)分隔。但是,您不需要使用分隔符;请注意,actionid标记之间没有斜杠,以下内容完全有效:

{controller=Admin}/{action=Process}{id}

下面是另一个稍微复杂一点的示例,其中包括添加一个 catch-all 标记querystring

{controller=Admin}/{action=Process}/{?id}?{*querystring}

此模板将与以下 URL 匹配:

| URL | 参数 | | / | 控制器:Admin行动:Process编号:不适用查询字符串:不适用 | | /Account | 控制器:Account行动:Process编号:不适用查询字符串:不适用 | | /Admin/Process | 控制器:Admin行动:Process编号:不适用查询字符串:不适用 | | /Admin/Process/1212 | 控制器:Admin行动:Processid:1212 | | /Admin/Process/1212?force=true | 控制器:Admin行动:Processid:1212查询字符串:force=true |

另一个非常有效的例子是:

api/{controller=Search}/{action=Query}?term={term}

这将符合以下条件:

api?term=.net+core
api/Search?term=java
api/Search/Query?term=php

请注意,无论大小写如何,任何文本都必须以与 URL 中所示完全相同的方式呈现。

现在,让我们看看模板中指定的路由参数是如何匹配的。

匹配路由参数

记住模板需要有一个controller令牌和一个action令牌;这些是唯一必需的标记,具有特殊意义。控制器将匹配控制器类,操作将匹配其公共方法之一。任何其他模板参数都将与 action 方法中同名的参数匹配。例如,采用具有以下模板的路线:

{controller=Search}/{action=Query}/{phrase}

该路径将映射到名为SearchController的类中的Query方法:

public IActionResult Query(string phrase) { ... }

By convention, the name of the controller in a template does not take the Controller suffix.

如果路由令牌是可选的,则它必须映射到具有默认值的参数:

{controller=Account}/{action=List}/{page?}

匹配方法将具有以下签名:

public IActionResult List(int page = 0)

请注意,page参数是一个int实例,其默认值为0。例如,这可以用于分页,其中默认页面是第一个页面(从零开始)。这与拥有默认值为0的令牌并将其映射到没有默认值的参数相同。

到目前为止,我们只看到了如何映射字符串或基本类型的简单值;我们将很快看到如何使用其他类型。

我们已经提到,action参数是必需的,但是,尽管在某种程度上这是正确的,它的值可能会被跳过。在这种情况下,ASP.NET Core 将使用 HTTP 操作头中的值,例如GETPOSTPUTDELETE等等。这在 web API 的情况下特别有用,并且通常非常直观。因此,例如,采用具有以下模板的路线:

api/{controller}/{id}

假设其有以下要求:

GET /api/Values/12

它可以映射到一个名为ValuesController的控制器中的如下方法:

public IActionResult Get(int id) { ... }

所以,我们刚刚了解了如何将模板参数从模板匹配到控制器类的方法。现在我们将学习动态路由,其中映射不是预定义的。

使用动态路由

到目前为止,我们已经看到了静态地将路由模板映射到控制器操作的路由表,但还有另一种:动态路由。在这种情况下,我们仍然使用路由模板,但问题是,我们可以动态更改它们。

通过调用MapDynamicControllerRoute注册动态路由处理程序。我将提供一个示例,使用翻译服务将用户提供的控制器和操作名称(以任何语言)翻译成项目中存在的纯英语。

让我们从头开始。我们为翻译服务定义了接口:

public interface ITranslator
{
    Task<string> Translate(string sourceLanguage, string term);
}

如您所见,这有一个异步方法Translate,它接受两个参数:源语言和要翻译的术语。我们不要浪费太多时间在这上面。

核心动态路由功能作为继承自DynamicRouteValueTransformer的类实现。下面是一个此类的示例,并对其进行了说明:

public sealed class TranslateRouteValueTransformer : DynamicRouteValueTransformer
{
    private const string _languageKey = "language";
    private const string _actionKey = "action";
    private const string _controllerKey = "controller";

    private readonly ITranslator _translator;

    public TranslateRouteValueTransformer(ITranslator translator)
    {
        this._translator = translator;
    }

    public override async ValueTask<RouteValueDictionary> TransformAsync(
        HttpContext httpContext, RouteValueDictionary values)
    {          
        var language = values[_languageKey] as string;
        var controller = values[_controllerKey] as string;
        var action = values[_actionKey] as string;

        controller = await this._translator.Translate(
         language, controller) ?? controller;
        action = await this._translator.Translate(language, action)
         ?? action;

        values[_controllerKey] = controller;
        values[_actionKey] = action;

        return values;
    }
}

TranslateRouteValueTransformer类在其构造函数上接收ITranslator的实例,并将其保存为本地字段。在TransformAsync方法中,检索路由模板值、languagecontrolleraction的值;对于controlleraction,将其翻译为ITranslator。结果值将再次存储在 route values dictionary 中,并在最后返回。

要使这项工作发挥作用,我们需要三件事:

  1. 我们需要在ConfigureServices中将ITranslator注册为服务:
services.AddSingleton<ITranslator, MyTranslator>();
//MyTranslator is just for demo purposes, you need to roll out your own dictionary implementation
  1. 我们也需要将TranslateRouteValueTransformer注册为一项服务:
services.AddSingleton<TranslateRouteValueTransformer>();
  1. 最后,我们需要注册一个动态路由:
app.UseEndpoints(endpoints =>
{
    endpoints.MapDynamicControllerRoute<TranslateRouteValueTransformer>(
        pattern: "{language}/{controller}/{action}/{id?}");
    //now adding the default route
    endpoints.MapDefaultControllerRoute();
});

如您所见,我们的动态路线寻找一个模式language/controller/action/id,其中id部分是可选的。任何可以映射到此模式的请求都将落入此动态路由。

请记住,动态路由的目的不是更改路由模式,而是更改路由模板令牌。这不会导致任何重定向,但实际上会确定如何处理请求、操作方法和控制器以及任何其他路由参数。

为了结束本节,本例允许解析这些路线,前提是词典支持法语(fr、德语(de)和葡萄牙语(pt):

  • /fr/Maison/Index/Home/Index
  • /pt/Casa/Indice/Home/Index
  • /de/Zuhause/Index/Home/Index

You can have multiple dynamic routes with different patterns; this is perfectly OK.

了解了动态路由之后,让我们回到静态路由,这次使用类和方法中的属性来定义路由。

从属性中选择路由

ASP.NET Core,或者更确切地说,路由中间件,将获取请求 URL 并检查它知道的所有路由,以查看是否有与请求匹配的路由。它将在遵守路由插入顺序的情况下执行此操作,因此请注意,您的请求可能会意外地落入您预期的路由之外。总是先添加最具体的,然后再添加通用的。

在找到与请求匹配的模板后,ASP.NET Core 将检查目标控制器上是否存在一个可用的操作方法,该方法没有禁止将方法用作操作的NonActionAttribute实例,或者具有从HttpMethodAttribute继承的属性,该属性与当前 HTTP 动词相匹配。以下列出了:

  • HttpGetAttribute
  • HttpPostAttribute
  • HttpPutAttribute
  • HttpDeleteAttribute
  • HttpOptionsAttribute
  • HttpPatchAttribute
  • HttpHeadAttribute

它们都继承自HttpMethodAttribute:这是用于基于 HTTP 谓词进行过滤的根类。

如果找到其中任何一个,则仅当 HTTP 谓词与指定的谓词之一匹配时,才会选择路由。可以有许多属性,这意味着可以使用指定的任何 HTTP 谓词调用 action 方法。

There are other HTTP verbs, but ASP.NET Core only supports these out of the box. If you wish to support others, you need to subclass HttpMethodAttribute and supply your list or use ActionVerbsAttribute. Interestingly, ASP.NET Core—as before in the ASP.NET web API—offers an alternative way of locating an action method: if the action token is not supplied, it will look for an action method whose name matches the current HTTP verb, regardless of the casing.

您可以使用这些属性来提供不同的操作名称,这允许您使用方法重载。例如,如果有两个同名的方法采用不同的参数,则区分它们的唯一方法是使用不同的操作名称:

public class CalculatorController
{
    //Calculator/CalculateDirectly
    [HttpGet(Name = "CalculateDirectly")]
    public IActionResult Calculate(int a, int b) { ... }

    //Calculator/CalculateByKey
    [HttpGet(Name = "CalculateById")]
    public IActionResult Calculate(Guid calculationId) { ... }
}

如果不可能,则可以使用不同的目标 HTTP 谓词:

//GET Calculator/Calculate
[HttpGet]
public IActionResult Calculate(int a, int b) { ... }

//POST Calculator/Calculate
[HttpPost]
public IActionResult Calculate([FromBody] Calculation calculation) { ... }

当然,您可以限制一个操作方法或整个控制器,以便只有在使用AuthorizeAttribute对请求进行身份验证的情况下才能访问它。我们在这里不再赘述,这将在第 11 章安全中讨论。

但值得注意的是,即使整个控制器上都标有AuthorizeAttribute,如果带有AllowAnonymousAttribute

[Authorize]
public class PrivateController
{
    [AllowAnonymous]
    public IActionResult Backdoor() { ... }
}

另一个选项是根据请求的内容类型约束操作。您使用ConsumesAttribute用于此目的,您可以按如下方式应用它:

[HttpPost]
[Consumes("application/json")]
public IActionResult Process(string payload) { ... }

For an explanation of what content types are, please see https://www.w3.org/Protocols/rfc1341/4_Content-Type.html.

另一个有助于路由选择的属性是RequireHttpsAttribute。如果请求存在于方法或控制器类中,则只有通过 HTTPS 发出的请求才会被接受。

最后,还有路由约束。它们通常用于验证请求中传递的令牌,但也可以用于验证整个请求。我们将很快讨论这些问题。

因此,顺序如下:

  1. 查找与请求匹配的第一个模板。
  2. 检查是否存在有效的控制器。
  3. 通过操作名称或动词匹配,检查控制器中是否存在有效的操作方法。
  4. 检查存在的任何约束是否有效。
  5. 检查所有有助于路由选择的属性(AuthorizeAttributeNonActionAttributeConsumesAttributeActionVerbsAttributeRequireHttpsAttributeHttpMethodAttribute均有效。

我们将很快看到约束如何影响路线选择。

使用特殊路线

以下路由是特殊的,因为它们对 ASP.NET Core 具有特殊意义:

  • [HttpGet("")]:这是控制器的默认动作;只能定义一个。如果应用于没有所需参数的方法,它将是整个应用的默认操作。
  • [HttpGet("~/")]:这是应用对默认控制器的默认操作:它映射到应用的根目录(例如,**/**

因此,如果您在控制器的动作方法上设置[HttpGet("")]并且没有定义任何其他路由,那么它将是该控制器的默认动作,如果您在没有路由表的情况下设置[HttpGet("~/")],那么它将是默认动作和默认控制器。

下一节将解释如何基于调用主机和/或服务器端口限制路由。

从属性中选择主机

从 ASP.NET 3 开始,还可以基于主机头和端口限制路由。您可以通过属性或使用 fluent(基于代码)配置来实现这一点。

以下是使用属性的示例:

[Host("localhost", "127.0.0.1")]
public IActionResult Local() { ... }

[Host("localhost:80")]
public IActionResult LocalPort80() { ... }

[Host(":8080")]
public IActionResult Port8080() { ... }

这里有三个使用[Host]属性的示例:

  1. 第一种方法仅当本地头为localhost127.0.0.1时,才使Local动作方法可达;可以提供任意数量的主机头。
  2. 第二个示例需要主机头和端口的组合,在本例中为80
  3. 最后一个只需要端口8080

当然,[Host]属性可以与任何[Http*][Route]属性组合。

下面是如何通过代码执行此操作:

endpoints.MapControllerRoute("Local", "Home/Local").RequireHost("localhost", "127.0.0.1");

本例仅接受来自"localhost""127.0.0.1"(通常为同义词)的给定路由请求。

现在,下一个主题将是如何为管线模板参数指定默认值。

设置路由默认值

我们已经了解了如何在模板中为路由参数指定默认值,但还有另一种方法:通过重载接受包含默认值的对象的MapControllerRoute扩展方法。您可以使用以下内容,而不是将这些默认值作为字符串提供:

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

即使路由中没有令牌,这也是有效的,如下所示:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "My/Route",
        defaults: new { controller = "My", action = "Route" });
});

请记住,您必须提供controlleraction;如果它们不在模板中,则需要作为默认值提供。

下一节将深入研究路由的内部工作机制,以及我们如何处理请求。

路由到内联处理程序

在 ASP.NET Core 中,可以直接处理请求,即不路由到控制器操作。我们使用扩展方法定义内联处理程序,该扩展方法指定要匹配的 HTTP 谓词和模板,如下所示:

  • MapGet:HTTPGet
  • MapPost:HTTPPost
  • MapPut:HTTPPut
  • MapDelete:HTTPDelete
  • MapVerb:任何命名的 HTTP 动词;例如,Get与使用MapGet相同

实际上有两种扩展方法,MapXXXMapXXXMiddleware,第一种采用委托,第二种采用中间件类。下面是一个例子。

这些方法提供了两种可能的签名(除了采用 HTTP 动词的Map<*verb*>之外),并采用以下参数:

  • pattern:这是一个路由模板。
  • requestHandler:此处理程序接受当前上下文(HttpContext并返回任务。

这里有两个例子。在第一种情况下,我们只是设置响应内容类型并向输出中写入一些文本:

endpoints.MapGet(
    pattern: "DirectRoute",
    requestDelegate: async ctx =>
    {
        ctx.Response.ContentType = "text/plain";
        await ctx.Response.WriteAsync("Here's your response!");
    });

在这里,我们将向响应添加一个中间件:

var newAppBuilder = endpoints.CreateApplicationBuilder();
newAppBuilder.UseMiddleware<ResponseMiddleware>();

endpoints.MapGet(
    pattern: "DirectMiddlewareRoute", newAppBuilder.Build());

ResponseMiddleware可能是这样的:

public class ResponseMiddleware
{
    private readonly RequestDelegate _next;

    public ResponseMiddleware(RequestDelegate next)
    {
        this._next = next;
    }

    public async Task InvokeAsync(HttpContext ctx)
    {
        await ctx.Response.WriteAsync("Hello, from a middleware!");
    }
}

When using MapMiddlewareXXX, you can't return the next delegate, as it is meant to be the only response.

这两种方法(使用处理程序或应用生成器)是相似的,因为前者让我们直接访问请求上下文,而后者允许我们为特定路由模板向请求管道添加步骤。这完全取决于你想做什么。

You cannot mix direct handlers with controllers: the first handler that is picked up in the routing table will be processed, and no other. So, for example, if you have MapGet followed by MapControllerRoute for the same template, the handler or action specified in MapGet will be processed, but not the controller in MapControllerRoute.

现在我们了解了如何处理路由请求,接下来我们将学习如何约束路由的适用性。

应用路线约束

当我们定义一个路由模板或模式时,我们可能还想指定该路由应该如何匹配,这是的约束。我们可以通过多种方式约束路线,例如:

  • 请求需要匹配给定的 HTTP 方法。
  • 请求需要与给定的内容类型匹配。
  • 其参数需要与某些规则匹配。

约束可以在路由模板中表示,也可以使用MapControllerRoute方法表示为离散对象。如果选择使用路由模板,则需要在其应用的令牌旁边指定其名称:

{controller=Home}/{action=Index}/{id:int}

注意{id:int}:这将id参数约束为整数,这是我们稍后将讨论的提供的约束之一。另一个选项是使用defaults参数:

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

您应该能够猜测在constraints参数中传递的匿名类必须具有与路由参数匹配的属性。

根据本例,您还可以传递未绑定到任何管线参数的约束,而是执行某种定制验证,如下所示:

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

在这种情况下,BarRouteConstraint约束类仍将被调用,并可用于使路由选择无效。

HTTP 方法

如前所述,为了使操作方法仅对某些 HTTP 谓词或特定内容类型可用,可以使用以下方法之一:

  • HttpGetAttribute
  • HttpPostAttribute
  • HttpPutAttribute
  • HttpDeleteAttribute
  • HttpOptionsAttribute
  • HttpPatchAttribute
  • HttpHeadAttribute
  • ActionVerbsAttribute
  • ConsumesAttribute

这些名字应该是不言自明的。您可以为不同的谓词添加属性,如果存在任何谓词,则只有当其谓词与这些属性之一匹配时,路由才会匹配。ActionVerbsAttribute允许您传递希望支持的单个方法或方法列表。ConsumesAttribute采用有效的内容类型。

默认约束

ASP.NET Core 包含以下约束:

| 约束 | 目的 | 示例 | | alpha (AlphaRouteConstraint) | 将文本限制为字母数字字符,即不包括符号 | {term:alpha} | | bool (BoolRouteConstraint) | 仅为truefalse | {force:bool} | | datetime (DateTimeRouteConstraint) | 给出日期或日期和时间模式 | {lower:datetime} | | decimal (DecimalRouteConstraint) | 包括十进制值 | {lat:decimal} | | double (DoubleRouteConstraint) | 包括双精度浮点值 | {precision:double} | | exists (KnownValueRouteConstraint) | 强制存在路由令牌 | {action:exists} | | float (FloatRouteConstraint) | 包括单精度浮点值 | {accuracy:float} | | guid (GuidRouteConstraint) | 包括 guid | {id:guid} | | int (IntRouteConstraint) | 包括整数值 | {id:int} | | length (LengthRouteConstraint) | 包括受约束的字符串 | {term:length(5,10) | | long (LongRouteConstraint) | 包含一个长整数 | {id:long} | | max (MaxRouteConstraint) | 这是整数的最大值 | {page:max(100)} | | min (MinRouteConstraint) | 这是整数的最小值 | {page:min(1)} | | maxlength (MaxLengthRouteConstraint) | 包括最大长度的任何字母数字字符串 | {term:maxlength(10)} | | minlength (MinLengthRouteConstraint) | 包括任何最小长度的字母数字字符串 | {term:minlength(10)} | | range (RangeRouteConstraint) | 包括一个整数范围 | {page:range(1,100)} | | regex (RegexRouteConstraint) | 正则表达式 | {isbn:regex(^d{9}[d&#124;X]$)} | | required (RequiredRouteConstraint) | 包括必须实际存在的必需值 | {term:required} |

一个 route 参数可以同时接受多个约束,以:分隔,如下所示:

Calculator/Calculate({a:int:max(10)},{b:int:max(10)})

在本例中,ab参数需要是整数,同时具有10的最大值。另一个例子如下:

Book/Find({isbn:regex(^d{9}[d|X]$)])

这将匹配一个 ISBN 字符串,该字符串以 9 位数字开头,后跟尾随数字或X字符。

还可以提供您自己的自定义约束,我们将在下面看到。

创建自定义约束

约束是实现IRouteConstraint的任何类。如果要在管线模板中内联使用,则必须对其进行注册。以下是验证偶数的路由约束示例:

public class EvenIntRouteConstraint : IRouteConstraint
{
    public bool Match(
        HttpContext httpContext,
        IRouter route,
        string routeKey,
        RouteValueDictionary values,
        RouteDirection routeDirection)
    {
        if ((!values.ContainsKey(routeKey)) || (values[routeKey] == null))
        {
            return false;
        }

        var value = values[routeKey].ToString();

        if (!int.TryParse(value, out var intValue))
        {
            return false;
        }

        return (intValue % 2) == 0;
    }
}

您应该能够知道所有路由参数都在values集合中提供,并且路由参数名称在routeKey中。如果没有实际提供路由参数,它将只返回false,如果无法将参数解析为整数,它将返回false。现在,要注册约束,您需要使用本章前面所示的AddRouting方法:

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

这实际上与从注册的配置中检索RouteOptions相同:

services.Configure<RouteOptions>(options =>
{
    //do the same
});

就这些。

如果希望使用路由约束验证 URL 或任何请求参数,可以使用未绑定到路由密钥的路由约束:

public class IsAuthenticatedRouteConstraint : IRouteConstraint
{
    public bool Match(
        HttpContext httpContext,
        IRouter route,
        string routeKey,
        RouteValueDictionary values,
        RouteDirection routeDirection)
    {
        return httpContext.Request.Cookies.ContainsKey("auth");
    }
}

当然,还有其他(甚至更好的)方法可以做到这一点;这只是一个例子。

现在我们可以这样使用它,在一条路线中:

Calculator/Calculate({a:evenint},{b:evenint})

另一方面,如果您更愿意在MapControllerRoute调用中直接使用约束类,则不需要注册它们。无论如何,路由约束集合作为IInlineConstraintResolver服务提供:

var inlineConstraintResolver = routes
    .ServiceProvider
    .GetRequiredService<IInlineConstraintResolver>();

If you wish to specify custom route constraints in routing attributes, you will need to register them.

在本章中,我们了解了如何定义路由令牌的约束,包括创建我们自己的约束,这对于预先验证 URL 非常有用。下一节将解释什么是数据令牌。

路由数据令牌

路由数据令牌路由令牌路由参数相反,只是您在路由表条目中提供的一些任意数据,可用于路由处理管道,包括 MVC 操作方法。与路由令牌不同,路由数据令牌可以是任何类型的对象,而不仅仅是字符串。它们对 MVC 毫无意义,只会被忽略,但它们很有用,因为您可以有多个路由指向同一个操作方法,并且您可能希望使用数据令牌来找出触发调用的路由。

您可以按如下方式传递数据令牌:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller}/{action}/{id?}",
        defaults: new { controller = "Home", action = "Index" },
        constraints: null,
        dataTokens: new { foo = "bar" });
});

您还可以从IDataTokensMetatata元数据项中检索它们,如从控制器操作中检索:

public class HomeController : Controller
{
    public IActionResult Index()
    {
        var metadata = this.HttpContext.GetEndpoint().Metadata.
         GetMetadata<IDataTokensMetadata>();
        var foo = metadata?.DataTokens["foo"] as string;
        return this.View();
    }
}

因为DataTokens值的原型是object,所以您需要知道将要检索什么。另外,请注意,如果没有设置数据令牌,GetMetadata<IDataTokensMetadata>()方法可能返回null

无法更改数据标记的值。另外,ControllerBase类的旧RouteData属性和HttpContext上的GetRouteData扩展方法现在已经过时,可能会在未来版本的 ASP.NET Core 中删除。

最后,让我们继续,看看如何配置区域的路由。

路由到区域

MVC 长期以来一直支持区域的概念。从本质上讲,区域用于隔离和组织控制器和视图,因此,例如,可以在不同区域中具有相同名称的控制器。

VisualStudio 允许您在项目中创建文件夹,然后向其中添加控制器和视图。您可以将这些文件夹标记为区域。

在涉及路由的情况下,区域将另一个路由令牌(适当地命名为area)添加到controlleraction。如果要使用区域,则模板中可能会有另一段,例如:

Products/Phones/Index
Reporting/Sales/Index

这里,ProductsReporting是区域。您需要将它们映射到路由,以便 MVC 能够识别它们。您可以使用MapControllerRoute扩展方式,但您需要提供area令牌,如下所示:

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

您也可以使用MapAreaControllerRoute扩展方法,该方法负责添加area参数:

endpoints.MapAreaControllerRoute(
    name: "default",
    areaName: "Products",
    pattern: "List/{controller}/{action}/{id?}",
    defaults: new { controller = "Phones", action = "Index" });

此路由将List/Phones/Index请求映射到Products区域内PhonesController控制器的Index动作方式。

这就是区域的问题。现在让我们看看路由属性。

使用路由属性

将路由添加到路由表的替代方法是使用路由属性。路由属性存在于 ASP.NET Core 之前,甚至存在于 ASP.NET MVC 和 Web API 中。如果希望 ASP.NET Core 自动识别路由属性,则需要执行以下操作:

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

在以下部分中,我们将了解一些路由属性,并了解如何应用它们。

让我们看看如何使用属性定义路由。

定义路线

这些属性用于定义路线,可以组合在一起;如果我们给一个类添加一个路由属性,给它的一个方法添加另一个路由属性,实际路由将由这两个属性产生。

路由属性最明显的用途是修饰动作方法,如下所示:

[Route("Home/Index")]
public IActionResult Index() { ... }

例如,如果您在同一控制器中有多个操作,并且希望使用相同的前缀(Home来映射它们,则可以执行以下操作:

[Route("Home")]
public class HomeController
{
    [Route("Index")]
    public IActionResult Index() { ... }

    [Route("About")]
    public IActionResult About() { ... } 
}

In previous (non-Core) versions of MVC and Web API, you could use RoutePrefixAttribute for this purpose. Now, RouteAttribute takes care of both cases.

路由是加法的,这意味着如果您在控制器中指定一个路由,然后在操作方法中,您将获得这两个路由,如Home/IndexHome/About

如您所见,HomeController类中的 route 参数与控制器的常规名称(Home匹配。因此,我们也可以使用[controller]特殊代币:

[Route("[controller]")]
public class HomeController { ... }

对于 API 控制器,我们可以使用:

[Route("api/[controller]")]
public class ServicesController { ... }

此外,每个操作都映射了一个与方法名称完全匹配的名称。同样,我们可以使用[action]

[Route("[action]")]
public IActionResult Index() { ... }

[Route("[action]")]
public IActionResult About() { ... } 

可以传递多个路由属性,以便操作方法将响应不同的请求:

[Route("[action]")]
[Route("")]
[Route("Default")]
public IActionResult Index() { ... }

Index方法可通过以下任一请求调用:

/Home
/Home/Index
/Home/Default

注意,Home部分来自类级别应用的 route 属性。另一方面,如果在模板中指定斜杠,则使模板成为绝对模板;此模板的外观如下所示:

[Route("Default/Index")]
public IActionResult Index() { ... }

这只能通过以下方式访问:

/Default/Index

如果您想考虑控制器,您应该在模板中显式地命名它,或者使用[controller]特殊标记:

[Route("[controller]/Default/Index")]
public IActionResult Index() { ... }

可通过以下方式访问:

/Home/Default/Index

The [controller] and [action] tokens are for when we want to use constants for routes. These constants have the potential to be used in lots of places, as they are not stuck to specific actions and controllers. They were not available in previous versions of ASP.NET MVC or Web API.

默认路由

通过路由属性,您可以使用空白模板应用RouteAttribute来指定默认控制器:

[Route("")]
public class HomeController { ... }

控制器中的默认操作也将是具有空模板的操作,如下所示:

[Route("")]
public IActionResult Index() { ... }

如果没有具有空路由模板的方法,ASP.NET Core 将尝试查找名称与当前 HTTP 方法匹配的方法。

约束路线

您还可以指定管线约束,其语法与我们之前看到的相同:

[Route("Calculate({a:int},{b:int})")]
public IActionResult Calculate(int a, int b) { ... }

界定领域

通过将AreaAttribute应用于控制器,您也可以定义包含区域的路由:

[Area("Products")]
[Route("[controller]")
public class ReportingController { ... }

[controller][action]类似,还有一个特殊的[area]标记,您可以在模板中使用它来指示当前区域,从文件系统推断:

[Route("[area]/Default")]
public IActionResult Index() { ... }

指定操作名称

您可以通过ActionNameAttribute为控制器方法指定操作名称,如下所示:

[ActionName("Default")]
public IActionResult Index() { ... }

您还可以通过 HTTP 动词选择属性(HttpGetAttributeHttpPostAttributeHttpPutAttributeHttpOptionsAttributeHttpPatchAttributeHttpDeleteAttributeHttpHeadAttribute中的任何一个)来执行此操作:

[HttpGet(Name = "Default")]
public IActionResult Index() { ... }

Please do remember that you cannot specify a route template and an action name at the same time, as this will result in an exception being thrown at startup time when ASP.NET Core scans the routing attributes. Also, do not specify ActionNameAttribute and a verb selection attribute at the same time as specifying the action name.

定义非行动

如果要防止控制器类中的公共方法被用作操作,可以使用NonActionAttribute对其进行修饰:

[NonAction]
public IActionResult Process() { ... }

限制路线

当我们谈到路由约束时,我们看到我们可以限制一个动作方法,使其仅在满足以下一个或多个条件时才可调用:

  • 它与给定的 HTTP 动词(ActionVerbsAttributeHttp*Attribute匹配。
  • 使用 HTTPS(RequireHttpsAttribute调用。
  • 使用给定的内容类型(ConsumesAttribute调用。

我们将不再详细讨论这个问题,因为前面已经解释过了。

设置路线值

可以在操作方法中提供任意路由值。这就是RouteValueAttribute抽象类的目的。您需要从中继承:

public class CustomRouteValueAttribute : RouteValueAttribute
{
    public CustomRouteValueAttribute(string value) : base("custom", value) { }
}

然后,按如下方式应用和使用:

[CustomRouteValue("foo")]
public IActionResult Index()
{
    var foo = this.ControllerContext.RouteData.Values["foo"];
    return this.View();
}

AreaAttribute is an example of a class inheriting from RouteValueAttribute. There is no way to pass arbitrary route data tokens through attributes.

正如您所看到的,通过属性可以实现很多功能。这还包括错误处理;现在让我们来了解更多。

路由中的错误处理

对于在处理请求期间捕获的错误和异常,例如,当找不到资源时,我们该怎么办?您可以为此使用路由。在这里,我们将介绍一些策略:

  • 路由
  • 添加全面覆盖的路由
  • 显示开发人员错误页面
  • 使用状态代码页中间件

我们将在以下部分了解这些。

控制器路由的路由错误

当发生错误时,您可以通过调用UseExceptionHandler强制调用特定控制器的操作:

app.UseExceptionHandler("/Home/Error");

请注意,您在这个视图(Error中的内容完全取决于您。

您甚至可以做一些更有趣的事情,即注册中间件以在发生错误时执行,如下所示:

app.UseExceptionHandler(errorApp =>
{
    errorApp.Run(async context =>
    {
        var errorFeature = context.Features.Get<IException
         HandlerPathFeature>();
        var exception = errorFeature.Error;  //you may want to check what 
                                             //the exception is
        var path = errorFeature.Path;
        await context.Response.WriteAsync("Error: " + exception.Message);
    });
});

You will need to add a using reference for the Microsoft.AspNetCore.Http namespace in order to use the WriteAsync method.

IExceptionHandlerPathFeature功能允许您检索发生的异常和请求路径。使用这种方法,您必须自己生成输出;也就是说,您没有 MVC 视图的好处。

接下来,我们将介绍如何显示用户友好的错误页面。

使用开发人员异常页面

在开发模式下运行时,您可能需要一个显示开发人员相关信息的页面,在这种情况下,您应该调用UseDeveloperExceptionPage

app.UseDeveloperExceptionPage();

这将基于也包含环境变量的默认模板显示异常消息,包括所有请求属性和堆栈跟踪。它通常仅用于Development环境,因为它可能包含攻击者可能使用的敏感信息。

由于.NETCore3,可以通过IDeveloperPageExceptionFilter实现来调整它的输出。我们在依赖项注入容器中注册了一个,或者在HandleExceptionAsync方法中提供我们自己的输出,或者只返回默认实现:

services.AddSingleton<IDeveloperPageExceptionFilter, CustomDeveloperPageExceptionFilter>();

此方法非常简单:它接收一个错误上下文和一个委托,该委托指向管道中的下一个异常筛选器,该筛选器通常生成默认错误页:

class CustomDeveloperPageExceptionFilter : IDeveloperPageExceptionFilter
{
    public async Task HandleExceptionAsync(ErrorContext
     errorContext, Func<ErrorContext, Task> next)
    {
        if (errorContext.Exception is DbException)
        {
            await errorContext.HttpContext.Response.WriteAsync("Error 
             connecting to the DB");
        }
        else
        {
            await next(errorContext);
        }
    }
}

这个简单的示例具有依赖于异常的条件逻辑,它要么发送自定义文本,要么只委托给默认处理程序。

使用“一网打尽”的路线

您可以通过添加一个操作方法来添加一个全面覆盖的路由,该方法的路由在没有其他路由匹配的情况下始终匹配(如回退端点部分离子中的回退页面)。例如,我们可以使用如下路由属性:

[HttpGet("{*url}", Order = int.MaxValue)]
public IActionResult CatchAll()
{
    this.Response.StatusCode = StatusCodes.Status404NotFound;
    return this.View();
}

当然,在Configure方法中,同样可以通过流畅的配置实现:

app.UseEndpoints(endpoints =>
{
    //default routes go here
    endpoints.MapControllerRoute(
        name: "CatchAll",
        pattern: "{*url}",
        defaults: new { controller = "CatchAll", action = "CatchAll" }
    );
});

在这里,您只需添加一个带有友好错误消息的漂亮视图!请注意,同一控制器中的其他操作也需要指定路由;否则,默认路由将变为CatchAll

Fallback pages are a simpler alternative to catch-all routes.

使用状态代码页中间件

现在让我们看看如何使用 HTTP 状态码响应错误,这是向客户端返回高级响应的标准方法。

状态代码页

另一种选择是在400坏请求599网络连接超时之间添加代码以响应特定的 HTTP 状态代码,该代码没有主体(尚未处理),我们通过UseStatusCodePages来实现:

app.UseStatusCodePages(async context => { context.HttpContext.Response.ContentType = "text/plain";
 var statusCode = context.HttpContext.Response.StatusCode;
 await context.HttpContext.Response.WriteAsync("HTTP status code: " + statusCode); });

该方法将中间件组件添加到管道中,该组件负责在异常发生后执行两项操作:

  • IStatusCodePagesFeature特性上填充Error属性
  • 从那里处理死刑

这里有一个不同的重载,基本上与上一个重载相同:

app.UseStatusCodePages("text/plain", "Error status code: {0}");

下面是一些自动重定向到路由(找到一个 HTTP 代码为302)的东西,其中一个特定的状态代码作为路由值:

app.UseStatusCodePagesWithRedirects("/error/{0}");

相反,这一个在不发出重定向的情况下重新执行管道,从而使其更快:

app.UseStatusCodePagesWithReExecute("/error/{0}");

可通过IStatusCodePagesFeature功能禁用与特定状态代码相关的所有执行:

var statusCodePagesFeature = HttpContext.Features.Get<IStatusCodePagesFeature>();
statusCodePagesFeature.Enabled = false;

路由到特定状态代码页

您可以向控制器添加这样的操作,使其响应"error/404"请求(只需将错误代码替换为您想要的代码):

[Route("error/404")]
public IActionResult Error404()
{
    this.Response.StatusCode = StatusCodes.Status404NotFound;
    return this.View();
}

现在,要么添加一个Error404视图,要么调用一个通用视图,将404状态代码传递给它,可能通过视图包。同样,此路由可以流畅地配置,如下所示:

endpoints.MapControllerRoute(
    name: "Error404",
    pattern: "error/404",
    defaults: new { controller = "CatchAll", action = "Error404" }
);

当然,这需要与UseStatusCodePagesWithRedirectsUseStatusCodePagesWithReExecute一起使用。

任何状态代码

要捕获单个方法中的所有错误,请执行以下操作:

[Route("error/{statusCode:int}")]
public IActionResult Error(int statusCode)
{
    this.Response.StatusCode = statusCode;
    this.ViewBag.StatusCode = statusCode;
    return this.View();
}

在这里,我们调用一个名为Error(从动作名称推断)的通用视图,因此我们需要向其传递原始状态代码,我们通过视图包执行此操作,如下所示:

endpoints.MapControllerRoute(
    name: "Error",
    pattern: "error/{statusCode:int}",
    defaults: new { controller = "CatchAll", action = "Error" }
);

对于/error/<statusCode>的请求,我们被引导到CatchAllController控制器和Error动作。同样,这需要UseStatusCodePagesWithRedirectsUseStatusCodePagesWithReExecute

在这里,我们介绍了基于异常或状态代码的不同错误处理方法。挑一个最适合你的!

总结

在现实生活中,您可能会混合使用基于代码的路由配置和属性。在我们的示例中,我们将使用本地化功能,这需要大量配置,通常是基于代码的配置。属性路由也有它的位置,因为我们可以直接定义不需要受常规路由模板限制的可访问端点。路由约束非常强大,应该使用。

从包含的默认路线模板开始并从那里开始,总是很好的。它应该足以满足您大约 80%的需求。其他路由将通过自定义路由或路由属性定义。

我们在本章中看到,安全性是需要考虑的因素,为此目的使用路由属性似乎很理想,因为我们可以通过查看控制器方法立即了解安全限制。

我们已经看到了配置路由的不同方式,换句话说,将浏览器请求转化为操作。我们研究了基于代码和基于属性的路由,并了解了它们的一些优点和局限性。我们了解了如何将 URL 参数限制为特定类型或符合特定要求,以及如何防止调用动作方法,除非它符合特定动词、HTTPS 要求或请求内容类型。最后,我们研究了如何使用路由指向状态代码或特定于错误的操作,以便返回友好的错误页面。

本章中涉及的许多主题将在后面的章节中再次出现。在下一章中,我们将讨论 MVC 最重要的部分,这也是本章的主题:控制器和动作。

问题

现在,您已经完成了本章的学习,您应该能够回答以下问题:

  1. 什么是特殊路线代币?
  2. 我们如何防止根据请求的 HTTP 谓词选择路由?
  3. 除非请求使用 HTTPS,否则如何防止选择路由?
  4. 如何根据出现的 HTTP 错误代码提供不同的视图?
  5. 如何防止调用控制器中的方法?
  6. 如何强制路由值为特定类型(例如,数字)?
  7. 什么是路由处理程序?