学习ASP.NET MVC 路由这一关是肯定必不可少的。这一节,我们就来简单介绍下MVC中的路由机制。简单的路由机制相信大家都已了解,这一节主要介绍路由中很少使用的部分。
在一个路由中,并不是所有的URL片段都要求是动态的,也可以创建具有静态片段的模式。例如以下的路由:
1 routes.MapRoute("StaticRoute", "X{controller}/{action}", 2 new { controller = "Home", action = "Index" }, 3 new string[] { "MyFirstMvcProject.Controllers" });
我们看到这条路由定义了第一个片段以字母X打头,controller的值是X字母以后的部分,第二个片段Action定义了默认值Index。这条路由将匹配任何X字母开头,controller值是取自第一个片段除字母X以外的部分。
一个有趣的例子:
设想下这样的场景,如果您的网站已经发布好久了,用户与网站之间已经形成了某种契约。例如。用户对于这个地址已经非常熟悉http://www.asp.net-example.com/Learn/Index。那么很显然控制器是Learn。如果现在您需要对程序进行重构,那么我们最好保留用户已经熟悉的链接地址。假设重构以后,新的控制器是Home。那么我们可以通过一条保留旧有URL地址的路由来实现这个功能。
1 routes.MapRoute("StaticRoute", "Learn/{action}", 2 new { controller = "Home", action = "Index" }, 3 new string[] { "MyFirstMvcProject.Controllers" });
上面的路由,我们输入Learn/Index时,路由机制会自动匹配新的控制器Home。这样一方面没有打断网站与已有用户之间形成的契约,同时又对程序功能做了一个较好的迁移。我们在浏览器中运行如下:
我们看到,我们输入/Learn/Index。路由找到的是HomeController。
我们通过定义可变长度的路由来匹配任意长度的URL。我们通过设置一个*catchall片段变量可以定义对可变片段数量的支持。请看下面的路由
1 routes.MapRoute( 2 name: "Default", 3 url: "{controller}/{action}/{id}/{*catchall}", 4 defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }, 5 namespaces: new[] { "MyFirstMvcProject.Controllers" } 6 );
我们来看下面的例子:
从这个例子我们看出,我们输入的URL是Home/Index/Id/Do/Operation。我们通过{*catchall}片段变量来获取URL中的片段。我们看到catchall获取的是Do/Operation。说明任意长度的片段变量我们都可以获取到。只是在后续处理时,我们需要自行处理诸如Do/Opeartion这样的片段变量。
从上面一个例子我们看到我们在路由里面设置了namespaces属性的值。如果我们不设置呢?看看会发生上面。请看下面的例子。
我们看到,我们的项目中存在两个HomeController控制器,浏览器路由机制解析出控制器名称是Home后,两个名称是Home的控制器,MVC框架不知道选择哪一个。这种情况下,我们需要设置下路由器的namespaces属性。这样MVC就会优先从这个命名空间下去寻找控制器。
MVC框架默认给我们提供了根据正则表达式和HTTP方法来约束路由。如果这些方法还是无法满足要求的话,我们可以通过实现IRouteConstraint接口来自定义路由约束。我们首先来看下IRouteConstraint的情况。
1 // 摘要: 2 // 确定 URL 参数是否包含此约束的有效值。 3 // 4 // 参数: 5 // httpContext: 6 // 一个对象,封装有关 HTTP 请求的信息。 7 // 8 // route: 9 // 此约束所属的对象。 10 // 11 // parameterName: 12 // 正在检查的参数的名称。 13 // 14 // values: 15 // 一个包含 URL 的参数的对象。 16 // 17 // routeDirection: 18 // 一个对象,指示在处理传入请求或生成 URL 时,是否正在执行约束检查。 19 // 20 // 返回结果: 21 // 如果 URL 参数包含有效值,则为 true;否则为 false。 22 bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection);
我们看到,这个接口就一个方法Match。MVC路由在匹配路由时会调用这个Match方法看请求的URL与当前路由是否匹配。下面我们还是通过一个例子来看一下把。请看以下例子:
1 public class UserAgentConstraint : IRouteConstraint 2 { 3 private string requestUserAgent; 4 5 public UserAgentConstraint(string userAgent) 6 { 7 this.requestUserAgent = userAgent; 8 } 9 10 public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) 11 { 12 bool result = (httpContext.Request.UserAgent != null && httpContext.Request.UserAgent.Contains(this.requestUserAgent)); 13 return result; 14 } 15 }
我们写一条全新的路由来测试下,看看效果。如下:
1 routes.MapRoute("chromeRoute", "{controller}/{action}", 2 new { controller = "Home", action = "Index" }, 3 new { customConstraint = new UserAgentConstraint("Chrome") });
这条路由使用了我们自定义的约束路由机制,我们看到我们传递了Chrome给自定义的路由匹配器。这样火狐浏览器和IE应该没法进行浏览,谷歌是可以的。打开浏览器试一下。
我们使用火狐浏览器来访问/Home/Index。会出现404的问题。
而我们使用谷歌浏览器访问时,是可以正常进行访问的。
我们知道对于所有的请求并不是都是针对控制器和动作的。也有很多是对静态文件的访问。例如图像,样式文件,JS代码文件等。默认情况下,路由系统在评估应用程序的路由之前,会考察一个URL是否匹配一个磁盘文件。如果匹配,该文件会用来对该请求进行服务。应用程序定义的路由就不会被使用。
我们可以通过设置routes.RouteExistingFiles = true来对MVC的默认机制进行修改。通过设置为true,意味着对已存在文件也进行路由。为了演示例子,我们还需要配置应用程序服务器,告诉IIS Express以便在请求到达MVC路由系统之前,不要拦截对磁盘文件的请求。请看下面的配置。
1 <add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" preCondition="" />
找到这一配置节点,将preCondition设置为""即可。
这时候我们在浏览器中输入Content/CustomerJS.js会看的下面这一幕。
我们看到,我们启用了对磁盘文件进行路由,这样MVC路由系统会设法找到名称为Content的控制器,这显然不是我们想要的。我们可以通过设置默认值来修复这个问题。
1 routes.MapRoute("StaticRoute", "Content/CustomerJS.js", 2 new { controller = "Home", action = "Index" }, 3 new string[] { "MyFirstMvcProject.Controllers" });
通过使用routes.IgnoreRoute("Content/{filename}.js")我们可以对Content文件夹下的JS文件不进行路由,这样我们上面例子的时候,就会展示JavaScript代码了。