我记录网站综合系统 -- 技术原理解析[4:我记录框架 路由系统]
源代码位置:wojilu.Web.Mvc.Processors:RouteProcessor.cs
:wojilu.Web.Mvc.Routes :RouteTool.cs路由(route)系统的目的,主要也就是将 url 解析成特定的数据,比如 url 中的 controller 是什么,action 是什么,id 又是什么,翻页是第几页等等。
如果还记得上一篇文章的话,wojilu系统通过IhttpHandler获得页面请求后,首先执行的就是RouteProcess,对的,第一步就是将请求的URL进行路由处理。
Config ->
wojilu.Web.Mvc.CoreHandler.ProcessRequest - >
wojilu.Web.Mvc.CoreHandler.ProcessRequest: ProcessContext.Begin ->
RouteProcess
我们看看RouteProcess的主方法里面干了一些什么:
1 public override void Process( ProcessContext context ) {
2
3 MvcEventPublisher.Instance.BeginParseRoute( context.ctx );
4 if (context.ctx.utils.isSkipCurrentProcessor()) return;
5
6 Route r = RouteTool.Recognize( context.ctx );
7 context.ctx.utils.setRoute( r );
8
9 IsSiteClosed( context.ctx );
10 }
2
3 MvcEventPublisher.Instance.BeginParseRoute( context.ctx );
4 if (context.ctx.utils.isSkipCurrentProcessor()) return;
5
6 Route r = RouteTool.Recognize( context.ctx );
7 context.ctx.utils.setRoute( r );
8
9 IsSiteClosed( context.ctx );
10 }
首先,通过EventPublisher向系统进行广播,我们现在要开始一个路由解析过程了,这个时候如果有其他监听者的话,可以进行一些预处理工作。接下来看看这个过程是不是可以Skip掉,在wojilu里面,可以在结束当前Process的时候设定下一个Process是否可以Skip(跳过)。当然,这里理由是第一个Process,不会Skip掉的。
在 wojilu mvc 中有一个 RouteTool,就是专门用来解析 url 的。解析完成的路由信息将放入ProcessContext传递到下一个Process中。
ProcessContext是一个巨大的内容容器,里面放着每一个请求的所有信息。在每一个Process后,都可能改变或者追加内容。
最后,判断一下,当前这个网站是否处于关闭状态。wojilu可以通过后台来控制网站是否对外公开。如果网站现在处于关闭状态的话,这个时候还需要检查一下是不是在请求网站后台登陆页面,如果是网站后台登陆页面的话,则进行后续操作;如果处于关闭状态,又非网站后台登陆页面的话,则将预设的关闭理由输出到用户请求页面,请求处理结束。
1 private void IsSiteClosed( MvcContext ctx ) {
2
3 Boolean isAdmin = isSiteAdmin( ctx.route );
4
5 if (!config.Instance.Site.IsClose || isAdmin != false) return;
6 ctx.web.ResponseWrite( config.Instance.Site.CloseReason );
7 ctx.web.ResponseEnd();
8 }
9
10 private Boolean isSiteAdmin( Route route ) {
11 String ns = route.ns;
12 if (strUtil.IsNullOrEmpty( ns )) return false;
13 if (ns.StartsWith( "Admin." ) || ns.Equals( "Admin" )) return true;
14 return false;
15 }
2
3 Boolean isAdmin = isSiteAdmin( ctx.route );
4
5 if (!config.Instance.Site.IsClose || isAdmin != false) return;
6 ctx.web.ResponseWrite( config.Instance.Site.CloseReason );
7 ctx.web.ResponseEnd();
8 }
9
10 private Boolean isSiteAdmin( Route route ) {
11 String ns = route.ns;
12 if (strUtil.IsNullOrEmpty( ns )) return false;
13 if (ns.StartsWith( "Admin." ) || ns.Equals( "Admin" )) return true;
14 return false;
15 }
Route r = RouteTool.Recognize( context.ctx );
通过 Recognize 方法,就得到了结果,也就是一个 Route 对象,各属性的值是:
ctx.route.id | 当前ID(整数) |
ctx.route.controller | 当前控制器(字符串) |
ctx.route.action | 当前控制器的方法(字符串) |
ctx.route.owner | 被访问对象(字符串) |
ctx.route.ownerType | 被访问对象的类型(site/group/user等)(字符串) |
ctx.route.appId | 当前应用程序的appId(有时候可以为0)(整数) |
ctx.route.page | 当前页面(在翻页的时候出现)(整数) |
ctx.route.query | 当前url中的查询字符串(QueryString)(字符串) |
wojilu mvc 在解析并获取到这些值之后,会传递给 MvcContext,然后框架根据 controller 和 action 的字符串,创建相应的 controller 对象。这些过程,都是框架自动处理的。所以,开发者一般不需要用到 ctx.route.controller 等的字符串的值。
下面举个例子,比如 Article/2/Edit.aspx 这个 url ,在被解析之后,其实对应着 ArticleController 的 Edit(int id) 方法;
而 Article/2.aspx 这个 url,在被解析之后,则对应着 ArticleController 的 Show(int id) 方法,默认 Show 方法不在 url 中显示。
基本上,开发者不用操心链接的生成和路由的解析这个事情。只有在你需要自定义路由解析规则的时候,才需要理解下面的内容。
下面举个例子,比如 Article/2/Edit.aspx 这个 url ,在被解析之后,其实对应着 ArticleController 的 Edit(int id) 方法;
而 Article/2.aspx 这个 url,在被解析之后,则对应着 ArticleController 的 Show(int id) 方法,默认 Show 方法不在 url 中显示。
基本上,开发者不用操心链接的生成和路由的解析这个事情。只有在你需要自定义路由解析规则的时候,才需要理解下面的内容。
一、常规路由
路由的规则是由配置文件 /framework/config/route.config 定义的:
我 们看第三行,这条规则把 url 分成两部分,controller和id部分,如果一个 url 是由两部分组成,并且第二部分符合后面的要求(requirements)是整数,那么这个 url 的第一部分就是 controller,第二部分就是 id,比如 Article/3.aspx 的控制器就是 ArticleController(Controller后缀会自动补上),ID就是 3。
下面几行路由规则的解析原理依次类推。
那 么,第一条路由(第一行那个)是什么意思?它表示,如果请求的 url 是 default.aspx ,则默认(default)使用 MainController;但它没有指定默认的action。在没有 id 的情况下,默认的 action 是 Index;如果有 id ,则默认的 action 是 Show。
二、自定义路由
有些网址比较长,比如 www.wojilu.com 的论坛网址实际是: http://www.wojilu.com/Forum1/Forum/Index 在域名后面的路径为 /Forum1/Forum/Index ,不容易记忆,这时候,自定义路由就派上了用场。请在 route.config 中增加一行:
bbs;default:{ownertype=site,owner=site}
你 可以和原先 route.config 中的第一行比较一下,其实是差不多的。它的意思是,bbs.aspx 将被解析到 site 对象的默认 controller 的默认 action 上,但是奇怪的是,没有定义默认的 controller?这是“我记录网站综合系统”在路由基础上,额外增加的一个功能,就是默认controller根据数据库中的菜单设置决定。这部分 你可以进一步参考“我记录网站综合系统”。
我们甚至可以将首页指向特定的用户,比如某用户名的友好url是 zhangsan,那么如果你第一行修改成这样:
default;default:{owner=zhangsan,ownertype=user}
那么,用户访问网站首页,就会直接看到 zhangsna 这个用户的空间首页。顺便说一下,这意味着你完全可以把 “我记录网站综合系统” 这个多用户系统当做个人独立博客使用, 比起那些单用户博客系统来说,要方便、强大得很多。这就像你把 windows 2003 当 windows xp 用,把wondows 2008当 windows 7 用一个道理。另外,这样使用还有个好处,就是一旦你的独立博客有了一定知名度,你完全可以简单修改一下路由,就把它转换成支持“论坛/SNS”等在内的多 用户系统。
下面几行路由规则的解析原理依次类推。
那 么,第一条路由(第一行那个)是什么意思?它表示,如果请求的 url 是 default.aspx ,则默认(default)使用 MainController;但它没有指定默认的action。在没有 id 的情况下,默认的 action 是 Index;如果有 id ,则默认的 action 是 Show。
二、自定义路由
有些网址比较长,比如 www.wojilu.com 的论坛网址实际是: http://www.wojilu.com/Forum1/Forum/Index 在域名后面的路径为 /Forum1/Forum/Index ,不容易记忆,这时候,自定义路由就派上了用场。请在 route.config 中增加一行:
bbs;default:{ownertype=site,owner=site}
你 可以和原先 route.config 中的第一行比较一下,其实是差不多的。它的意思是,bbs.aspx 将被解析到 site 对象的默认 controller 的默认 action 上,但是奇怪的是,没有定义默认的 controller?这是“我记录网站综合系统”在路由基础上,额外增加的一个功能,就是默认controller根据数据库中的菜单设置决定。这部分 你可以进一步参考“我记录网站综合系统”。
我们甚至可以将首页指向特定的用户,比如某用户名的友好url是 zhangsan,那么如果你第一行修改成这样:
default;default:{owner=zhangsan,ownertype=user}
那么,用户访问网站首页,就会直接看到 zhangsna 这个用户的空间首页。顺便说一下,这意味着你完全可以把 “我记录网站综合系统” 这个多用户系统当做个人独立博客使用, 比起那些单用户博客系统来说,要方便、强大得很多。这就像你把 windows 2003 当 windows xp 用,把wondows 2008当 windows 7 用一个道理。另外,这样使用还有个好处,就是一旦你的独立博客有了一定知名度,你完全可以简单修改一下路由,就把它转换成支持“论坛/SNS”等在内的多 用户系统。
三、查询参数
我们先看一下传统的方式。在传统的web开发中,一个页面向另外一个页面传递参数,是通过queryString的方式进行的,也就是在网址后面附上一个问号和几个参数列表,比如:
/product.aspx?factory=联想&category=pc
然后在服务端通过 Request.QueryString( "factory" ) 和 Request.QueryString( "category" ) 依次获得 factory 和 category 的值。
在wojilu框架中,这种方式仍然支持。但对 Request.QueryString 进行了简化,用 ctx.Get("") 代替,效果是一样的。也就是说 ctx.Get("factory") 和 Request.QueryString( "factory" ) 的值完全相同。
但是这种带问号?的参数传递方式有一些缺点,主要是SEO不够友好。传统的解决方法是使用url重写(rewrite)。大概思路这样:
1)设计一个对SEO友好的网址,比如 product/联想/pc.aspx
2)设计一个重写的正则匹配表,此处省略;
3)添加代码,在客户端请求到达服务器之后、正式解析之前,将 product/联想/pc.aspx 这样的网址解析成传统的 /product.aspx?factory=联想&category=pc 形式。
1)设计一个对SEO友好的网址,比如 product/联想/pc.aspx
2)设计一个重写的正则匹配表,此处省略;
3)添加代码,在客户端请求到达服务器之后、正式解析之前,将 product/联想/pc.aspx 这样的网址解析成传统的 /product.aspx?factory=联想&category=pc 形式。
在 wojilu 框架中,不用重写(rewrite)技术,直接使用自定义路由功能,即可达到类似效果。
1)在路由中增加如下规则项:
search/{query};default:{controller=_.Main,action=Search}
product/{factory}/{category};default:{controller=_.Product,action=List}
book/{author};default:{controller=_.Book,action=List}
2)如果是 product/苹果/电脑.aspx 类似的网址,服务端可以通过 ctx.route.getItem( "factory" ) 来获得。以下是测试代码,供参考:
result = RouteTool.RecognizePath( "search/新闻" );
Assert.AreEqual( result.controller, "Main" );
Assert.AreEqual( result.action, "Search" );
Assert.AreEqual( result.query, "新闻" );
result = RouteTool.RecognizePath( "product/苹果/电脑" );
Assert.AreEqual( result.controller, "Product" );
Assert.AreEqual( result.action, "List" );
Assert.AreEqual( result.getItem( "factory" ), "苹果" );
Assert.AreEqual( result.getItem( "category" ), "电脑" );
result = RouteTool.RecognizePath( "book/金庸" );
Assert.AreEqual( result.controller, "Book" );
Assert.AreEqual( result.action, "List" );
Assert.AreEqual( result.getItem( "author" ), "金庸" );
3)如何生成 product/苹果/电脑.aspx 类似的链接?默认的 Link.To 或者 controller 自带的to方法,并不支持字符串参数,所以你需要自己写一个帮助方法来生成链接,具体方法就是拼接字符串,比如:
public String getProductLink( String factory, String category ) {
return "/product/" + factory + "/" + category + MvcConfig.Instance.UrlExt;
public String getProductLink( String factory, String category ) {
return "/product/" + factory + "/" + category + MvcConfig.Instance.UrlExt;
}
【常见问题】
比如自定义路由——
article/{typeid}/{id};default:{controller=Article,action=List}
却发生错误——
url=http://localhost:53091/article/12/1
ex.Message=控制器不存在: article.Article
请将自定义路由的controller前面加上“下划线和点号”,如下
article/{typeid}/{id};default:{controller=_.Article,action=List}
“下划线和点号”表示前面没有namespace,否则article会被加上。
补充:关于双向路由
熟悉 rails 的用户,会关心双向路由的问题。我的答案是,目前 wojilu mvc 不支持双向路由。原因是 wojilu mvc 要支持namespace,要支持不带查询形式(即?page=99这种url)的翻页链接,所以目前没有使用双向路由。
熟悉 rails 的用户,会关心双向路由的问题。我的答案是,目前 wojilu mvc 不支持双向路由。原因是 wojilu mvc 要支持namespace,要支持不带查询形式(即?page=99这种url)的翻页链接,所以目前没有使用双向路由。
但使用上面的自定义路由功能,一样能得到类似的效果。另 外,wojilu mvc 在实践中发现,虽然不支持双向路由,但一样能工作得非常良好,“我记录网站综合系统”就是一个活生生的项目例子。还有,因为 wojilu mvc 支持 namespace,所以你完全可以通过controller灵活的目录划分方式,来获得具有 RESTfull 风格的、语义清晰的url。
官网的路由相关文章:http://www.wojilu.com/Common/Page/74
如果有关于路由的任何问题,请在以上页面留言。管理者会热情回复您的疑问。
我记录网址 www.wojilu.com
欢迎大家加入我记录开发团队