ASP.NET MVC(以下简称mvc)的其中一个特性是使用了一个新的路由组件(routing engine)来提供一种更为舒适的将URL映射到程序中的特定页面上。在mvc开发的早期微软就意识到System.Web.Routing这个基础组件不但只为mvc使用,还应该能使用在传统的asp.net模型中,以提供更简单的URL重写功能(当然微软还意识到可以把它与Dynamic Data配合使用)。因此,他们把Routing这个功能从mvc中提取出来,并且作为.net 3.5 sp1的一部分发布.
那我们来看看它的工作原理吧!
System.Web.Routing有两个核心部分:Route和Route Handler。一个route是一个简单的类,包含与请求的url想匹配的模式(pattern)。每个传入的url将会与你定义的Routes集合相匹配,只要匹配上第一个就会立刻使用该模式。一个Route看起来会像这样:
"Catalog/{Category}/{ProductId}"
这个模式将会匹配任何传入的以”/Catalog/”开头的url,比如“/Catalog/Computers/3344”就会与该模式匹配。在花括号中的字符串叫做段(segment),这些将会被记录并且在之后的route handler中使用。这些route被定义在System.Web.Routing.RouteTable类的Routes这个静态字段中,在global.asax的Application_Start方法中是这样:
void Application_Start(object sender, EventArgs e)
{
RegisterRoutes(RouteTable.Routes);
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.Add(new Route("Catalog/{Category}/{ProductId}", new CatalogRouteHandler()));
}
到这里,您可能注意到了RegisterRoutes方法中CatalogRouteHandler这个类, 为了处理传入的请求去对应我们提供的route,我们需要创建他。一个程序可以有N个这样的Route handler去处理不同类型的请求。
所有的Route Handlers都是实现了只拥有一个叫做GetHttpHandler方法的IRouteHandler接口,这个方法返回一个IHttpHandler。这个接口大家应该非常熟悉了,他也只有一个方法——带有HttpContext类型参数的ProcessRequest方法。
我们刚刚从提供的一系列模式(pattern)中去匹配了一系列url,当找到了匹配的模式后我们将使用轩昂关联的IRouteHandler去获取将要能够回应这个请求的IHttpHandler。
我们的例子IRouteHandler将要返回一个Page类的实例。上文中的GetHttpHandler方法带有一个RequestContext类型的参数。RequestContext类有2个属性:一个是类型为System.Web.HttpContextBase的HttpContext,另一个是System.Web.Routing.RouteData类型的RouteData。
System.Web.HttpContextBase是.net 3.5 sp1中增加的一个类,他是一个对以前的不方便做测试的HttpContext类的abstract wrapper。需要注意到是他属于System.Web.Abstractions程序集。所以要引用它才可以。
HttpContext属性只允许访问我们从HttpContext中收集到的信息,因此我们可以根据请求的数据自身来判断使用的route。比如如果我们想要对于http和https的请求做不同的操作,或者我们需要redirect到其他域,或者是接受从子域(sub-domain)传递过来的请求。
RouteData属性存储了所有与我们定义的route和段(segements)的数据。他有一个RouteBase类型的Route属性,他存储的是该请求所对应的route。其次,他还有一个Values属性包含有段信息的数据。比如我们请求的”/Catalog/Computers/3444”将会使Values属性是这样:
routeData.Values["Category"]=="Computers"
routeData.Values["ProductId"]=="3444"
然后我们就可以把这些值通过HttpContext的tem属性传递到页面上。比如:
public class CatalogRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
foreach (KeyValuePair<string, object> token in requestContext.RouteData.Values)
{
requestContext.HttpContext.Items.Add(token.Key, token.Value);
}
IHttpHandler result = BuildManager
.CreateInstanceFromVirtualPath("~/Product.aspx", typeof(Product)) as IHttpHandler;
return result;
}
}
接下来我们看一下routes的一些其他特性:第一个就是我们可以为routes设置默认值,比如当“Catalog/{Category}/{ProductId}”中没有ProductId段时ProductId段的默认值为0001,或者Category段没有提供时默认为Default。这个非常简单,是一个Route类构造函数的简单的overload。这部分代码大致是这样:
private static void RegisterRoutes(RouteCollection routes)
{
routes.Add(new Route("Catalog/{Category}/{ProductId}",
new RouteValueDictionary(new { Category = "Default", ProductId = "0001" }),
new CatalogRouteHandler()));
}
就像你看到的一样,我们创建了一个RouteValueDictionary,并且使用默认值复制给属性来初始化它。这个过程也可以通过使用集合初始化来完成:
private static void RegisterRoutes(RouteCollection routes)
{
routes.Add(new Route("Catalog/{Category}/{ProductId}",
new RouteValueDictionary { {"Category", "Default"}, {"ProductId", "0001"} },
new CatalogRouteHandler()));
}
现在我们看一下route约束(route constraints),约束有多种格式。比如,如果我们限制ProductId段最多由4个数字组成,那么我们可以这样实现这个约束:
routes.Add(new Route("Catalog/{Category}/{ProductId}",
new RouteValueDictionary { {"Category", "Default"}, {"ProductId", "0001"} },
new RouteValueDictionary { {"ProductId", @"\d{1,4}"} },
new CatalogRouteHandler()));
上面这个例子我们可以看到另一个只有一个ProductId项的RouteValueDictionary。当ProductId为5个数字的时候就不会匹配该route了。约束也可以是实现IRouteConstraint接口的形式,该接口有一个Match方法,传递所有与request有关的信息到类里,并且允许为route约束创建的自定义的逻辑传递到类中,是一个很强大的东东。有一个已经内建的HttpMethodConstraint允许你限制你的route区别一个给定的http verb,比如get或post。下面的代码就限制了只有get的请求才会匹配这个route:
routes.Add(new Route("Catalog/{Category}/{ProductId}",
new RouteValueDictionary { {"Category", "Default"}, {"ProductId", "0001"} },
new RouteValueDictionary { {"ProductId", @"\d{1,4}"}, {"httpMethod", new HttpMethodConstraint("get")} },
new CatalogRouteHandler()));
这里我们只是在httpMethod上做了约束,其实你可以任意调用你需要的。HttpMethodConstraint只是使用一系列的http verbs作为构造函数然后做检查。
工作原理基本就介绍完了,这里再提一个类:StopRoutingHandler。从名字就能看出来是用来停用一个route的。这个需要放在我们提供的所有route定义的顶部(因为匹配原则):
routes.Add(new Route("{service}.asmx/{*path}", new StopRoutingHandler()));
如果有一大堆的route的话,这个东东可是非常实用的。
但是这东西怎么进入asp.net的管线(pipeline)的?当然是需要HttpModule了。System.Web.Routing.UrlRoutingModule就是实现IHttpModule接口的HttpModule,他插入request pipeline然后中断asp.net框架对url的处理,而让routing去处理,如此以来routing就开始掌权了。
web.config里需要这样设置:
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
如果使用的是iis7,在handlers节里插入:
<add name="UrlRoutingHandler" preCondition="integratedMode" verb="*" path="UrlRouting.axd" type="System.Web.HttpForbiddenHandler, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
OK,现在对routing的机制了解了吧:)