URL Rewrite(3):在URL Rewrite後保持PostBack地址

 

摘要:URL Rewrite後保持PostBack地址

  在進行了URL Rewrite之後,經常會遇到的問題就是頁面中PostBack的目標地址並非客戶端請求的地址,而是URL Rewrite之後的地址。以上一篇文章中的重寫爲例:

<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”之後,頁面中的出現的代碼卻會是

,這是因爲在生成代碼時,頁面會使用當前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_Browsers/Form.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會在一些細節方面造成什麼樣的區別,以及相關的注意點。

 

 

原:http://kb.cnblogs.com/page/44422/

發佈了57 篇原創文章 · 獲贊 13 · 訪問量 40萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章