< rewriter >
< rewrite url = " ^/User/(d+)$ " to = " ~/User.aspx?id=$1 " processing = " stop " />
< rewrite url = " ^/User/(w+)$ " to = " ~/User.aspx?name=$1 " processing = " stop " />
</ rewriter >
當使用者要求「/User/jeffz」之後,頁面中的出現的程式碼卻會是<form action="/User.aspx?name=jeffz" />,這是因為在產生程式碼時,頁面會使用目前Request.Url.PathAndQuery的值來得到form元素的action。這導致了一旦PostBack,地址欄裡就會出現“User.aspx?name=jeffz”,而這個地址很可能是請求不到正確的資源的(因為可能被Rewrite到了別處,或者由於目錄級別的關係而根本沒有該資源)。在之前《 UpdatePanel與UrlRewrite 》一文中,我說可以在頁面末尾添加一行JavaScript程式碼來解決這個問題:
< script language ="javascript" type ="text/javascript">
document.getElementsByTagName( "form" )[0].action = window.location;
</ script >
這行程式碼的意圖非常明顯,將form的action修改為window.location(即瀏覽器網址列中的路徑),這樣當頁面進行PostBack時,目標位址就會是URL Rewrite之前的位址了。這種做法能夠讓程式正常運作,但是實在不能讓我滿意。為什麼?
因為太醜了。
因為我們還是把URL Rewrite之後的位址暴露給了客戶端。使用者只要裝一個HTTP嗅探器(例如著名的Fiddler),或是在IE中直接選擇檢視來源文件,我們的目標位址就毫無遮掩的顯示在使用者面前了。怎麼能讓使用者知道我們的重寫規則?我們必須解決這個問題。解決的方法很簡單,也已經非常流行了,就是使用Control Adaptor來改變Form生成時的行為。不過讓我覺得比較奇怪的是,關於這個Control Adaptor,在網路上搜到的盡是VB.NET的版本,倒是微軟主推的C#語言卻找不到。雖然只要了解一點VB.NET的語法要改寫起來並不困難,但畢竟也是個額外的工作啊。所以我現在就將這個Adaptor的C#版本程式碼貼出來,讓朋友們可以直接使用:
namespace Sample.Web.UI.Adapters
{
public class FormRewriterControlAdapter :
System.Web.UI.Adapters. ControlAdapter
{
protected override void Render( HtmlTextWriter writer)
{
base .Render( new RewriteFormHtmlTextWriter (writer));
}
}
public class RewriteFormHtmlTextWriter : HtmlTextWriter
{
public RewriteFormHtmlTextWriter( HtmlTextWriter writer)
: base (writer)
{
this .InnerWriter = writer.InnerWriter;
}
public RewriteFormHtmlTextWriter( TextWriter writer)
: base (writer)
{
this .InnerWriter = writer;
}
public override void WriteAttribute( string name, string value, bool fEncode)
{
if (name == "action" )
{
HttpContext context = HttpContext .Current;
if (context.Items[ "ActionAlreadyWritten" ] == null )
{
value = context.Request.RawUrl;
context.Items[ "ActionAlreadyWritten" ] = true ;
}
}
base .WriteAttribute(name, value, fEncode);
}
}
}
簡單的說,這個Control Adaptor其實一直在等待「action」這個屬性被輸出的那一刻,將value變成目前Request物件的RawUrl屬性。這個屬性在ASP.NET剛接受到IIS傳來的請求時就確定了,它不會隨著接下來BeginRequest中的Rewrite操作而改變,因此我們只要為Form的action輸出RawUrl就可以解決PostBack位址改變這個問題了。
不過要讓這個Control Adaptor生效,還必須在Web專案中建立一個browser文件,例如“App_BrowsersForm.browser”,在裡面寫入如下程式碼:
< browsers >
< browser refID = " Default " >
< controlAdapters >
< adapter controlType = " System.Web.UI.HtmlControls.HtmlForm "
adapterType = " Sample.Web.UI.Adapters.FormRewriterControlAdapter " />
</ controlAdapters >
</ browser >
</ browsers >
至此,在ASP.NET層面上作URL Rewrite導致PostBack位址改變的問題已經完美解決了--等等,為什麼要強調「ASP.NET層面」?沒錯,因為如果在IIS層面作URL Rewrite,這個問題依舊存在。例如您使用了IIRF做URL Rewrite,讓上面的Control Adapter生效,還是會發現頁面上PostBack的位址和客戶端請求的位址不同。難道RawUrl也變成「不忠誠」了?這不是RawUrl的緣故,而是由ASP.NET機制所決定的。為了解釋這個問題,我們重新看一下在第一篇文章《 IIS與ASP.NET 》中那幅示意圖:
IIS等級的URL Rewrite發生在上面這幅圖中步驟2之前,正因為被重新Rewrite了,所以IIS的ISAPI選擇器才會將該請求交給ASPNET ISAPI處理。換句話說,當IIS把請求交由ASP.NET引擎處理的時候,ASP.NET從IIS那裡獲得的資訊中已經是URL Rewrite之後的位址了(例如/User.aspx?name=jeffz),這樣無論在ASP.NET處理該請求的哪個環節,都無法得知IIS當初收到請求時的URL。
也就是說,其實真沒辦法了。
不過「真沒辦法」四個字是有條件的,完整地說應該是:「靠ASP.NET自身」的確「真沒辦法」了。不過如果IIS在進行URL Rewrite的時候幫我們一把,那麼情況又會如何呢? IIRF作為一個成熟的開源元件,它自然知道ASP.NET引擎,甚至所有的ISAPI處理程序都需要它的幫助,它自然知道「改出手時就出手」的道理,因此它練就了將原始地址存放在伺服器變數HTTP_X_REWRITE_URL之中的能力。不過IIRF也不會「自覺」地這麼做(多累啊),這還要我們在設定檔中提醒它:
RewriteRule ^/User/(d+)$ /User.aspx?id=$1 [I, L , U]
RewriteRule ^/User/(w+)$ /User.aspx?name=$1 [I, L, U]
請注意,我們使用了額外的Modifier。在Modifier集合中加入U表示我們需要IIRF將URL Rewrite之前的原始位址存放在伺服器變數HTTP_X_REWRITE_URL中。現在我們就可以在ASP.NET取得到這個值了,於是我們將之前的Control Adapter程式碼中的WriteAttribute方法作如下修改:
public override void WriteAttribute( string name, string value, bool fEncode)
{
if (name == "action" )
{
HttpContext context = HttpContext .Current;
if (context.Items[ "ActionAlreadyWritten" ] == null )
{
value = context.Request.ServerVariables[ "HTTP_X_REWRITE_URL" ]
?? context.Request.RawUrl;
context.Items[ "ActionAlreadyWritten" ] = true ;
}
}
base .WriteAttribute(name, value, fEncode);
}
現在action的value已經不是簡單地從RawUrl屬性中取得了,而是設法從ServerVariables集合中取得HTTP_X_REWRITE_URL變數的值,因為那裡存放了IIS所接受到的原始請求的位址。
至此,有關URL Rewrite的主要主題已經講完了,在下一篇,也就是本系列的最後一篇文章中,我們將重點看一下使用不同層面的URL Rewrite會在一些細節方面造成什麼樣的區別,以及相關的注意點。