作者 朱先忠
一、 简介
ASP.NET MVC框架使用URL路由技术实现把URL映射到控制器类及相应行为中。URL路由将根据一个你定义的模式分析URL中的变量,并且自动地把这些变量以参数方式传递给一个控制器行为。
本文将着重讨论如何使用MVC框架所提供的URL路由技术。
ASP.NET中提供的URL路由机制能够使你使用URL而不必映射到一个网站中特定的物理文件。因为该URL并不必一定要映射到一个物理文件上,所以你可以在一个Web应用程序中使用更易于描述用户的行为并且更易于为用户理解的URL。
在URL路由中,你需要事先定义URL模式。该模式中将包含当你处理URL请求时需要使用的占位符。你还可以以编程方式使用URL模式从而创建相应于路由的URL,这有助于使你把ASP.NET应用程序中与创建超级链接相关的逻辑集中到一起。
典型情况下,在一个不使用URL路由的ASP.NET应用程序中,一个到来的URL请求将被映射到一个磁盘上的物理文件(例如一个.aspx文件)。例如,一个对于的http://server/application/Products.aspx?id=4的请求将被映射到服务器上的物理文件Products.aspx。文件Products.aspx中需要提供相应的代码和标记,用以生成一个发送到浏览器的响应。Web页面使用查询字符串值id=4来决定显示什么类型的内容,但是用户有可能对该值并不感兴趣。
通过使用URL路由机制,应用程序后面跟随的URL片断将通过一个你事先定义的URL模式被解析成离散的值。例如,在一个对于的http://server/application/Products/show/beverages的请求中,URL路由解析器会把Products、show和beverages传递给一个相应于此请求的专门的处理器中进行处理。相反,在一个没有使用URL路由机制的请求中,/Products/show/beverages片断将被简单地解释为应用程序中一个文件的路径信息。
值得注意的是,ASP.NET中的URL路由机制不同于其它的URL重写模式。典型情况下,URL重写并没有提供一个相应的API用于根据你提供的模式创建URL。在把该请求发送到Web页面之前,URL重写技术将通过改变该URL来处理到来的请求。例如,一个使用URL重写技术的应用程序可能把一个URL/Products/Widgets/改变为/Products.aspx?id=4。在URL重写技术中,如果你改变一个URL模式,你必须手工地更新所有的包含原始URL的超级链接。
在URL路由机制中,当处理一个到来的请求时并不改变URL,因为URL路由能够从URL中提取相应的值。当你必须创建一个URL时,你需要把参数值传递给一个方法,然后由此方法为你生成相应的URL。如果要改变该URL模式,你仅需要在一处位置改变一下,然后在该应用程序基于此模式创建的所有的链接都将自动地使用该新的模式。
二、 定义URL路由
前面我们所讨论的URL模式简称作“路由”。根据前面的描述,在一个路由中,你需要指定某些占位符。在解析URL请求时这些占位符将被映射为相应的值。值得注意的是,你还可以指定常量来匹配URL请求。
在一个路由中,你需要使用一对大括号来定义占位符(称为URL参数),即需要把它们包括在括号中。当分析URL时,字符/和.被解释为分隔符,并且从分隔符间提取出来的值被赋给占位符。在路由定义中的没有包含在大括号或方括号内的信息被作为一个常量对待。
下列表格展示了一些有效的路由模式以及与该模式相匹配的URL请求的例子。
典型情况下,你是在文件Global.asax的Application_Start事件的处理器函数中添加路由。这个方法可以确保当应用程序启动时路由是可用的,并且在对应用程序进行单元测试时还支持你直接调用该方法。如果你想在单元测试应用程序时直接调用它,那么,你必须把注册路由的方法设置为静态的并且为其提供一个参数RouteCollection。
也就是说,你是通过把各个路由添加到RouteTable类的静态Routes属性中实现最终添加路由的。其中,属性Routes是一个RouteCollection对象,其中存储了ASP.NET应用程序所有的路由。下列示例展示了来自于文件Global.asax中的代码片断,其中添加了一个Route对象;此对象中定义了两个名字分别为action和categoryName的URL参数。
protected void
Application_Start(object sender, EventArgs e)
{
RegisterRoutes(RouteTable.Routes);
}
public static void
RegisterRoutes(RouteCollection routes)
{
routes.Add(new Route
(
"Category/{action}/{categoryName}"
, new CategoryRouteHandler()
));
}
当URL路由处理URL请求时,它 试图使用请求的URL来匹配路由中定义的模式。例如,前面例子中的路由匹配就与http://server/application/Category/Show/Tools相匹配。在这种情况下,参数action被赋值为Show,而参数categoryName被赋值为Tools。
然而,上面的路由并不匹配http://server/application/Category/Add,因为此URL中并不包含路由定义中的三个URL参数值,而且并没有为该参数定义缺省的值。而且,上面的路由也不匹配URLhttp://server/application/Products/Show/Coffee,因为此URL并没有以“Category”开头—它仅仅是一个常量而不是一个参数。
如果在URL和在RouteTable集合中定义的Route对象之间不存在路由匹配,那么URL路由就不会处理请求。而是,处理被进一步传递到一个ASP.NET页面、Web服务或其它ASP.NET端点。
Route对象在路由集合中出现的顺序是非常重要的。路由匹配将从集合中的第一个路由进行,直到最后一个路由。当发生一个匹配时,则不再计算其他的路由。典型情况下,缺省的路由将是最后一个路由。
三、 默认的路由
ASP.NET MVC工程模板包括预先配置好的URL路由,它们定义在Global.asax文件的ASP.NET应用程序类中。这些路由使得你在大多数场所下不必显式地进行路由配置即可以开始构建ASP.NET MVC应用程序。
下列表格列出了ASP.NET MVC支持的默认的URL模式。
这些路由都使用了默认值加以定义,在处理请求时使用它们。每一个默认的路由值指定将使用哪一个控制器、行为和ID—如果没有在URL中提供它们的话。下列表格列出了每一种模式使用的默认值。
默认的URL模式 | 默认值 |
{controller}/{action}/{id} | action="Index" id=null(在Visual Basic中为Nothing) |
Default.aspx | controller="Home" action="Index" id=null(在Visual Basic中为Nothing) |
当处理一个来自于页面Default.aspx发出的请求时,MVC框架将总是使用默认的路由值,因为它没有提供可用于检索的URL参数。当处理一个来自于/Products/show的请求时,MVC框架将把“Products”赋值给参数controller,把“show” 赋值给参数action,而把null或Nothing赋值给参数id。
下列示例给出了Global.asax文件中默认的路由定义:
RouteTable.Routes.Add(New
Route With { _
.Url =
"{controller}/{action}/{id}", _
.Defaults = New With {.action =
"Index", .id = CStr(Nothing)}, _
.RouteHandler = New MvcRouteHandler _
})
RouteTable.Routes.Add(New
Route With { _
.Url = "default.aspx", _
.Defaults = New With {.controller =
"Home", .action = "Index", .id = CStr(Nothing)}, _
.RouteHandler = New MvcRouteHandler _
})
RouteTable.Routes.Add(new
Route
{
Url =
"{controller}/{action}/{id}",
Defaults = new { action =
"Index", id = (string)null },
RouteHandler = new MvcRouteHandler()
});
RouteTable.Routes.Add(new
Route
{
Url = "Default.aspx",
Defaults = new { controller =
"Home", action = "Index",
id = (string)null },
RouteHandler = new MvcRouteHandler()
});
【注意】对于IIS 7.0来说,不需要使用文件扩展名,但是对于IIS 6.0,你必须在URL模式中包括.mvc文件扩展名,例如{controller}.mvc/{action}/{id}。
四、 设置路由参数的缺省值
当你定义一个路由时,你可以把一个缺省的值赋给一个参数。如果URL中没有提供此参数值,那么将使用此缺省值。你可以为一个路由设置缺省值,这可以通过把一个字典赋值给Route类的Defaults属性来实现。下列示例给出了一个带有缺省值的路由。
void
Application_Start(object sender, EventArgs e)
{
RegisterRoutes(RouteTable.Routes);
}
public static void
RegisterRoutes(RouteCollection routes)
{
routes.Add(new Route
(
"Category/{action}/{categoryName}"
new CategoryRouteHandler()
)
{
Defaults = new RouteValueDictionary
{{"categoryName",
"food"}, {"action", "show"}}
}
);
}
当URL路由处理一个URL请求时,本例中所定义的路由将产生如下列表格所示的结果。
URL | 参数值 |
/Category | action = "show" categoryName =
"food" |
/Category/add | action = "add" categoryName =
"food" |
/Category/add/beverages | action = "add" categoryName=
"beverages" |
五、 处理包含未知URL片断数的URL请求
有些情况下,你需要处理一些包含未知URL片断数的URL请求。当你定义一个路由时,你可以指定最后一个参数匹配与该URL的其他部分相匹配—这只需要使用一个星号来标记此参数即可。这样的参数称作是catch-all参数。下面的示例给出了一个路由模式,它匹配一个未知数目的URL片断。
query/{queryname}/{*queryvalues}
当URL路由处理一个URL请求时,下列定义的路由将生成如下列表格所示的结果。
URL | queryvalues参数 |
/query/select/bikes/onsale | "bikes/onsale" |
/query/select/bikes | "bikes" |
/query/select | 空字符串 |
|
|
|
一个带有catch-all参数的路由将匹配在最后一个参数中不包含任何值的URL。
六、 为匹配的URL添加约束条件
除了把一个URL请求与一个URL中的参数个数定义的路由进行匹配之外,你还可以使参数中的值满足某种约束。添加约束条件的目的是为了确保URL参数中正确包含了可以正确地用于你的应用程序中的值。
当你把路由定义添加到路由集合时,你可以添加约束条件。下面的示例添加的约束用于限制在locale和year参数中应该包括什么样的值。
void
Application_Start(object sender, EventArgs e)
{
RegisterRoutes(RouteTable.Routes);
}
public static void
RegisterRoutes(RouteCollection routes)
{
routes.Add(new Route
(
"{locale}/{year}"
, new ReportRouteHandler()
)
{
Constraints = new
RouteValueDictionary
{{"locale",
"{a-z}{2}-{A-Z}{2}"},{year, @"\d{4}"}}
});
}