asp.net MVC之创建自定义路由约束

原文链接:http://www.asp.net/learn/mvc/

1.创建自定义路由

对于简单的ASP.NET MVC应用程序,默认的路由表已经可以很好的完成工作了。然而,你可能发现会存在特定的路由需求。在这种情况下,你可以创建一个自定义路由。

设想一下,举个例子,你正在创建一个博客应用程序。你可能想要像这样处理即将到来的请求:/Archive/12-25-2009

当用户输入这一请求,你想要返回对应于日期12/25/2009的博客条目。为了处理这种类型的请求,你需要创建一个自定义路由。

代码清单1 - Global.asax(含有自定义路由)

 1 using System.Web.Mvc;
 2 using System.Web.Routing;
 3 namespace MvcApplication1
 4 {
 5     public class MvcApplication : System.Web.HttpApplication
 6     {
 7         public static void RegisterRoutes(RouteCollection routes)
 8         {
 9             routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
10             routes.MapRoute(
11                 "Blog",                                           // Route name
12                 "Archive/{entryDate}",                            // URL with parameters
13                 new { controller = "Archive", action = "Entry" }  // Parameter defaults 
14             );
15             routes.MapRoute(
16                 "Default",                                              // Route name
17                 "{controller}/{action}/{id}",                           // URL with parameters
18                 new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
19             );
20         }
21         protected void Application_Start()
22         {
23             RegisterRoutes(RouteTable.Routes);
24         }
25     }
26 }

注:添加到路由表中的路由顺序非常重要。我们的新自定义Blog路由在现有的Default路由前面。如果你将这个顺序颠倒过来,那么Default路由将总是被调用,而不是自定义路由。

自定义Blog路由匹配任何以/Archive/作为开始的请求。因此,它匹配所有下面的URL:

/Archive/12-25-2009

/Archive/10-6-2004

/Archive/apple

自定义路由将即将到来的请求映射到名为Archive的控制器,并且调用了Entry()动作。当调用Entry()方法时,条目日期作为entryDate参数进行了传递。

代码清单2 - ArchiveController.cs

 1 using System;
 2 using System.Web.Mvc;
 3 namespace MvcApplication1.Controllers
 4 {
 5     public class ArchiveController : Controller
 6     {
 7         public string Entry(DateTime entryDate)
 8         {
 9             return "You requested the entry from " + entryDate.ToString();
10         }
11     }
12 }

注意到代码清单2中的Entry()方法接受一个DateTime类型的参数。MVC框架非常的聪明,足以自动地将URL中的条目日期转换为DateTime值。如果URL中的条目日期参数不能转换为DateTime,将会引发错误(如图1)。

图1 - 转换参数时的错误

2.创建路由约束

可以使用路由约束来限制匹配特定路由的浏览器请求。可以使用正则表达式来指定一个路由约束。

举个例子,假设你已经在Global.asax文件中定义了一个路由。

代码清单1 - Global.asax.cs

1 routes.MapRoute(
2     "Product",
3     "Product/{productId}",
4     new {controller="Product", action="Details"}
5 );

代码清单1包含一个叫做Product的路由。你可以使用Product路由将浏览器请求映射到代码清单2中的ProductController。

代码清单2 - Controllers\ProductController.cs

 1 using System.Web.Mvc;
 2 namespace MvcApplication1.Controllers
 3 {
 4     public class ProductController : Controller
 5     {
 6         public ActionResult Details(int productId)
 7         {
 8             return View();
 9         }
10     }
11 }

注意到Product控制器公布的Details()动作接受一个叫做productId的参数。这个参数是一个整数参数。

定义在代码清单1中的路由将会匹配下面的任意URL:

  • /Product/23
  • /Product/7

不幸的是,路由也会匹配下面的URL:

  • /Product/blah
  • /Product/apple

因为Details()动作期望的是一个整数值,发起一个含有非整数值的请求将会导致错误。举个例子,如果你在浏览器中输入/Product/apple URL,那么你将会得到图1所示的错误页。

