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的机制了解了吧:)