我们在ASP.NET Core MVC中如果要启用Area功能,那么会看到在Startup类的Configure方法中是这么定义Area的路由的:

app.UseMvc(routes =>
{
routes.MapRoute(
name: "subAreaRoute",
template: "{area:exists}/{subarea:exists}/{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "areaRoute",
template: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});

我们可以看到其中{area:exists}和{subarea:exists}这两个路由参数后面都有个:exists后缀,那么这是用来干什么的呢?

来举个例子就明白了:

如果现在ASP.NET Core MVC项目中有个HomeController,并且HomeController下有名为Index的Action,如下所示:

HomeController:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; namespace WebCore.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
}
}

并且Index这个cshtml文件的View也存在:

然后项目中没有定义任何Area文件夹

现在我们访问如下Url:

http://localhost:49908/Home/Index

该Url可以成功显示Index视图,这是因为该Url成功配匹到了ASP.NET Core MVC路由"default",也就是"{controller=Home}/{action=Index}/{id?}"

但是如果我们现在将ASP.NET Core MVC的路由配置改成下面这样,去掉"subAreaRoute"路由的{area}和{subarea}路由参数的:exists后缀:

app.UseMvc(routes =>
{
routes.MapRoute(
name: "subAreaRoute",
template: "{area}/{subarea}/{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "areaRoute",
template: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});

我们再访问Url:

http://localhost:49908/Home/Index

结果如下:

很明显路由解析失败了,ASP.NET Core MVC抛出了异常,可这是为什么呢?我们的项目中有HomeController也有Index这个Action,也有Index.cshtml视图,为什么Url地址http://localhost:49908/Home/Index没有成功匹配到呢?

这是因为当我们去掉了ASP.NET Core MVC路由"subAreaRoute"的路由参数:exists后缀后,Url地址http://localhost:49908/Home/Index匹配到的是"subAreaRoute"路由”{area}/{subarea}/{controller=Home}/{action=Index}/{id?}“,并不是"default"路由"{controller=Home}/{action=Index}/{id?}",我们来看看Url地址为什么能匹配"subAreaRoute"路由”{area}/{subarea}/{controller=Home}/{action=Index}/{id?}“:

  1. 首先Url地址http://localhost:49908/Home/Index中的Home,匹配到"subAreaRoute"路由”{area}/{subarea}/{controller=Home}/{action=Index}/{id?}“中的路由参数{area},所以路由参数{area}的值为Home
  2. 然后Url地址http://localhost:49908/Home/Index中的Index,匹配到"subAreaRoute"路由”{area}/{subarea}/{controller=Home}/{action=Index}/{id?}“中的路由参数{subarea},所以路由参数{subarea}的值为Index
  3. 接下来Url地址http://localhost:49908/Home/Index没有信息可以提供给"subAreaRoute"路由了,所以"subAreaRoute"路由”{area}/{subarea}/{controller=Home}/{action=Index}/{id?}“中的{controller}路由参数为默认值Home,{action}路由参数为默认值Index,{id}路由参数由于是可以为空的所以没有值。这样"subAreaRoute"路由的所有路由参数都有值了,所以Url地址http://localhost:49908/Home/Index匹配路由"subAreaRoute"成功了,它就不会再去匹配路由表中的"areaRoute"路由"{area:exists}/{controller=Home}/{action=Index}/{id?},和"default"路由"{controller=Home}/{action=Index}/{id?}"了

现在Url地址http://localhost:49908/Home/Index匹配路由"subAreaRoute"成功了,接下来就要根据路由参数的值去寻找相应的项目文件了:

  1. 由于现在"subAreaRoute"路由的{area}路由参数值为Home,而{area}路由参数为ASP.NET Core MVC的系统路由参数,其代表的是项目中的Area文件夹,那么需要项目中存在一个叫Home的Area文件夹
  2. 其次由于"subAreaRoute"路由的{controller}路由参数也是ASP.NET Core MVC的系统路由参数,其代表MVC控制器的名称,{controller}路由参数值为Home,所以它要求在Home文件夹下要有一个HomeController文件
  3. 然后由于"subAreaRoute"路由的{action}路由参数也是ASP.NET Core MVC的系统路由参数,其代表的是MVC的Action名称,{action}路由参数值为Index,所以它要求HomeController里面因该有一个叫Index的Action方法

然而我们的项目中并没有叫Home的Area文件夹,因此"subAreaRoute"路由的{controller}路由参数和{action}路由参数也无法找到对应的Controller文件,所以虽然Url地址http://localhost:49908/Home/Index匹配"subAreaRoute"路由成功了,但是由于找不到Area文件夹所以最后ASP.NET Core MVC报错了抛出了异常,其错误描述页也显示ASP.NET Core MVC试图寻找Area文件夹的地址失败了:

所以我们可以看到当去掉"subAreaRoute"路由”{area:exists}/{subarea:exists}/{controller=Home}/{action=Index}/{id?}“的:exists后缀后,相当于ASP.NET Core MVC不会关心匹配到的{area}路由参数是否在项目中真的有对应的Area文件夹,只要Url地址http://localhost:49908/Home/Index能提供"subAreaRoute"路由的所有路由参数值,那么路由匹配就算成功。而:exists后缀可以保证不仅路由参数能从Url地址匹配到值,还要确保路由参数值在项目中能找到真正的文件夹或文件,整个路由才算匹配成功。如果匹配失败,Url地址会去匹配ASP.NET Core MVC中的其它路由。

我们可以再将"subAreaRoute"路由改为”{area:exists}/{subarea}/{controller=Home}/{action=Index}/{id?}“只给{area}参数加上:exists后缀:

app.UseMvc(routes =>
{
routes.MapRoute(
name: "subAreaRoute",
template: "{area:exists}/{subarea}/{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "areaRoute",
template: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});

然后在浏览器还是输入Url地址:

http://localhost:49908/Home/Index

结果如下:

页面成功显示,因为现在Url地址http://localhost:49908/Home/Index匹配"subAreaRoute"路由”{area:exists}/{subarea}/{controller=Home}/{action=Index}/{id?}“失败了,Url最终来会匹配到的路由表中的"default"路由"{controller=Home}/{action=Index}/{id?}",而"default"路由"{controller=Home}/{action=Index}/{id?}"是可以找到对应的HomeController及Index这个Action和Index.cshtml视图的,所以页面显示成功。那么我们再来分析下为什么现在Url地址http://localhost:49908/Home/Index匹配"subAreaRoute"路由”{area:exists}/{subarea}/{controller=Home}/{action=Index}/{id?}“失败了,最终会匹配"default"路由"{controller=Home}/{action=Index}/{id?}"成功:

  1. 首先Url地址http://localhost:49908/Home/Index中的Home,会匹配"subAreaRoute"路由”{area:exists}/{subarea}/{controller=Home}/{action=Index}/{id?}“中的{area}路由参数,其路由参数值为Home,但是现在{area}路由参数带:exists后缀,又加上{area}路由参数是ASP.NET Core MVC的系统路由参数,代表的是项目中的Area文件夹,所以现在ASP.NET Core MVC立即会去检查项目中是否存在一个叫Home的Area文件夹,结果在项目中找不到叫Home的Area文件夹,这样ASP.NET Core MVC就会认为Url地址http://localhost:49908/Home/Index匹配"subAreaRoute"路由”{area:exists}/{subarea}/{controller=Home}/{action=Index}/{id?}“的{area}参数失败,所以Url地址无法匹配路由"subAreaRoute"。
  2. 接下来由于ASP.NET Core MVC中还有两个路由"areaRoute"和"default",所以Url地址http://localhost:49908/Home/Index会去尝试匹配"areaRoute"路由"{area:exists}/{controller=Home}/{action=Index}/{id?}",因为和上面一点同样的原因,"areaRoute"路由"{area:exists}/{controller=Home}/{action=Index}/{id?}"的{area}路由参数会匹配失败,导致Url也无法匹配"areaRoute"路由
  3. 最后ASP.NET Core MVC中还剩路由"default",Url地址http://localhost:49908/Home/Index会和"default"路由"{controller=Home}/{action=Index}/{id?}"匹配成功,原因就不再介绍了,最后浏览器通过"default"路由"{controller=Home}/{action=Index}/{id?}"成功返回了正确的页面

所以:exists后缀在ASP.NET Core MVC路由中有验证文件夹和文件是否存在的功能,只有当路由参数值对应的文件夹和文件在项目中确实存在时,路由匹配才算成功,否者路由匹配失败,会继续用Url去匹配 ASP.NET Core MVC路由表中的后续路由。

Url地址太长也会匹配失败

当然如果Url地址太长也会导致匹配失败,假如我们在浏览器中输入如下Url地址:

http://localhost:49908/Home/Index/1/2/3/4

还是去匹配如下三个路由,在ASP.NET Core中Startup类的Configure方法中:

app.UseMvc(routes =>
{
routes.MapRoute(
name: "subAreaRoute",
template: "{area:exists}/{subarea:exists}/{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "areaRoute",
template: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});

毫无疑问"subAreaRoute"路由和"areaRoute"路由都会匹配失败,因为我们的项目中压根就没有定义Area文件夹。那么这个Url地址匹配"default"路由的时候会发生什么情况呢?

我们来分析下:

  1. 首先Url地址http://localhost:49908/Home/Index/1/2/3/4中的Home,会为"default"路由"{controller=Home}/{action=Index}/{id?}"中的路由参数{controller}提供参数值Home
  2. 接下来Url地址http://localhost:49908/Home/Index/1/2/3/4中的Index,会为"default"路由"{controller=Home}/{action=Index}/{id?}"中的路由参数{action}提供参数值Index
  3. 然后Url地址http://localhost:49908/Home/Index/1/2/3/4中的1,会为"default"路由"{controller=Home}/{action=Index}/{id?}"中的路由参数{id}提供参数值1
  4. 那么问题来了,剩下在Url地址http://localhost:49908/Home/Index/1/2/3/4中没有标注成蓝色部分的/2/3/4该去匹配"default"路由"{controller=Home}/{action=Index}/{id?}"的什么路由参数呢?答案是匹配不到任何"default"路由的参数,因为"default"路由的所有参数都被匹配完了,最终Url地址http://localhost:49908/Home/Index/1/2/3/4中剩下的/2/3/4会导致Url地址http://localhost:49908/Home/Index/1/2/3/4匹配"default"路由也失败,因为Url太长了有多余的部分无法被"default"路由匹配到。

所以最终Url地址http://localhost:49908/Home/Index/1/2/3/4和"subAreaRoute"路由、"areaRoute"路由及"default"路由都匹配失败了,该Url地址和ASP.NET Core MVC中定义的所有路由都匹配失败了,浏览器页面最终返回404错误:

但是我们如果用Url地址:

http://localhost:49908/Home/Index/1

是可以匹配到"default"路由"{controller=Home}/{action=Index}/{id?}"的,因为该Url恰好提供了"default"路由的所有路由参数值:

在ASP.NET Core MVC中Url地址提供的路由参数太少会导致路由匹配失败, Url地址提供的路由参数太多也会导致路由匹配失败,只有当Url地址提供了合适的路由参数时,才会令路由匹配成功,否者浏览器会返回404错误匹配不到任何路由。匹配到一个路由后还要去看能否根据路由参数找到对应的项目文件和文件夹,如果无法找到又会像前面我们讨论:exists后缀时那样,页面会抛出异常。所以一个Url地址在ASP.NET Core MVC中最后能正确地返回一个页面,提供的信息必须要恰到好处才行,否则ASP.NET Core MVC最终都无法返回正确的结果。

{controller}和{action}路由参数默认是带:exists后缀的

ASP.NET Core MVC的{controller}和{action}这两个系统路由参数,分别代表的是Controller的名称和Controller下Action方法的名称,这两个路由参数我发现比较特殊,因为它们默认就是带:exists后缀的,拿本例中的"default"路由来举例,也就是说"default"路由定义为"{controller=Home}/{action=Index}/{id?}"和"{controller:exists=Home}/{action:exists=Index}/{id?}"效果是一样的,其效果是都带:exists后缀,ASP.NET Core MVC都会在匹配路由时,去检查{controller}路由参数的值是否在项目中存在Controller类及文件,也会去检查{action}路由参数的值能否找到对应的Action方法。

还是用我们在本例Startup类的Configure方法中配置的路由表:

app.UseMvc(routes =>
{
routes.MapRoute(
name: "subAreaRoute",
template: "{area:exists}/{subarea:exists}/{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "areaRoute",
template: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});

我们来匹配如下Url地址:

http://localhost:49908/User/Login

那么Url地址http://localhost:49908/User/Login会匹配"subAreaRoute"路由"{area:exists}/{subarea:exists}/{controller=Home}/{action=Index}/{id?}"和"areaRoute"路由"{area:exists}/{controller=Home}/{action=Index}/{id?}"失败,原因如我们前面匹配Url地址http://localhost:49908/Home/Index时所述。现在Url地址http://localhost:49908/User/Login会尝试匹配"default"路由"{controller=Home}/{action=Index}/{id?}",那么如果{controller}路由参数和{action}路由参数假如不像我们这里所说是默认带:exists后缀的,其匹配过程应该如下:

  1. 首先Url地址http://localhost:49908/User/Login中的User,会匹配到匹配"default"路由"{controller=Home}/{action=Index}/{id?}"的{controller}路由参数,路由参数值为User
  2. 接着Url地址http://localhost:49908/User/Login中的Login,会匹配到匹配"default"路由"{controller=Home}/{action=Index}/{id?}"的{action}路由参数,路由参数值为action
  3. 现在Url地址http://localhost:49908/User/Login没有内容可以提供给"default"路由"{controller=Home}/{action=Index}/{id?}"作为路由参数值了,再加上"default"路由的{id}路由参数可空可以忽略,所以"default"路由"{controller=Home}/{action=Index}/{id?}"的所有路由参数都已经匹配到参数值了,那么Url地址http://localhost:49908/User/Login匹配"default"路由"{controller=Home}/{action=Index}/{id?}"成功,接下来该去寻找项目文件了
  4. 由于"default"路由"{controller=Home}/{action=Index}/{id?}"的{controller}路由参数为ASP.NET Core MVC的系统路由参数,代表Controller的类名及文件,其值为User,所以现在ASP.NET Core MVC会去检查项目中是否有UserController类及文件,结果找不到,所以浏览器按道理应该收到一个异常错误信息,显示寻找项目文件失败

那么结果真如我们上面四点所说吗,我们来试下看下面截图:

结果显示Url地址http://localhost:49908/User/Login直接返回了404错误,压根就没和"default"路由"{controller=Home}/{action=Index}/{id?}"匹配成功,因为如果和路由匹配成功但是找不到项目文件,浏览器收到的应该是异常错误页面而不是404页面。所以这验证了我们所说{controller}路由参数和{action}路由参数是默认带:exists后缀,只有带:exists后缀的路由参数,才会把寻找项目文件和文件夹也作为路由匹配中的一个环节,而不是等路由匹配成功后才去寻找项目文件和文件夹。因为Url地址http://localhost:49908/User/Login匹配"default"路由"{controller=Home}/{action=Index}/{id?}"时,{controller}路由参数是默认带:exists后缀的,其路由参数值为User,所以Url地址http://localhost:49908/User/Login在匹配"default"路由时就会去检查项目中是否存在UserController类及文件,但是找不到,所以Url地址http://localhost:49908/User/Login和"default"路由"{controller=Home}/{action=Index}/{id?}"匹配失败,而ASP.NET Core MVC路由表中的所有路由("subAreaRoute","areaRoute","default")都和Url地址http://localhost:49908/User/Login匹配失败了,所以最终浏览器出现了上面的404错误。

ASP.NET Core MVC的路由参数中:exists后缀有什么作用,顺便谈谈路由匹配机制的更多相关文章

  1. ASP.NET CORE MVC 2.0 项目中引用第三方DLL报错的解决办法 - InvalidOperationException: Cannot find compilation library location for package

    目前在学习ASP.NET CORE MVC中,今天看到微软在ASP.NET CORE MVC 2.0中又恢复了允许开发人员引用第三方DLL程序集的功能,感到甚是高兴!于是我急忙写了个Demo想试试,我 ...

  2. ASP.NET Core MVC的Razor视图中,使用Html.Raw方法输出原生的html

    我们在ASP.NET Core MVC项目中,有一个Razor视图文件Index.cshtml,如下: @{ Layout = null; } <!DOCTYPE html> <ht ...

  3. asp.net core mvc 里的application中的start,end等事件

    我们以前在用asp.net mvc或者webform的时候,经常用用到Application里的事件 start,end等.我们在.net core 里也同样有类似的方法. 在Startup类里,Co ...

  4. asp.net core mvc 统一过滤参数,防止注入漏洞攻击

    参考链接: http://www.lanhusoft.com/Article/132.html 在core下,多少有些改动,其中js部分被注释掉了,如下: public static string F ...

  5. ASP.NET Core MVC 2.1 顶级参数验证

    本文讨论ASP.NET Core 2.1中与ASP.NET Core MVC / Web API控制器中的模型绑定相关的功能.虽说这是一个功能,但从我的角度来看,它更像是一个错误修复! 请注意,我使用 ...

  6. ASP.NET Core 入门教程 3、ASP.NET Core MVC路由入门

    一.前言 1.本文主要内容 ASP.NET Core MVC路由工作原理概述 ASP.NET Core MVC带路径参数的路由示例 ASP.NET Core MVC固定前/后缀的路由示例 ASP.NE ...

  7. ASP.NET Core 入门笔记4,ASP.NET Core MVC路由入门

    敲了一部分,懒得全部敲完,直接复制大佬的博客了,如有侵权,请通知我尽快删除修改 摘抄自https://www.cnblogs.com/ken-io/p/aspnet-core-tutorial-mvc ...

  8. ASP.NET CORE MVC 2.0 如何在Filter中使用依赖注入来读取AppSettings,及.NET Core控制台项目中读取AppSettings

    问: ASP.NET CORE MVC 如何在Filter中使用依赖注入来读取AppSettings 答: Dependency injection is possible in filters as ...

  9. Kubernetes中分布式存储Rook-Ceph的使用:一个ASP.NET Core MVC的案例

    在<Kubernetes中分布式存储Rook-Ceph部署快速演练>文章中,我快速介绍了Kubernetes中分布式存储Rook-Ceph的部署过程,这里介绍如何在部署于Kubernete ...

随机推荐

  1. sharepint 2013 添加subsite

    在用服务器端对象模型往里面添加subsite的时候,照着书上的代码,结果,失败.报错 not suported language. bing了半天,说是语言未支持,又是修改系统区域,显示语言等,还是失 ...

  2. MySQL数据库(1)----入门级操作

    1.在服务器主机上以 root 用户登陆,创建位于其他客户端的新用户: mysql> CREATE USER 'newuser'@'192.168.1.109' IDENTIFIED BY 'p ...

  3. 【Python】Python3基本语法入门学习

    0.Python概述 1.First Word Game 2.变量与字符串 3.improved game 4.Python数据类型 5.常用操作符 6.分支与循环 7.列表 8.元组 9.字符串内置 ...

  4. centos 开机执行的命令

    centos开机执行的命令-------待验证,因为有可能涉及到root问题,没想明白怎么输入密码 1.增加rc.local可执行权限 chmod +x /etc/rc.d/rc.local 2.在里 ...

  5. Process.start: how to get the output?

    1: Synchronous example static void runCommand() { Process process = new Process(); process.StartInfo ...

  6. Python初学者第八天 元组和字典

    8day 1.数据类型:元组 元组:有序的,不可变地数据的集合.但若包含其他可变元素,这些元素可变.显示的告诉别人,此处不可修改: a = (1,2,3,4,5,['1','a']) 2.数据类型:字 ...

  7. Struts2学习-Ioc学习

    网址:http://blog.csdn.net/qq_22654611/article/details/52606960可以在这个网址学学. 1.新建空项目,然后建立Java EE,在把Java EE ...

  8. C#中的"?"和"??"

    摘自:http://www.cnblogs.com/zxjyuan/archive/2009/10/27/1590795.html 如果你看到C#中的“?”问号脑袋里便充满问号,那么这个贴子便是为你而 ...

  9. 二、基于事件的异步编程模式(EAP)

    一.引言 在上一个专题中为大家介绍了.NET 1.0中提出来的异步编程模式--APM,虽然APM为我们实现异步编程提供了一定的支持,同时它也存在着一些明显的问题--不支持对异步操作的取消和没有提供对进 ...

  10. tcp长连接分包方法

    tcp长连接分包的四种方法1.消息长度固定2.使用特殊的字符串作为消息边界.比如http协议的headers以“\r\n”为字段的分隔符3.在每条消息的头部加一个长度字段.这是最常见的4.利用消息本身 ...