asp.net url重写
刚毕业不久那会儿,在公司干的活是利用cms做网站,有一天,客户要求添加论坛功能。由于cms没有自带此功能,我得找现有的论坛功能,然后给整合进去,整合何谈容易。公司给留的时间不多。我找到一个论坛,整合到cms中,在本地测试没有问题。我跑到客户那儿去部署,那时候,主流用的是IIS6,这时候出了一个问题,此论坛有url重新功能。当时不知道是配置还是什么问题,导致某些页面不能正常访问。我那时的急的像热锅上的蚂蚁,公司同事出谋划策也没有解决问题。客户给你部署的时间也是有限的,一旦出问题,那客户的脸色不好看,话不好听,还担心你把他的服务器搞坏。想想晚上熬夜整合论坛,几乎一夜未睡,在现场,客户提这问题,提那问题。
为什么会出现这样的事情呢?
1、老板不会给你时间,让你自己开发论坛。平常一个网站两三天就可以用cms做好,如果加论坛,顶多给你多加2天时间。
2、自己的技术水平还没有达到一个应有的阶段。
利用cms做网站赚钱,干的是一个快餐式的买卖。一个稍微复杂的网站,顶多做5天时间。简单的就2、3天时间。这也就是你为什么看到政府的网站都是千篇一律的原因,而老板赚得金盆钵满。
这样对程序员好吗?
进入这样的公司,程序员是幸运,还是不幸运呢?这要看你是一个怎样的程序员。当时,老板给的工资高一些,我是被这给吸引了,没有顾及到其它。因为年轻,因为缺钱,想多挣一点钱。这些都没有错。如果你是一个比较懒的程序员,一个不为前途着想的程序员,可以在这里利用cms做网站,当然也可以不加思索,套套模板,改改样式,就一个网站速成了。短期内看领到的工资还可以。如果你是一个比较爱思考的程序员,也许是幸运的。
我在公司里,竭尽全力地理解cms生成网站的原理。cms值得研究一下,毕竟有好多东西,那时候不知道。后来,我生成网站,并不是直接用cms,而是我做了一个程序,调用cms生成网站,这时候,我稍微改改就行了。这样我会节省一部分时间出来。这样你就多了研究cms源码的时间了。我们程序员最大的优点就是不愿意手动重复枯燥乏味的工作。于是就逼着我们自己写点代码,自动化执行,解放自身。
我想要写一篇关于url重写的文章,以纪念早年那些青涩的岁月。
时至今日,读了.net本质论,对url重写有了一定的认识,总结一下。 我们今天要谈的是写一个HttpMoudle模块来完成url的重写工作。具体需要完成以下几步:
1、地址转换
比如: /MyUrlRewriteWebSite/wbq3311/Default.aspx 这是我的url请求的原始地址,实际在.net中要处理的地址:/MyUrlRewriteWebSite/Default.aspx?Folder=wbq3311 这是转换规则,我们得把规则配置到config中。
2、webconfig中配置规则
1 <configSections> 2 <section name="myUrl" type="MyUrlRewrite.MyUrlRewriteConfigSection,MyUrlRewrite"/> 3 </configSections> 4 5 <myUrl enabled="true" rebaseClientPath="true"> 6 <rule source="(.*)/Default.aspx" destination="Default.aspx?Folder=$1"/> 7 </myUrl>
ConfigSections中加了myUrl节点,是为了程序能够非常方便地读取我们的规则。规则的配置中用了正则表达式。
3、写一个HttpMoudle模块,并配置到webConfig中
从上图中,模块在初始化的时候,给应用程序注册了两个事件。一个是BeginRequest,Application管线处理的第一个事件。在这个事件里面,我们处理url映射规则,最终通过HttpContext中的RewritePath方法实现重写。请思考一个问题:为什么要在这个事件当中处理映射规则呢?我想从用户请求到HttpApplication接管的开始,就让它变成新的地址,这样后面就按照新的地址处理了。从RewritePath方法,我们明白微软在底层上是支持url重写的。
另一个事件是PreRequestHandlerExcute,它是Application执行处理程序前引发的一个事件。
1 void Application_PreRequestHandlerExecute(object sender, EventArgs e) 2 { 3 HttpContext context = HttpContext.Current; 4 if (context.CurrentHandler is Page) 5 { 6 Page page = context.CurrentHandler as Page; 7 page.PreInit += new EventHandler(Page_PreInit); 8 } 9 }
从代码中可以看出,当前的处理程序是Page对象,换一句话说,Page就是处理程序。如下图所示:
Page对象就是带有模板控件的一个处理程序。Application在这个事件里又注册了Page对象的事件 PreInit,在PreInit中,又做了些什么事情呢?稍后再看,我们看HttpMoudle完成了后的配置:
<system.web>
<httpModules>
<add name="RewriteModule" type="MyUrlRewrite.RewriteModule, MyUrlRewrite"/>
</httpModules>
</system.web>
注意:Type以逗号分隔,前段是命名空间+类,后端是程序集名。II6和II7下面的配置有所不同,上面的配置是IIs6或者IIS7下的经典模式。IIS7下的集成模式的配置如下:
<system.webServer> <modules> <add name="RewriteModule" type="MyUrlRewrite.RewriteModule, MyUrlRewrite"/> </modules> </system.webServer>
4、 asp.net的回发
.net UI设计页面和后台处理页面是同一个类,当UI页面中的form提交表单的时候,会提交到自己的后台。用户操作页面控件,回发到服务器。此时Page对象,虽然和之前的Page对象不是同一个对象,但是它们的确长的一模一样,唯一不同的是,它们的状态不同。什么状态不同呢?每次请求的cookie、控件的状态等等可能有所不同。用户操作页面上的控件,实际上是再一次向服务器发送了请求,那这次请求的地址,是上一次从服务器上传递过来的。那么这个地址必须是原始的地址,否则,我们url重写就没有什么意义了。
我们看看PreInit事件的处理:
// 为了保证页面在生成 form 地址的时候,正确生成 // 重新将原来的地址设置回去 void Page_PreInit(object sender, EventArgs e) { HttpContext context = HttpContext.Current; if (context.Items.Contains("OriginalUrl")) { string path = context.Items["OriginalUrl"] as string; RewriteContext con = new RewriteContext(context.Request.QueryString, path); context.Items["RewriteContext"] = con; if (path.IndexOf("?") == -1) path += "?"; //context.Items["wbq3311"] = context.Request.QueryString["Folder"]; context.RewritePath(path); } }
context.RewritePath把原始地址写回去了。那这个原始地址存在哪儿?它存在context的Item集合中,名称:OriginalUrl。Item是什么类型的集合呢?
字典类型的接口,而且实现类型是Hashtable。那我们平常用的Dictionary肯定是实现了IDictionary接口:
接口使得C#类型有多重性身份。因此说,Dictionary是一个实现了IDictionary的集合类型(实现了ICollection接口),它又是可迭代的,用foreach循环遍历,因为它实现了IEnumerable。
从上面的分析知道,Application在处理的时候,把原始的地址保存到context中的Item字典集合中,然后在页面的PreInit事件中取出,最终ReWritePath重写了原始地址,这样等下一次页面请求的时候,携带的又是原始地址。这不就是Asp.net在不同页面间保持状态的一种机制吗?所以HttpContext对象非常重要,它不光携带了很多关于用户请求响应的数据,而且可以跨越不同的处理周期传递数据。
从上面种种分析描述,我们知道重写url并非易事。它需要理解Asp.net更多的内幕。
1、Http请求到HTTPRuntime,再到HttpApplication接管并调用处理器处理的管线机制。
2、HttpApplication的事件引发顺序,以及每个事件大致都做了些什么工作。
3、页面的生命周期。(我们经常看到是Page_Load事件)
好了,这就是.net url重写的一些感想和总结了,最后附上Application_BeginRequest的处理:
1 void Application_BeginRequest(object sender, EventArgs e) 2 { 3 if (!MyUrlRewrite.Enabled) 4 return; 5 6 HttpContext context = HttpContext.Current; 7 8 // 取得当前请求的路径 9 string path = context.Request.Path; 10 11 // 遍历所有的映射规则,进行映射处理 12 foreach (MyUrlRewriteConfigRule rule in MyUrlRewrite.Rules) 13 { 14 Regex regex = new Regex(MyUrlRewrite.RewriteBase + rule.Source, RegexOptions.IgnoreCase); 15 Match match = regex.Match(path); 16 if (match.Success) 17 { 18 // 映射 19 string newPath = regex.Replace(path, rule.Destination); 20 21 if (context.Request.QueryString.Count != 0) 22 { 23 string sign = (path.IndexOf('?') == -1) ? "?" : "&"; 24 newPath = newPath + sign + 25 context.Request.QueryString.ToString(); 26 } 27 28 // 为了在页面中正确生成 PostBack 地址 29 // 保存原来的请求信息 30 context.Items.Add("OriginalUrl", context.Request.RawUrl); 31 32 newPath = MyUrlRewrite.RewriteBase + newPath; 33 34 // 重写请求地址 35 context.RewritePath(newPath, MyUrlRewrite.RebaseClientPath ); 36 return; 37 } 38 } 39 }