雖然我一般屬於只看不寫的人,但距上一篇post竟然相隔一年多,不得不感慨時間真是快得恐怖啊……
最近創業,開展了一個Web 2.0項目,之前對AJax、Url重寫技術還有所謂的XHtml+CSS+DIV只停留在理論階段,現在有機會付諸實踐了,結果在玩UrlRewrite的時候就遇到了必然會遇到的ASP.Net的HttpForm自動將真實頁面地址賦給action屬性的問題。
網上Google了一下,解決辦法就三種,添加客戶端Java腳本,重載HttpForm或者Page類
老趙這篇帖子說明了添加客戶端腳本以及重載HttpForm類的方法:http://www.cnblogs.com/JeffreyZhao/archive/2006/12/27/604373.Html
而馬哥(我恨用輩分稱呼當網名的同志……下次我改名叫干爹,嘿嘿)這篇則說明了重載Page的方法:http://www.cnblogs.com/kaima/archive/2006/12/27/604758.Html
UrlRewrite本身就有隱藏服務器處理細節的作用,這一部分要交給客戶端來做感覺很別扭,所以用Java腳本的方案很快就被我否決了。
重載HttpForm類也被我否決了,通過Reflector查看代碼,HttpForm的RenderAtttributes方法還包含處理客戶端OnSubmit事件的代碼,相當多的Web控件依賴這部分功能,去掉後就會破壞框架結構。MSDN竟然教大家用這種方法,果然MSDN還是一個需要去懷疑的東西。
馬哥介紹的方法比較合理,但是我認為重載Page類也是在一般情況下應該避免的行為,一個是決定哪個頁用新Page類哪頁不用比較麻煩,如果為了省卻麻煩,那麼在web.config裡設置pageBaseType屬性也行,但是整個網站的頁面都要過一下這個類也不太符合創業用網站的細節要求。
是不是有更好的辦法呢?還真的有,是我今天在研究 ASP.Net CSS FrIEndly Adapters 的時候醒悟的。
這個解決方案基於上面馬哥的方案修改,不過前提是必須有.Net 2.0的支持。
.NET 2.0框架給ASP.Net增加了幾個特殊目錄,其中有一個最容易被忽視的App_Browsers目錄,這裡是用來存放浏覽器定義文件的,相關說明可以參考MSDN:http://msdn2.microsoft.com/zh-cn/library/ms228122(VS.80).ASPx
在網站根目錄創建App_Browsers目錄,在裡面建立一個新的文件起名 RewriteForm.browser ,其內容如下:
<browsers>
<browser refID="Default">
<controlAdapters>
<adapter controlType="System.Web.UI.HtmlControls.HtmlForm"
adapterType="Kuang.HtmlFormAdapter" />
</controlAdapters>
</browser>
</browsers>
其中,browser節的 refID="Default" 屬性是表示擴展系統原有的Default.browser文件(位於 %windir%\Microsoft.Net\Framework\v2.0.50727\CONFIG\Browsers ),Default.browser 是全部浏覽器定義的根,具體細節請參考MSDN說明。
Adapter的意思是適配器,在.NET領域表示在兩個對象之間進行協調的對象,例如ADO.Net中眾所周知的SqlDataAdapter類就是在SqlCommand和DataSet之間協調的Adapter。
ASP.NET 2.0帶來了ControlAdapter的概念,意思是位於System.Web.UI.Control對象和ASP.Net之間的Adapter,同時也有PageAdapter,用於處理System.Web.UI.Page對象。
ControlAdapter並沒有什麼特殊的功能,只不過和重載Page類的方法相比較,前者提供了重載Web控件Render方法的能力而又不需要繼承該控件,並且可以只針對特定的控件例如這裡的HtmlForm類。而在馬哥的方法中,如果有別的控件也用了action屬性,就會被錯誤的改寫。在 RewriteForm.browser 文件中,通過 <adapter controlType="System.Web.UI.HtmlControls.HtmlForm" adapterType="Kuang.HtmlFormAdapter" /> 這行,我指定了要重載HtmlForm類,並且提供了我自定義的ControlAdapter類的類型 Kuang.HtmlFormAdapter。
下面這個是自定義的ControlAdapter類的代碼:
using System;
using System.Web.UI.Adapters;
namespace Kuang {
public class HtmlFormAdapter : ControlAdapter {
protected override void Render(System.Web.UI.HtmlTextWriter writer) {
base.Render(new FormRewriteTextWriter(writer));
}
}
}
和重載Page類的手段一樣,這段代碼也引用了一個自定義的 HtmlTextWriter 類,以下是該類的實現代碼,我自己做了一定的修改:
using System;
using System.IO;
using System.Web;
using System.Web.UI;
namespace Kuang {
public class FormRewriteTextWriter : HtmlTextWriter {
public FormRewriteTextWriter(TextWriter writer) : base(writer) {
if(writer is HtmlTextWriter)
this.InnerWriter = (writer as HtmlTextWriter).InnerWriter;
else
this.InnerWriter = writer;
}
public override void WriteAttribute(string name, string value, bool fEncode) {
HttpContext context = HttpContext.Current;
object rewroteAlready = context.Items["ForMactionRewroteAlready"];
if(name == "action" && rewroteAlready == null) {
value = context.Request.RawUrl;
context.Items["ForMactionRewroteAlready"] = new object();
}
base.WriteAttribute(name, value, fEncode);
}
}
}
把以上兩段代碼放入到網站的App_Code目錄下,就大功告成了,這個方法一個特別的優點是,不需要改動原來網站的任何代碼,連 web.config 都不用改。
大家可以嘗試一下,歡迎交流心得。
BTW:老趙提到的UpdatePanel和Form並存是不能提交第二次的問題,我沒有遇到過,難道是因為我的UpdatePanel是嵌在Form裡面的而不是Form嵌在UpdatePanel裡面的緣故?