我们在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?}",然而如果根据”subAreaRoute“路由去解析Url地址http://localhost:49908/Home/Index,那么需要项目中存在一个叫Home的Area文件夹,其下面还要存在一个叫Index的SubArea文件夹,然后Index文件下还应该有一个HomeController文件,然后HomeController里面因该有一个叫Index的Action方法,然而我们的项目中并没有叫Home的Area,也没有叫Index的SubArea,所以虽然Url地址http://localhost:49908/Home/Index匹配"subAreaRoute"路由成功了,但是由于找不到Area文件夹所以最后ASP.NET Core MVC报错了,其错误描述页也显示ASP.NET Core MVC试图寻找Area文件夹的地址失败了:
那么问题是为什么现在Url地址http://localhost:49908/Home/Index会匹配"subAreaRoute"路由"{area}/{subarea}/{controller=Home}/{action=Index}/{id?}"成功了呢?
我们来分析下Url地址http://localhost:49908/Home/Index在匹配 "subAreaRoute"路由时,首先Home匹配路由参数{area},Index匹配路由参数{subarea},然后由于{controller=Home}和{action=Index}有默认参数值,所以参数{controller}就是Home,参数{action}就是Index,然后{id?}是可选参数,所以Url地址中没有对应项也无所谓,所以如此看来"subAreaRoute"路由中的所有路由参数都能从Url地址http://localhost:49908/Home/Index中找到对应项目,那么匹配当然成功!由于Url地址http://localhost:49908/Home/Index匹配"subAreaRoute"路由成功了,所以就不会在匹配"default"路由"{controller=Home}/{action=Index}/{id?}"了,开始进入寻找文件路径的环节,结果寻找文件路径失败,页面报错。。。
所以我们可以看到当去掉"subAreaRoute"路由”{area:exists}/{subarea:exists}/{controller=Home}/{action=Index}/{id?}“的:exists后缀后,相当于ASP.NET Core MVC不会关心匹配到的{area}和{subarea}路由参数是否在项目中真的有对应的Area文件夹,只要Url地址http://localhost:49908/Home/Index能提供"subAreaRoute"路由的所有路由参数值,那么路由匹配就算成功。而:exists后缀可以保证不仅路由参数能从Url地址匹配到值,还要确保路由参数值在项目中能找到真正的文件夹或文件,整个路由才算匹配成功。
我们可以再将"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匹配到的又是"default"路由"{controller=Home}/{action=Index}/{id?}"了。"default"路由"{controller=Home}/{action=Index}/{id?}"是可以找到对应的HomeController及Index这个Action和Index.cshtml视图的,所以页面显示成功。因为现在"subAreaRoute"路由”{area:exists}/{subarea}/{controller=Home}/{action=Index}/{id?}“要求不光要从Url地址http://localhost:49908/Home/Index中匹配到{area}路由参数的值,由于{area}路由参数定义了:exists后缀,还要去检查项目中是否真的有名为Home的Area文件夹,结果找不到Area文件夹,所以匹配路由"subAreaRoute"失败,继续匹配路由表的"areaRoute"路由 "{area:exists}/{controller=Home}/{action=Index}/{id?}",同理还是匹配失败,再匹配路由表的最后一个"default"路由"{controller=Home}/{action=Index}/{id?}",这时匹配"default"路由成功,成功显示页面到浏览器。
所以:exists后缀在ASP.NET Core MVC路由中有验证文件夹和文件是否存在的功能,只有当路由参数值对应的文件夹和文件在项目中确实存在时,路由匹配才算成功,否者路由匹配失败,会继续用Url去匹配 ASP.NET Core MVC路由表中的后续路由。