为什么 "Default" 站点路由到 ASP.NET MVC 中的站点根目录有效?

Why does the "Default" site route to the site root in ASP.NET MVC work?

ASP.NET MVC 站点带有以下默认根,当访问站点的根时执行 Home 控制器的 Index 操作(例如 http://localhost:12345

routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );

所以当没有找到路由时,这是一种回退?

没那么快。如果我们尝试导航到 http://localhost/a/b(其中不存在 'a' 控制器),它将不会执行 Home 控制器的 Index 操作 - 将返回一个错误。

为什么在那种情况下无法执行 Home/Index - 但在路径中绝对不输入任何内容却会执行 Home/Index?

这里的逻辑是什么 - 为什么这条路线被称为 'defaults'?

我看过很多关于路由的文章 - 但没有一篇对此进行解释。

相关问题

在其他情况下,'defaults' 中的内容看起来更像是路线图。例如

url: "abc/def", defaults: new { controller = "bongo", action = "bingo" }

这只会在 bongo 控制器上执行宾果游戏操作 - 只要输入确切的 url "abc/def"。为什么叫它 'defaults' - 这个词似乎不太合适。 (删除'defaults'有没有效果,我看到它被省略了)。

在默认路由中它看起来更像是一个回退,在后一个例子中更像是一个映射?

我觉得这里缺少概念层面的东西。

谢谢。

逻辑虽然起初不是很直观,但实际上非常简单。

一般来说,传入请求发生时会发生两种不同的情况。

  1. 尝试匹配路由
  2. 为 MVC 提供一组路由值(以及可选的路由元数据),它用于查找操作方法

当有传入请求发生时,MVC 执行路由table中第一个路由的GetRouteData方法。如果不匹配,它将尝试第二次、第三次,依此类推,直到找到匹配项。

如果路由table最终没有匹配到,RouteCollection.GetRouteData(每条路由调用GetRouteData的方法)会returnnull .如果找到匹配项,来自匹配路由的路由值将被 returned 到 MVC,在 MVC 中它使用它们来查找要执行的控制器和操作。请注意,在这种情况下,没有检查路由 table 中的其他路由 是否匹配。换句话说,第一场比赛总是赢。

匹配过程依赖于三件事:

  1. 占位符
  2. 文字段
  3. 约束条件

占位符

您要问的部分是 placeholders 以及当没有值时它们匹配的原因。占位符(即 {controller})就像变量一样。他们会接受任何价值。它们可以初始化为默认值。如果它们未初始化为默认值,则它们 需要在 URL 中才能匹配。

考虑路由定义中的defaults

defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }

如果 URL 中未提供 占位符,将使用这些值(作为 RouteValues 中的匹配和输出)。 =84=]

按照同样的逻辑,默认情况下所有这些 URL 都会到达 HomeController.Index 操作方法。

  • /
  • /Home
  • /Home/Index
  • /Home/Index/Foo
  • /Home/Index/123

URL: /

如果你把URL/传递给框架,它会匹配Default路由并发送给HomeController.Index方法 因为如果提供了 none,则默认值为 HomeIndex。在这种情况下,路由值为:

| Key         | Value       |
|-------------|-------------|
| controller  | Home        |
| action      | Index       |
| id          | {}          |

URL /Home

请注意,您也可以只传递控制器名称 /Home。路线 table 看起来完全一样。

| Key         | Value       |
|-------------|-------------|
| controller  | Home        |
| action      | Index       |
| id          | {}          |

但是,在这种情况下,controller 值是通过 URL 中的占位符传入的。它不再考虑路由的默认 controller 值,因为在 URL.

中提供了一个值

URL: /Test

遵循同样的逻辑,URL /Test 将导致以下路线 table.

| Key         | Value       |
|-------------|-------------|
| controller  | Test        |
| action      | Index       |
| id          | {}          |

路由不会自动检查控制器是否实际存在。它只是提供价值。如果您的应用程序中没有名为 TestController 且具有 Index 操作的控制器,这将导致错误。

这就是您上面提供的 URL /a/b 不起作用的原因 - 您的项目中没有名为 AController 的控制器和名为 B 的操作。

If placeholders are not initialized to a default value, they are required to be in the URL in order for the route to match.

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { action = "Index", id = UrlParameter.Optional }
);

所以,给定上面的路由,它将匹配 URL /Home:

| Key         | Value       |
|-------------|-------------|
| controller  | Home        |
| action      | Index       |
| id          | {}          |

但它不会匹配 URL / 因为控制器没有默认值。

文字段

routes.MapRoute(
    name: "Foo",
    url: "abc/def",
    defaults: new { controller = "bongo", action = "bingo" }
);

以上路线在URL中使用了文字段。文字段需要完全匹配(不区分大小写)才能将路由视为匹配项。因此,唯一匹配的 URL 是 abc/def 或这两个片段的任何 uppercase/lowercase 组合。

但是,这种情况在一个方面有所不同。 无法通过 URL 传递值。因此,您 必须 设置默认值(至少 controlleraction)以便将任何路由值传递给 MVC。

| Key         | Value       |
|-------------|-------------|
| controller  | bongo       |
| action      | bingo       |

MVC 框架要求有一个 BongoController 和一个名为 Bingo 的动作,否则这条路线将失败得很惨。

约束条件

约束是额外的条件,这是路由匹配所必需的。每个约束 return 都是一个布尔值(match/no 匹配)响应。每条路线可以有 0 到多个约束。

RegEx constraints

routes.MapRoute(
    name: "CustomRoute",
    url: "{placeholder1}/{action}/{id}",
    defaults: new { controller = "MyController" },
    constraints: new { placeholder1 = @"^house$|^car$|^bus$" }
);

匹配

  • /house/details/123
  • /car/foo/bar
  • /car/bar/foo

不匹配

  • /house/details
  • /bank/details/123
  • /bus/foo
  • /car
  • /

Custom constraints

public class CorrectDateConstraint : IRouteConstraint
{
    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        var year = values["year"] as string;
        var month = values["month"] as string;
        var day = values["day"] as string;

        DateTime theDate;
        return DateTime.TryParse(year + "-" + month + "-" + day, System.Globalization.CultureInfo.InvariantCulture, DateTimeStyles.None, out theDate);
    }
}

routes.MapRoute(
    name: "CustomRoute",
    url: "{year}/{month}/{day}/{article}",
    defaults: new { controller = "News", action = "ArticleDetails" },
    constraints: new { year = new CorrectDateConstraint() }
);

注意: 分配给约束的值(在上述情况下 year =)与传递到自定义约束中的值相同。但是,自定义约束没有义务使用此值。有时使用任何值都没有意义,在这种情况下,您可以在 controller.

上设置约束

匹配

  • /2012/06/20/some-great-article
  • /2016/12/25/all-about-christmas

不匹配

  • /2012/06/33/some-great-article
  • /2012/06/20
  • /99999/09/09/the-foo-article

在大多数情况下,只要 URL 中有占位符(例如 {controller}{something}),您 应该使用约束 防止它们匹配它们不应该匹配的值。

Literal segments (or partial literal segments with placeholders), constraints, and required values are all generally very good things to use in your routing setup. They help to ensure that your routes don't match in such a wide scope that they block execution of routes registered after them in the route table.

Placeholders match any value, so it is generally not recommended to use only placeholders in any route but the Default route unless done in conjunction with constraints. Many folks here on Whosebug recommend to remove the Default route entirely to ensure that unintended routes don't function, and I don't necessarily disagree with this sentiment.

进一步阅读