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

來源:互聯網
上載者:User
在進行了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”之後,頁面中的出現的代碼卻會是<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_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會在一些細節方面造成什麼樣的區別,以及相關的注意點。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.