图1:错误页

你实际想做的是只匹配包含合适整数productId的URL。当定义路由来限制与路由相匹配的URL时,你可以使用约束。代码3中的修改后的Product路由包含了一个正则表达式,它限制了只匹配数字。

代码清单3 - Global.asax.cs

1 routes.MapRoute(
2     "Product",
3     "Product/{productId}",
4     new {controller="Product", action="Details"},
5     new {productId = @"\d+" }
6  );

正则表达式\d+匹配一个或多个整数。这个限制使得Product路由匹配了下面的URL:

  • /Product/3
  • /Product/8999

但是不匹配下面的URL:

  • /Product/apple
  • /Product

这些浏览器请求将由另外的路由处理,或者,如果没有匹配的路由,将会返回如下图错误:

3. 创建一个自定义路由约束

这部分内容的目标是演示如何创建一个自定义路由约束。自定义路由约束允许你阻止某个路径被匹配,除非满足一些自定义的条件。

在这部分内容中,我们创建了一个Localhost路由约束。Localhost路由约束只匹配本地计算机发出的请求。通过互联网发出的远程请求不会被匹配。

你可以通过实现IRouteConstraint接口来实现一个自定义路由。这是一个极其简单的接口,它只描述了一个方法:

bool Match(
    HttpContextBase httpContext,
    Route route,
    string parameterName,
    RouteValueDictionary values,
    RouteDirection routeDirection
)

这个方法返回一个布尔值。如果返回了false,与约束相关联的路由将不会匹配浏览器请求。

Localhost约束包含在了代码清单1中。

代码清单1 - LocalhostConstraint.cs

 1 using System.Web;
 2 using System.Web.Routing;
 3 namespace MvcApplication1.Constraints
 4 {
 5     public class LocalhostConstraint : IRouteConstraint
 6     {
 7         public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
 8         {
 9             return httpContext.Request.IsLocal;
10         }
11     }
12 }

代码清单1中的约束利用了HttpRequest类公布的IsLocal属性。当发出请求的IP地址是127.0.0.1或者与服务器的IP地址相同时,这个属性返回true。

你在定义于Global.asax的路由中使用了自定义约束。代码清单2中的Global.asax文件使用了Localhost约束来阻止任何人请求Admin页面,除非他们从本地服务器发出请求。举个例子,当请求来自远程服务器时,对于/Admin/DeleteAll的请求将会失败。

代码清单2 - Global.asax

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Web;
 5 using System.Web.Mvc;
 6 using System.Web.Routing;
 7 using MvcApplication1.Constraints;
 8 namespace MvcApplication1
 9 {
10     public class MvcApplication : System.Web.HttpApplication
11     {
12         public static void RegisterRoutes(RouteCollection routes){
13             routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
14             routes.MapRoute(
15                 "Admin",
16                 "Admin/{action}",
17                 new {controller="Admin"},
18                 new {isLocal=new LocalhostConstraint()}//是否本地访问验证
19             );
20             routes.MapRoute(
21                "Default",                                              
22                "{controller}/{action}/{id}",
23                new { controller = "Home", action = "Index", id = "" }  
24             );
25         }
26         protected void Application_Start(){
27             RegisterRoutes(RouteTable.Routes);
28         }
29     }
30 }

Localhost约束使用在了Admin路由的定义中。这个路由不会被远程浏览器请求所匹配。然而,应该意识到定义在Global.asax中的其他路由可能会匹配相同的请求。理解这一点很重要:约束阻止了特定路由匹配某一请求,而不是所有定义在Global.asax文件中的路由。

注意到Default路由在代码清单2中的Glabal.asax文件中被注释掉了。如果你包含Default路由,那么Default路由将会匹配对Admin控制器的请求。在这种情况下,远程用户仍然可以调用Admin控制器的动作,即使他们的请求不匹配Admin路由。

posted @ 2016-01-27 11:21  小丿悠悠  阅读(484)  评论(0编辑  收藏  举报