Rss & SiteMap
华夏网·艺术论坛 http://bbs.cnrr.cn/
概要
分析如何使用微软提供的ASP.NET来对动态产生的URL地址进行网址重写。网址重写是实现一种截取网址请求并将其进行处理后重新指向到一个指定的网址的过程。作者本人在对各种实现网址重写的技术进行研究和探讨后得出的经验和方法,希望能对您有所帮助。
内容简介
稍微花点时间看一看你做的网站里头的URL地址,你看到类似这样的地址吗http://yoursite.com/info/dispEmployeeInfo.aspx?EmpID=459-099&type=summary ?也许你会出于某种目的把大量的页面文件从一个目录甚至一个网站转移到其他地方,而许多访问者出于个人兴趣或者研究目的之前就已经将原有网址收藏了起来,如果这时他从收藏夹打开该页面的时候发现这已经是坏链了。本文旨在介绍如何使用网址重写将那些“难看”的网址转换成比较有实际意义的网址,使其便于记忆。例如将http://yoursite.com/info/dispEmployeeInfo.aspx?EmpID=459-099&type=summary转换成如下地址:http://yoursite.com/ dispEmployeeInfo/459-099/summary.html 。我们甚至发现网址重写技术可以解决令人头疼的404错误,或者说它可以创建一个智能化的404错误解决方案。
如上所述,网址重写是实现一种截取网址请求并将其进行处理后重新指向到一个指定的网址的过程。在网址重写执行的期间,相应处理程序处理被请求的网址,从中提取出相关的值,然后重新指向一个新的指定地址。例如:由于一次网站目录调整,原有的 /people/ 子目录下的所有网页全部移动到/info/employees/目录,原访问者从收藏夹或者其他什么地方点击链接发出访问/people/目录下的文件的请求时,你肯定希望他还是能通过原有地址看到和原来相同的页面,但实际上看到的却是网址重写指向的新目录下的相应文件。
在老版本ASP中,使用网址重写技术的途径很少,要么写一个 ISAPI过滤器,要么购买第三方厂商提供的网址重写组件,然而在微软提供的ASP.NET下你可以通过多种方法很简单地开发出自己的网址重写软件,以满足自己各种不同的需要。本文将和你一起讨论这门针对ASP.NET开发人员的实现网址重写的技术,然后举一些网址重写实际应用的例子。在我们深入探讨网址重写技术的细节之前,我们先看一下日常使用网址重写技术实现的场景。
网址重写的一般用途
创建一个数据操作的ASP.NET程序最常见的就是一个aspx页面后面带上一些查询参数集合。例如在设计一个电子商务网站的时候,假定你设计了一项功能允许用户浏览待售的商品,为了更加方便操作,你设计了一个页面displayCategory.aspx将商品按照给定的分类显示,那么该分类下的商品显示页面上应该在页面文件对应网址后面加上了一个商品分类的查询参数,例如用户要查询待售的“装饰品”,在数据库中所有的装饰品数据对应的分类编号CategoryID的值为5,那么用户会访问如下网址:http://yoursite.com/displayCategory.aspx?CategoryID=5。
创建一个包含类似这样网址的网站最终有两种结果,首先从最终用户的角度来观察,http://yoursite.com/displayCategory.aspx?CategoryID=5 这个网址有些杂乱, 可行性分析专家Jakob Neilson(主页: http://useit.com/)建议选择网址显示方式时候考虑如下要求(参考网址:http://www.useit.com/alertbox/990321.html):
· 是否简短
· 是否易于输入
· 是否将站点结构形象化
· 是否具有隐蔽性,也就是让用户通过一个虚拟的看似有意义的导航地址访问指向该地址
我想还应该在上述列表中再增加一条:是否便于记忆。http://yoursite.com/displayCategory.aspx?CategoryID=5 这个地址没有一个地方符合Neilson标准的任何一条,也不便于记忆。当然,对于有经验的网络开发专家来说,他们很熟悉这种键值对构成的查询参数结构体系,然而对于普通用户来说输入这些带有参数的网址实在是太麻烦了。
一种较好的方法就是使用一种比较直观且容易记忆的方式来将网址表示为: http://yoursite.com/products/Widgets 乍一看很容易就会推断这个网址所对应的内容极有可能会是显示装饰品(Widgets)信息,这个网址就变得更加容易记忆和传播!然后我告诉我的同事:“请查看这个网址:http://yoursite.com/products/widgets ”不用我说第二遍,她可能一次就把地址敲到浏览器上了(你也可以在亚马逊(Amazon.com)的网站上这样尝试一下)。很快就浏览器上就列出了装饰品(Widgets)的内容。这里“隐蔽性”表示:用户可以自行变更网址的结尾,例如输入:http://yoursite.com/products 就能看到全部分类相关的商品列表或者列出所有相关商品分类目录列表。
注:用上述简单的变更网址内容的方法来构思一下如今的比较流行的Blog网站生成的网址。例如:要查询2004年1月28日所发的帖子,只需输入 http://someblog.com/2004/01/28 即可,如果将网址裁减为 http://someblog.com/2004/01 则显示 2004年1月份的帖子 ,同样将月份裁减掉得到 http://someblog.com/2004 则显示出2004年全年所发的帖子。
网址重写技术除了用于将复杂的网址简单化之外,它还能用于处理因网站目录调整或者其他原因导致产生大量的无效链接和过期书签。
当一个Web请求传送到IIS会发生什么?
在探讨如何实现网址重写这项技术之前,很有必要了解一下IIS是处理所接收的Web请求的机制。 当一个Web请求到达IIS Web服务器时,IIS会根据所请求的文件后缀名来决定如何处理该请求,IIS可以处理诸如HTML页面、图片、静态内容,或者将请求转发给ISAPI应用程序,由该ISAPI应用程序处理后生成HTML静态内容返回给IIS,最后由IIS将请求结果发送回给客户端。(一个ISAPI应用程序就是一套编译好能随时在后台运行的类库,它的任务就是根据请求生成相关的内容。)
例如:如果IIS接收到一个对Info.asp的请求,它会将该请求转交给 asp.dll来处理,该ISAPI应用程序调出并执行所请求的ASP页面,然后把生成的HTML代码返回给IIS,IIS最后把内容发送回请求客户端。对于ASP.NET页面,IIS则将请求转交给名为 aspnet_isapi.dll的ISAPI应用程序来处理,该ISAPI应用程序调用托管的ASP.NET工作进程来处理该请求,并将生成的HTML代码返回给请求客户端。
你可以自定义IIS, 将某一类扩展名映射到指定的ISAPI应用程序,图一显示了IIS管理工具中的应用程序配置对话框。
图一.已配置的文件扩展名映射
关于对IIS如何管理所接收的请求的详细探讨有些超出本文内容,,重要的是要了解 ASP.NET引擎只负责处理对扩展名已经被正确配置映射到aspnet_isapi.dll的网络请求。
用ISAPI过滤器来分析请求
除了将请求的文件扩展名映射到相应的ISAPI应用程序外,IIS还执行一些其他工作。例如 IIS还主动对发出请求的客户端用户进行授权,并判断已授权用户是否对其请求的文件拥有访问权限,在一个请求过程的全部生命期内,IIS的处理经历了几个阶段,在每一个阶段IIS都生成一个事件,而该事件可以被ISAPI过滤器实时操控的。
如同ISAPI应用程序一样, ISAPI过滤器也是一块块安装在Web服务器上的非托管代码。 ISAPI应用程序用于对所接收的特定文件类型做出响应,而ISAPI过滤器含有对IIS生成的事件做出响应的代码(contain Code),甚至可以编辑进出的数据。ISAPI也含有众多应用程序,包括:
· 权限控制与授权(Authentication and Authorization)
· 日志记录与监视(Logging and Monitoring)
· HTTP内容压缩(HTTP Compression)
· 网址重写(URL Rewriting)
本文所探讨的用ASP.NET实现的网址重写技术就是基于ISAPI过滤器用于网址重写的技术内容,然而我们仍然要讨论一下究竟是使用ISAPI过滤器还是使用ASP.NET应用程序提供的技术来实现网址重写技术。
当一个请求传入ASP.NET引擎的时候会发生什么?
ASP.NET问世之前,在IIS Web服务器上的网址重写功能需要通过ISAPI过滤器来实现,自从这个家伙问世后我们就能通过ASP.NET来实现URL重写了,因为ASP.NET的解释引擎与IIS有极大的相似之处,产生这些相似性主要是因为 ASP.NET:
· 在处理接收的请求的生命期内也会产生事件;
· 允许任意数量的HttpModule操控产生的事件,这与IIS中的ISAPI过滤器类似;
· 将请求的资源委托给HttpHandler处理,这与IIS中的ISAPI应用程序类似。
和IIS一样,在一个请求的整个生命期内,ASP.NET对该请求的处理状态发出的状态改变信号引发相应的事件。例如:BeginRequest事件在ASP.NET开始响应客户端请求之始引发; AuthenticateRequest事件在ASP.NET确立用户身份后引发,当然还有诸如 AuthorizeRequest, ResolveRequestCache和EndRequest等其它很多事件,这些都是 System.Web.HttpApplication类下的事件,更多信息请参考技术文档中的类 HttpApplication概要。
如上所述,可以创建ISAPI过滤器并用于相应IIS引发的事件,同理,ASP.NET也提供了HttpModule用于响应ASP.NET引擎引发的事件,一个ASP.NET应用程序通过配置可以拥有多个 HttpModule。ASP.NET引擎每处理一个请求,便初始化一个相应配置好的HttpModule,并允许它针对请求处理期间引发的事件生成相应的事件委托。事实上ASP.NET引擎处理每一个请求调用大量的事件委托。 FormsAuthenticationModule就是众多内嵌 HttpModule中的一个,它首先检查是否使用表单授权,如果是的话,它将检查用户是否已授权,如果没有授权则自动把用户重定向到指定的登录页面。(即:在asp.net中可以直接记录并判别用户登录授权的问题了!)
回忆在IIS中,一项请求最后被转交给一个ISAPI应用程序处理,该应用程序针对每一项请求进行处理并返回相应的数据。例如,客户端发出一个访问经典ASP页面的请求,IIS将该请求转交给asp.dll程序处理,asp.dll针对该请求执行asp页面内容,并返回HTML编码。ASP.NET也使用了类似的手法,ASP.NET引擎在将这些 HttpModule初始化后,判断并决定调用相应的HttpModule来处理该请求。(问:怎么程序操作httpModule)
所有通过ASP.NET引擎解析的请求最终被送交一个HttpHandler或者HttpHandlerFactory(一个 HttpHandler只是简单地返回一个用于处理该请求的HttpHandler的实例。)最终的委托呈现并响应所请求的HTML编码,并发送回IIS,IIS则将HTML返回给请求客户端。
ASP.NET包含许多HttpHandler,例如,PageHandlerFactory是用于呈现ASP.NET页面内容, WebServiceHandlerFactory用于呈现ASP.NET Web服务的SOAP数据包,TraceHandler用于将ASP.NET请求资源的HTML标记写入trace.axd。
图二描绘了一个针对ASP.NET资源的请求所经过的处理流程。首先,IIS接收到该请求并将其转交给aspnet_isapi.dll。其次,ASP.NET引擎将一些 HttpModule初始化。最后,最终的HttpHandler被调用,生成相应的标记语言,并将其返回给IIS,最终返回到请求客户端。
图二.IIS和ASP.NET对请求的处理过程
创建并注册自定义HttpModule和HttpHandler
创建自定义HttpModule的工作相对较简单,它包括一个实现当前接口的托管类,HttpModule必须实现 System.Web.IHttpModule接口,同样HttpHandler和 HttpHandlerFactory必须分别实现System.Web.IHttpHandler接口和 System.Web.IhttpHandlerFactory接口。有关创建HttpHandler和 HttpModule的细节已经超出本书范围。
一旦HttpModule和HttpHandler被创建后,必须向Web应用程序注册。如果要向整个Web服务器 HttpModule和HttpHandler只需简单的写入machine.config文件;如果是由指定的Web应用程序调用则需在该程序的web.config配置文件中添加几行XML标记。
例如,要向指定的Web应用程序注册HttpModule和HttpHandler,只需向该Web应程序的web.config配置文件中configuration\System.Web节中添加下列几行:
<HttpModules>
<add type="type" name="name" />
</HttpModules>
其中type属性为HttpModule的标识号和类库名称, name属性则为该模块取一个较为友好的名称方便 在Global.asax调用。
HttpHandler和HttpHandlerFactory则是在web.config文件中configuration\System.Web节中添加<httpHandler>标记,例如:
<httpHandlers>
<add verb="verb" path="path" type="type" />
</HttpModules>
回忆上文, ASP.NET对每一个接收到的请求指派相应的HttpHandler来处理并呈现相应内容,该指派决定于所接收请求的verb和path的内容,verb为HTTP请求的类型:GET或者POST,path则为请求的文件的路径和文件名。如果我们打算用一个 HttpHandler来处理所有GET类型和POST类型的并且文件扩展名为.scott的内容,可以在web.config相应配置节中加入下列标记:
<httpHandlers>
<add varb="*" path=".scott" type="type" />
</httpHandlers>
其中 type是我们定义的HttpHandler的类型。
注意:在注册HttpHandler的时候必须注意HttpHandler所使用的文件扩展名必须已经在IIS中做指向ASP.NET引擎的映射,在上面.scott扩展名的例子中,如果我们所使用的.scott扩展名如果没有在IIS中做指向ASP.NET引擎的映射的话,假定对foo.scott文件发出请求,该请求将导致IIS将foo.scott文件内容直接呈现给客户端,为了能够让 HttpHandler处理该请求,必须将.scott扩展名在IIS中做指向ASP.NET引擎的映射,之后IIS才能正确地将.scott的请求转交给相应的 HttpHandler。
HttpModule | 事件 | 简介 |
FormsAuthenticationModule | AuthenticateRequest | 判断用户是否已通过表单授权方式获取授权,如果没有的话则将用户重定向到指定的登录页面 |
FileAuthorizationModule | AuthorizeRequest | 当使用Windows授权方式的时候,该HttpModule判断并确定该Microsoft Windows帐户是否对其请求的资源拥有足够的权限 |
UrlAuthorizationModule | AuthorizeRequest | 检查并确认请求者是否对所访问的网址拥有权限。该Url授权可以在web.config文件的<authorization>和<location>元素中配置 |
使用HttpHandler来调用网址重写
除了上面所述方法外,网址重写也可以放入HttpHandler或者HttpHandlerFactory中调用。 HttpHandler是一个负责针对特定请求生成相应内容的类,而HttpHandlerFactory返回一个HTTP的实例,该实例针对特定请求生成相应内容。
本节将着眼于为这些ASP.NET页面创建一个网址重写的HttpHandlerFactory。创建 HttpHandlerFactory必须实现IHTTPHandlerFactory接口,它包括一个 GetHandler()方法。ASP.NET引擎在初始化这些HttpModule后做出决定针对该请求调用相应的 HttpHandler或者HttpHandlerFactory,在调用 HttpHandlerFactory的时候,针对该Web请求以及随同的其他信息的HttpContext中经过的的 HttpHandlerFactory的GetHandler()方法将被ASP.NET引擎调用, HttpHandlerFactory必须返回一个能委托该请求的对象,并且该对象要能实现 IHttpHandler接口。
要通过一个HttpHandler来调用网址重写,可以先创建一个HttpHandlerFactory,它的 GetHandler()方法检查所请求的网址并决定是否需要调用网址重写。如果要调用网址重写的话则调用前文所述的已通过检查的 HttpContext对象的RewritePath()方法。最后该 HttpHandlerFactory返回一个由类 System.Web.UI.PageParser的GetCompiledInstance()方法返回的 HttpHandler。(这与内嵌于ASP.NET页面的HttpHandlerFactory( PageHandlerFactory)的工作原理相同。)
在所有HttpModule被初始化后,HttpHandlerFactory就开始被实例化。把网址重写放在这些事件场所的最后一个里头调用的时候,也会碰到相同的问题:文件授权将会失效。如果非要依赖于Windows验证和文件验证的时候,你可能得使用 HttpModule来调用网址重写了。
下一章我们着眼于如何构建一个可重用的网址重写引擎,使用下文所提的这些示例均以真实案例作为参照,在作者主页上提供下载。先用用一个简单的网址重写的例子来探讨如何实现网址重写,紧接着将利用网址重写引擎中正则表达式的强大处理能力来展示真正“隐蔽”的网址重写技术!
使用网址重写引擎实现简单的网址重写
为了便于在Web应用程序中实现网址重写,我构建了一个网址重写引擎,该引擎提供下列功能:
·可以在web.config文件中为页面开发者定义其所使用的网址重写引擎的规则;
·通过使用正则表达式来使所制定的网址重写规则具有更加强大的重写能力;
·能够通过简单配置即可在HttpModule和HttpHandler中使用网址重写。
本节只探讨通过HttpModule来实现网址重写,要了解如何通过HttpHandler来实现网址重写请下载本文提供的代码。
1. 设置网址重写引擎的配置信息
我们来探讨一下在web.config中网址重写规则的配置节。首先必须在web.config文件中指出是否需要在HttpHandler或者 HttpModule中调用网址重写,在web.config中,下文已经包含了两个已经被注释掉的配置节:
<!--
<HttpModules>
<add type="URLRewriter.ModuleRewriter,URLRewriter" name="ModuleRewriter"/>
</HttpModules>
-->
<!--
<httpHandlers>
<add verb="*" path="*.aspx" type="URLRewriter.RewriterFactoryHandler,URLRewriter" />
</httpHandlers>
-->
被注释掉的<HttpModules>为配置使用HttpModule调用网址重写;注释掉的<httpHandler>为配置使用 HttpHandler调用网址重写。
不论配置使用<HttpModules>还是<httpHandlers>调用网址重写,除此之外还须配置网址重写规则,一条重写规则包括两项字符串:请求URL中的查找模式和针对该模式的匹配成功后的替换字符串。该信息在web.config文件中用下列标签描述:
<RewriterConfig>
<Rules>
<RewriterRule>
<LookFor>pattern to look for</LookFor>
<SendTo>String to replace pattern with </SendTo>
</RewriterRule>
<RewriterRule>
<LookFor>pattern to look for</LookFor>
<SendTo>String to replace pattern with </SendTo>
</RewriterRule>
</Rules>
</RewriterConfig>
每一条规则都用一个<RewriterRule>元素表示,以<LookFor>节表示查询模式,当查询模式发现匹配字符串时便用<SendTo>节表示的字符串进行替换。这些规则从上到下进行查询匹配,如果找到一个匹配则按此规则执行网址重写,并且停止查找。
配置<LookFor>节要使用正则表达式来进行字符串匹配和替换。(在此我们举一个例子来说明如何使用正则表达式来对字符串进行匹配和替换。)既然该查找模式是一个正则表达式,那么要注意避开对正则表达式保留字符串的直接使用。(正则表达式的保留字符串包括有:.,?,^,$,等等,可以通过在前面加上一个反斜线来引用这些保留字符,例如\.表示引用一个句点)
2. 使用HttpModule来执行网址重写
创建一个HttpModule很简单,只要创建一个实现IHttpModule接口的类,该 IHttpModule接口定义了两个方法:
·Init(HttpApplication),该方法在HttpModule初始化时引发,通过该方法为 HttpApplication事件调用相应的事件委托;
·Dispose(),当相应请求处理结束并发送回IIS调用此方法,通过此方法执行最终所有的清理和回收程序。
为了更加方便地为网址重写创建HttpModule,从一开始我就创建一个抽象的基类( BaseModuleRewriter),该类实现了IHttpModule接口。在 Init(HttpApplication)事件中,它通过BaseModuleRewriter_AuthorizeRequest方法引发了 HttpApplication的AuthorizeRequest事件,该 BaseModuleRewriter_AuthorizeRequest方法通过该类的Rewrite()方法重写传入参数 HttpApplication对象的内部请求虚拟路径(Path)。在 BaseModuleRewriter对象中,该Rewrite()方法是抽象的,并且没有实际内容,但在继承自该类的对象中必须重载 Rewrite()方法并为该方法提供实际内容。
通过对该基类的继承,所有需要做的工作就是创建一个继承自BaseModuleRewriter的类,重载 Rewrite()方法并在该方法中添加网址重写逻辑代码。下文列出BaseModuleRewriter代码:
public abstract class BaseModuleRewriter : IHttpModule
onclick="this.style.display=''none''; Codehighlighter1_55_680_Open_Text.style.display=''none''; Codehighlighter1_55_680_Closed_Image.style.display=''inline''; Codehighlighter1_55_680_Closed_Text.style.display=''inline'';" alt="" src="http://soft.yesky.com/imagelist/06/31/7158q573bnkh.gif" align=top>onclick="this.style.display=''none''; Codehighlighter1_55_680_Closed_Text.style.display=''none''; Codehighlighter1_55_680_Open_Image.style.display=''inline''; Codehighlighter1_55_680_Open_Text.style.display=''inline'';" alt="" src="http://soft.yesky.com/imagelist/06/31/f02701a0ji2d.gif" align=top> {
onclick="this.style.display=''none''; Codehighlighter1_106_364_Open_Text.style.display=''none''; Codehighlighter1_106_364_Closed_Image.style.display=''inline''; Codehighlighter1_106_364_Closed_Text.style.display=''inline'';" alt="" src="http://soft.yesky.com/imagelist/06/31/g818508mguup.gif" align=top>onclick="this.style.display=''none''; Codehighlighter1_106_364_Closed_Text.style.display=''none''; Codehighlighter1_106_364_Open_Image.style.display=''inline''; Codehighlighter1_106_364_Open_Text.style.display=''inline'';" alt="" src="http://soft.yesky.com/imagelist/06/31/6e8y5mh50697.gif" align=top> public virtual void Init(HttpApplication app) {
// WARNING! This does not work with Windows authentication!
// If you are using Windows authentication,
// change to app.BeginRequest
app.AuthorizeRequest += new EventHandler(this.BaseModuleRewriter_AuthorizeRequest);
}
onclick="this.style.display=''none''; Codehighlighter1_401_402_Open_Text.style.display=''none''; Codehighlighter1_401_402_Closed_Image.style.display=''inline''; Codehighlighter1_401_402_Closed_Text.style.display=''inline'';" alt="" src="http://soft.yesky.com/imagelist/06/31/70wb98m5143q.gif" align=top>onclick="this.style.display=''none''; Codehighlighter1_401_402_Closed_Text.style.display=''none''; Codehighlighter1_401_402_Open_Image.style.display=''inline''; Codehighlighter1_401_402_Open_Text.style.display=''inline'';" alt="" src="http://soft.yesky.com/imagelist/06/31/240t55145s6h.gif" align=top> public virtual void Dispose() {}
onclick="this.style.display=''none''; Codehighlighter1_495_597_Open_Text.style.display=''none''; Codehighlighter1_495_597_Closed_Image.style.display=''inline''; Codehighlighter1_495_597_Closed_Text.style.display=''inline'';" alt="" src="http://soft.yesky.com/imagelist/06/31/5251hl8696y8.gif" align=top>onclick="this.style.display=''none''; Codehighlighter1_495_597_Closed_Text.style.display=''none''; Codehighlighter1_495_597_Open_Image.style.display=''inline''; Codehighlighter1_495_597_Open_Text.style.display=''inline'';" alt="" src="http://soft.yesky.com/imagelist/06/31/94cr6m02a593.gif" align=top> protected virtual void BaseModuleRewriter_AuthorizeRequest(object sender, EventArgs e) {
HttpApplication app = (HttpApplication) sender;
Rewrite(app.Request.Path, app);
}
protected abstract void Rewrite(string requestedPath, HttpApplication app);
}
注意:该BaseModuleRewriter类将网址重写放在AuthorizeRequest事件中调用,如果要使用Windows验证并使用文件验证模式时请修改代码将网址授权放在 BeginRequest或者AuthenticateRequest事件中。
ModuleRewriter继承自BaseModuleRewriter,并真正意义地实现了网址重写的操作,该类仅包含一个重载了的方法 Rewrite(),其内容如下文所示:
protected override void Rewrite(string requestedPath, System.Web.HttpApplication app)
onclick="this.style.display=''none''; Codehighlighter1_86_1078_Open_Text.style.display=''none''; Codehighlighter1_86_1078_Closed_Image.style.display=''inline''; Codehighlighter1_86_1078_Closed_Text.style.display=''inline'';" alt="" src="http://soft.yesky.com/imagelist/06/31/492ct2qhauh7.gif" align=top>onclick="this.style.display=''none''; Codehighlighter1_86_1078_Closed_Text.style.display=''none''; Codehighlighter1_86_1078_Open_Image.style.display=''inline''; Codehighlighter1_86_1078_Open_Text.style.display=''inline'';" alt="" src="http://soft.yesky.com/imagelist/06/31/742z4vx92wht.gif" align=top> {
// get the configuration rules
RewriterRuleCollection rules = RewriterConfiguration.GetConfig().Rules;
// iterate through each rule
for(int i = 0; i < rules.Count; i++)
onclick="this.style.display=''none''; Codehighlighter1_280_1076_Open_Text.style.display=''none''; Codehighlighter1_280_1076_Closed_Image.style.display=''inline''; Codehighlighter1_280_1076_Closed_Text.style.display=''inline'';" alt="" src="http://soft.yesky.com/imagelist/06/31/e31g487j3j1r.gif" align=top>onclick="this.style.display=''none''; Codehighlighter1_280_1076_Closed_Text.style.display=''none''; Codehighlighter1_280_1076_Open_Image.style.display=''inline''; Codehighlighter1_280_1076_Open_Text.style.display=''inline'';" alt="" src="http://soft.yesky.com/imagelist/06/31/682653l233o0.gif" align=top> {
// get the pattern to look for, and
// Resolve the Url (convert ~ into the appropriate directory)
string lookFor = "^" +
RewriterUtils.ResolveUrl(app.Context.Request.ApplicationPath, rules[i].LookFor) + "$";
// Create a regex (note that IgnoreCase is set )
Regex re = new Regex(lookFor, RegexOptions.IgnoreCase);
// See if a match is found
if (re.IsMatch(requestedPath))
onclick="this.style.display=''none''; Codehighlighter1_729_1070_Open_Text.style.display=''none''; Codehighlighter1_729_1070_Closed_Image.style.display=''inline''; Codehighlighter1_729_1070_Closed_Text.style.display=''inline'';" alt="" src="http://soft.yesky.com/imagelist/06/31/485hfil868ai.gif" align=top>onclick="this.style.display=''none''; Codehighlighter1_729_1070_Closed_Text.style.display=''none''; Codehighlighter1_729_1070_Open_Image.style.display=''inline''; Codehighlighter1_729_1070_Open_Text.style.display=''inline'';" alt="" src="http://soft.yesky.com/imagelist/06/31/e4r0je6c95wz.gif" align=top> {
// match found - do any replacement needed
string sendToUrl = RewriterUtils.ResolveUrl(app.Context.Request.ApplicationPath, re.Replace(requestedPath, rules[i].SendTo));
// Rewrite the URL
RewriterUtils.RewriteUrl(app.Context, sendToUrl);
break; // exit the for loop
}
}
}
该Rewriter()方法以获取web.config文件中的网址重写规则的设置为起始,它通过循环访问各条网址重写规则,每次均获取当前规则中的 LookFor属性,用正则表达式验证并判断是否查找是否对当前请求的网址是否有匹配。
如果发现一条匹配,将用当前规则的SendTo值对请求的路径执行一个正则表达式替换,替换后的地址通过参数的形式传给RewriterUtils.RewriteUrl()方法, RewriterUtils是一个帮助类,它提供一对HttpModule和 HttpHandler都可以使用的静态方法,RewriterUrl()方法只是简单地调用了 HttpContext对象的RewritePath()方法。
注意:你已经注意到了当执行正则表达式匹配和替换的时候调用了一个RewriterUtils.ResolveUrl()方法。该帮助方法简单地替换了应用程序路径中“~”的所有实例。
我们已经探讨了主要的部分,但是还有其它一些组件诸如将web.config文件中XML格式化了的网址重写规则反序列化至一个对象的类定义、通过 HttpHandlerFactory实现网址重写的类定义等。本文最后三节将通过一些真实案例来探讨网址重写的技术。
4.处理回送数据
如果要重写的网址上包含有服务器端Web Form并执行数据回送,当该Web Form回送数据时会暴露出真实的网址,也就是说,当用户访问/Products/Baverage.aspx时,浏览器上地址栏显示的也是/Products/Baverage.aspx,但是实际上是访问/ListProdutsByCategoryID.aspx?CategoryID=1的内容,如果ListProductsByCategoryID.aspx页面执行了数据回送的话,用户被数据回送定向给原始的/ListProductByCategoryID.aspx?CategoryID=1页面上,而不是/Products/Baverage.aspx页面。这虽然不是什么大问题,但是用户会觉察到点击一个按钮时网址发生了的变化,这也许会令人不安,因为如果出于网址安全的角度来说,直接把真实的网址暴露出来了。
之所以发生这种现象的原因是当Web Form在呈现之时就明确地设置其action属性为当前Request对象中文件路径的值。当然,在Web Form呈现之时,从/Produts/Baverage.aspx到/ListProductsByCategoryID.aspx?CategoryID=1的网址重写就已经执行完毕了,这意味着 Request对象所汇报的是当前用户所访问的地址是/ListProductsByCategoryID.aspx?CategoryID=1。这么看来,只需让该服务器端表单在呈现之时不呈现action属性即可解决问题了。(对浏览器来说,如果不设置action属性的话,那么在提交的时候将使用其默认值。)
然而不幸的是该Web Form不会允许你指定action属性,也不会允许你通过设置一些属性来达到禁用呈现action属性的目的。得自行继承System.Web.HtmlControls.HtmlForm这个类,并重载该类的 RenderAttribute()方法,明确指出该类不呈现acton属性。
感谢继承这个强大的功能,使得我们很简单就获取了HtmlForm这个类下所有的功能定义,只需少量几行代码就达到所需目的,完整代码如下所示:
namespace ActionlessForm
onclick="this.style.display=''none''; Codehighlighter1_25_766_Open_Text.style.display=''none''; Codehighlighter1_25_766_Closed_Image.style.display=''inline''; Codehighlighter1_25_766_Closed_Text.style.display=''inline'';" alt="" src="http://soft.yesky.com/imagelist/06/31/438dd0um4229.gif" align=top>onclick="this.style.display=''none''; Codehighlighter1_25_766_Closed_Text.style.display=''none''; Codehighlighter1_25_766_Open_Image.style.display=''inline''; Codehighlighter1_25_766_Open_Text.style.display=''inline'';" alt="" src="http://soft.yesky.com/imagelist/06/31/6s95u2a00h34.gif" align=top> {
public class Form:System.Web.UI.HtmlControls.HtmlForm
onclick="this.style.display=''none''; Codehighlighter1_88_764_Open_Text.style.display=''none''; Codehighlighter1_88_764_Closed_Image.style.display=''inline''; Codehighlighter1_88_764_Closed_Text.style.display=''inline'';" alt="" src="http://soft.yesky.com/imagelist/06/31/8u7ui48jntq6.gif" align=top>onclick="this.style.display=''none''; Codehighlighter1_88_764_Closed_Text.style.display=''none''; Codehighlighter1_88_764_Open_Image.style.display=''inline''; Codehighlighter1_88_764_Open_Text.style.display=''inline'';" alt="" src="http://soft.yesky.com/imagelist/06/31/jfs6yjt50czj.gif" align=top> {
protected override void RenderAttributes(System.Web.UI.HtmlTextWriter writer)
onclick="this.style.display=''none''; Codehighlighter1_200_754_Open_Text.style.display=''none''; Codehighlighter1_200_754_Closed_Image.style.display=''inline''; Codehighlighter1_200_754_Closed_Text.style.display=''inline'';" alt="" src="http://soft.yesky.com/imagelist/06/31/1oi544a75210.gif" align=top>onclick="this.style.display=''none''; Codehighlighter1_200_754_Closed_Text.style.display=''none''; Codehighlighter1_200_754_Open_Image.style.display=''inline''; Codehighlighter1_200_754_Open_Text.style.display=''inline'';" alt="" src="http://soft.yesky.com/imagelist/06/31/8s2j6hhb1ohk.gif" align=top> {
writer.WriteAttribute("name",this.Name);
base.Attributes.Remove("name");
writer.WriteAttribute("method",this.Method);
base.Attributes.Remove("method");
this.Attributes.Render(writer);
base.Attributes.Remove("action");
if (base.ID!=null)
onclick="this.style.display=''none''; Codehighlighter1_635_736_Open_Text.style.display=''none''; Codehighlighter1_635_736_Closed_Image.style.display=''inline''; Codehighlighter1_635_736_Closed_Text.style.display=''inline'';" alt="" src="http://soft.yesky.com/imagelist/06/31/4xh7a69754xh.gif" align=top>onclick="this.style.display=''none''; Codehighlighter1_635_736_Closed_Text.style.display=''none''; Codehighlighter1_635_736_Open_Image.style.display=''inline''; Codehighlighter1_635_736_Open_Text.style.display=''inline'';" alt="" src="http://soft.yesky.com/imagelist/06/31/4447qg924m8a.gif" align=top> {
writer.WriteAttribute("id",this.ClientID);
}
}
}
}
对RenderAttributes()方法重载的代码包含了原类HtmlForm的 RenderAttributes()方法全部的代码内容,只是简单地去掉了设置action属性这一节。
当创建并编译了这个类后,将其添加到引用目录即可在该ASP.NET Web应用程序中使用。为了将原有HtmlForm类替换,只需简单地在页面顶部添加下列代码:
onclick="this.style.display=''none''; Codehighlighter1_2_80_Open_Text.style.display=''none''; Codehighlighter1_2_80_Closed_Image.style.display=''inline''; Codehighlighter1_2_80_Closed_Text.style.display=''inline'';" alt="" src="http://soft.yesky.com/imagelist/06/31/hpom370oux0n.gif" align=top>onclick="this.style.display=''none''; Codehighlighter1_2_80_Closed_Text.style.display=''none''; Codehighlighter1_2_80_Open_Image.style.display=''inline''; Codehighlighter1_2_80_Open_Text.style.display=''inline'';" alt="" src="http://soft.yesky.com/imagelist/06/31/2de52x2t3h62.gif" align=top> <% @ Register TagPrefix = " skm " Namespace = " ActionlessForm " Assembly = " ActionlessForm " %>
然后将<Form runat=”server”>标签替换为
<skm:Form id="Form1" method="post" runat="Server">
并将结束标记</Form>替换为
<skm:Form>
你可以查看该文档相关下载中的ListProductsByCategoryID.aspx文件中的自定义Web Form,该下载已经提供了完整的Visual Studio.NET项目文件包。
注意:如果你打算进行网址重写的地址不执行数据回送,则没有必要使用该自定义Web Form的类。
创建必须的目录结构
当IIS接收到对/2004/03/19.aspx的请求时,他发现文件扩展名.aspx,便将该请求转交给ASP.NET引擎处理,在ASP.NET 引擎中传递时,该地址被重写到/ShowBlogContent.aspx?year=2004&month=3&day=19,最后用户将看到该Blog上2004年3月19日所有的帖子,但是在用户访问/2004/03/时会发生什么呢?除非已经存在一个/2004/01/的目录,否则IIS将返回一个404错误,而且该目录下还必须要有一个默认页面Default.aspx,IIS才能将请求转交给ASP.NET引擎处理。
通过这种方法你得手动为每一年的Blog创建一个年份的目录并在该年份下放置一个默认文件Default.aspx,而且还得在该年份目录下创建每一月的目录,从01、02、...、12,每一个目录下也要防止一个默认文件Default.aspx。(回想前面的例子,为了将/Products/重写到/ListCategories.aspx也是要建立一个/Products/目录并放置一个默认Default.aspx文件。
很明显,这样创建目录结构的过程是很痛苦的。解决这种问题的一个办法就是设置IIS将所有接收的请求都转交给ASP.NET引擎来处理,这种方法,甚至连访问这种地址/2004/04/,IIS都如实地将其转交给ASP.NET引擎处理,这种方法造成ASP.NET引擎得处理所有传入的请求,包括css文件,图片文件、Javascript文件以及Flash文件等等。
关于对所有类型文件的处理的详细讨论已经超出了本书范围。有关在ASP.NET Web应用程序中使用这些技术的例子请访问.Text 这个开源的Blog。.Text 可以通过配置将所有请求都转交给ASP.NET处理。它使用了一个自定义的HttpHandler来处理所有类型的文件类型,这个自定义的 HttpHandler可以识别并判断如何处理所有的文件类型。(图像文件、CSS文件等等。)
结束语
本文探讨了通过类HttpContext类的RewriteUrl()方法来实现ASP.NET一级的网址重写,正如我们所看到那样, RewriteUrl()方法在修改这个特有的HttpContext的 Request的属性时也修改了所请求的文件和路径。实际得到的效果就是在用户访问其特有的网址的时候,他实际却是在服务器端请求另一个与此不同的网址。
网址重写不但可以在HttpModule中执行,也可以在HttpHandler中运行。本文我们探讨了在一个 HttpModule中执行网址重写,也研究了一下网址重写在ASP.NET中的各个不同场所的情况。
当然,在ASP.NET一级的网址重写中,只有在IIS成功地将请求转交给ASP.NET引擎后才能成功地执行,当用户请求一个扩展名为.aspx的文件时这很自然地发生。然而,如果要让用户输入一个实际并不存在的网址,通过网址重写到另一个存在的aspx页面,你必须为该请求创建相应的目录和默认的Default.aspx页面,除非配置IIS让它把所有的请求都转交给IIS处理,但是这种方式盲目地将所有请求都转交给了ASP.NET引擎。
摘自天极