评论对象: 云中漫步 | 2007/4/1 12:26:02
评论言论: 3. 用网址重写引擎实现简单的网址重写
为了更好地示范网址重写引擎的运行,我们来建立一个ASP.NET Web应用程序来实现简单的网址重写引擎。假定我们为一家在线销售各类商品的公司服务,这些产品划分为以下类别:
分类编号(CategoryID) 分类名称(CategoryName)
1 饮料(Beverages)
2 调味品(Condiments)
3 工艺品(Confections)
4 日记本(Diary Products)
... ...
假定已经建立好一个名为ListProductsByCategoryID.aspx的ASP.NET页面文件,它通过查询参数获取一个分类编号,并根据此编号获取所有该分类下的所有商品。如果用户想浏览所销售的饮料类商品可以通过ListProductsByCategoryID.aspx?CategoryID=1来访问,如果用户想浏览所销售的日记本类商品可以通过ListProductsByCategoryID.aspx?CategoryID=4来访问。假定还有一个页面ListCategories.aspx,它列出所有代售商品的分类编号。
显然这里发现了一个网址重写的案例。对于用户来说他们所输入的地址不具有任何实际意义并且不具备任何“隐蔽性”,倒不如使用网址重写引擎让用户去访问/Products/Baverage.aspx地址,系统将该地址重写到ListProductsByCategoryID.aspx?CategoryID=1。我们可以在web.config文件中来完成网址重写任务:
<RewriterConfig>
<Rules>
<! —- Rules for products lister -->
<RewriterRule>
<LookFor>~/Products/Baverage.aspx</LookFor>
<SendTo>~/ListProductsByCategoryID.aspx?CategoryID=1</SendTo>
</RewriterRule>
</Rules>
</RewriterConfig>
很明显地看到,搜索用户访问的路径是否匹配/Products/Baverage.aspx,如果匹配的话,则将网址重写到/ListProductsByCategoryID.aspx?CategoryID=1。
注意:你会发现<LookFor>节点中避免直接在“Baverage.aspx”中使用句点“.”是因为<LookFor>节点的值是正则表达式的匹配模式,在正则表达式中句点符号是一个特殊字符,它表示匹配任何一个字符,也就是说如果访问BaverageQaspx时也会发生匹配,为了避免发生这个句点引起的匹配我们得在该句点符号前面加上一个“\”,表示引用句点符号。
通过该规则定义,当用户访问/Products/Baverage.aspx文件的时候,他们将看到代售的饮料类商品列表信息。图3为访问/Products/Baverage.aspx地址时的浏览器截图,注意在浏览器中地址栏上显示的是用户输入的/Products/Baverage.aspx地址,但是实际访问的地址却是网址重写后的/ListProductsByCategoryID.aspx?CategoryID=1。(事实上,在服务器上根本就不存在/Products/Baverage.aspx文件!)
图三.网址重写后的对商品分类的请求
和/Products/Baverage.aspx类似,下一步我们添加其它分类的重写规则,只需简单地在web.config文件中<Rules>中在添加其他<RewriteRule>节即可。该演示完整的重写规则集合请参考下载文档的web.config文件中的定义。
为了让该网址更具有“隐蔽性”,如果让用户把/Products/Baverage.aspx后面Baverage.aspx一段截去,在浏览器中输入/Products/来浏览产品分类列表会更好一些。乍一看,这项任务微不足道,只需添加一条网址重写规则将/Products/映射到/ListCategories.aspx即可。然而这里有一个微妙之处,你必须先创建一个/Products/目录,并在里面放一个空文件Default.aspx。
要认识为什么这些额外的步骤是必须的,先回顾一下前文。网址重写引擎是处于ASP.NET一级的,也就是说,如果ASP.NET没有获得处理请求的机会的话,网址重写引擎就不能对输入的网址请求作出判断。此外,IIS仅在请求文件包含相应扩展名时才将请求转交给ASP.NET引擎。如果用户访问/Products/,IIS并不知道其扩展名是什么,于是它检查该目录下的文件看是否包含有默认首页文件名(Default.aspx,Default.htm,Default.asp,等等,这些文件名在IIS管理工具对话框中Web服务器属性对话框中的文档标签中定义。)当然,如果/Products/目录不存在的话,IIS将返回一个HTTP 404错误。
所以我们需要创建一个/Products/目录并在该目录下额外创建一个空文件Default.aspx,IIS会检查该目录下的文件,发现有一个默认文件名Default.aspx,于是将请求转交给ASP.NET,这样,网址重写引擎才能生效。
<RewriterRule>
<LookFor>~/Products/Default.aspx</LookFor>
<SendTo>~ListCategories.aspx</SendTo>
</RewriterRule>
通过该规则,用户访问/Products/Default.aspx或者访问/Products/都可以看到如图四所示的产品分类列表。
图四.在网址上添加“隐蔽性”
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的类。