今天收到郵件,被問到為什麼UpdatePanel如果結合了UrlRewrite就會出現問題。一開始我不以為然,由於我也曾經在UrlRewrite時使用過UpdatePanel,沒有出現過問題。但是收到對方打包的程式碼後,發現這個問題的確重現了,如果直接造訪目標頁面就不會有任何問題。因為當時在公司,沒有仔細研究出錯的原因。在回家的路上,腦中一再模擬UpdatePanel的實現過程,卻沒有察覺到有任何不妥。最後還是忍不住,坐在公車上的時候就打開筆記本,仔細的尋找問題所在。公車實在晃得厲害,還好最後在我嘔吐之前找到了問題,剛才的思考還是棋差一著。
重現問題:
現在我將重現這個問題。在原來的程式碼中使用了NBear的UrlRewriteModule,為了簡單起見,我使用了最普通的UrlRewrite的做法來得到相同的效果,盡量避免有些朋友(包括我)因為不熟悉NBear而妨礙文章內容的理解。
首先,新建一個ASP.NET AJAX Enabled Web Site。建立一個檔案~/SubFolder/Target.aspx,內容如下:
~/SubFolder/Target.aspx
<html xmlns=" http://www.w3.org/1999/xhtml " >
<head runat="server">
<title>Target Page</title>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate>
<%= DateTime.Now.ToString() %>
<asp:Button ID="Button1" runat="server" Text="Refresh" />
</ContentTemplate>
</asp:UpdatePanel>
</form>
</body>
</html>
然後再建立一個Global.asax,提供Application_BeginRequest方法,在其中實作Url Rewrite,如下:
Global.asax中Application_BeginRequest方法
void Application_BeginRequest(object sender, EventArgs e)
{
HttpContext context = (sender as HttpApplication).Context;
if (context.Request.Path.Contains("Source.aspx"))
{
context.RewritePath("SubFolder/Target.aspx", false);
}
}
這樣,當我們存取~/Source.aspx檔案時,則會被Rewrite至~/SubFolder/Target.aspx,開啟頁面,一切正常:
點擊Refresh按鈕之後,時間就被更新了。然後當我們再點擊按鈕之後,錯誤發生了:「Sys.WebForms.PageRequestManagerServerErrorException: An unknown eror occurred while processing the request on the server. The status code returned from the server was: 12031」。
分析問題:
發生這個問題的原因是因為Url Rewrite更新了Form提交的地址,而UpdatePanel又將這地址的改變反映到了頁面上。
在第一次開啟頁面時,我們可以看到頁面的來源檔案中<form />元素的action已經不是我們造訪的Source.aspx,而是Url Rewrite後的目標檔案:
form元素的action為目標頁面
…
<form name="form1" method="post" action="SubFolder/Target.aspx" id="form1">
…
</form>
……
還好我們使用了Partial Rendering,只要「目標」是正確的,UpdatePanel依舊能夠正確地發送和獲取數據,然後更新頁面。因此,在點擊Refresh按鈕之後,頁面被正確更新了。可是,我們form元素的action也變了,使用Web Development Helper和IE Dev Toolbar便一目了然:
由於我們在進行非同步PostBack時,直接存取了~/SubFolder/Target.aspx,因此在產生的Form物件其action值為Target.aspx。於是乎,UpdatePanel兢兢業業地將客戶端form元素的action也進行了修改。這樣就讓我們再次提交時訪問了一個不存在的頁面,錯誤就再所難免了。
解決問題:
既然發現了問題所在,那麼解決起來自然也會得心應手。我們只要在回應Sys.Application的load事件即可,它會在頁面第一次載入時,以及每次Partial Rendering之後被觸發,我們在這時候修改頁面中form元素的action屬性即可,如下:
相應Sys.Application的load事件
Sys.Application.add_load(function()
{
var form = Sys.WebForms.PageRequestManager.getInstance()._form;
form._initialAction = form.action = window.location.href;
});
至於為什麼應該這樣獲得頁面中的form元素,_initialAction又是什麼,以及為什麼要設定它,就要牽涉到UpdatePanel的實作方式,在這裡就不多作解釋了。只要頁面中放置了這麼一小段程式碼,這個問題就被解決了。
深入問題:
造成這個問題的原因,其實就是因為Url Rewrite之後,form元素的action並非客戶端請求的位址,而是Url Rewrite的目標位址。如果我們沒有使用Partial Rendering,而是使用了最傳統的PostBack,雖然不會造成頁面功能的破壞,但是在PostBack之後,使用者就會發現網址列的內容變了,直接變成了目標位址。這可不是我們希望看到的結果,既然Rewrite了,就把它Rewrite到底。當然,我們仍然可以使用上面提到的辦法,使用JavaScript來修改form元素的action,但是這個做法實在不夠“美觀大方”,而且用戶從HTML源文件中也可以看到我們Url Rewrite的目標地址,不是嗎?
如果我們能夠在伺服器端設定Form的action就好了,可惜System.Web.UI.HtmlControls.HtmlForm類別不允許我們這麼做。不過還好,我們用的是ASP.NET,我們用的是物件導向的程式設計模型。於是我們「繼承」System.Web.UI.HtmlControls.HtmlForm,實作一個自己的Form控制項:
繼承HtmlForm類別實作自己的From
namespace ActionlessForm {
public class Form : System.Web.UI.HtmlControls.HtmlForm
{
protected override void RenderAttributes(HtmlTextWriter writer)
{
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)
writer.WriteAttribute("id", base.ClientID);
}
}
}
然後我們就可以在頁面中使用它了。當然,在這之前,我們需要在頁面(或Web.config)裡註冊它:
使用我們自己實現的Form
<%@ Register TagPrefix="skm" Namespace="ActionlessForm"
Assembly="ActionlessForm" %>
…
<skm:Form id="Form1" method="post" runat="server">
…
</skm:Form>
…
至此,我們已經不需要在頁面裡寫一段「巧妙」的JavaScript了,Url Rewrite之後form元素的action問題被解決了。
(「深入問題」參考了MSDN上一篇文章的部分內容: http ://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspp/html/urlrewriting.asp )
http://www.cnblogs.com/JeffreyZhao/archive/2006/12/27/updatepanel_with_url_rewrite.html