今天收到郵件,被問及為什么UpdatePanel如果結合了UrlRewrite就會出現問題。一開始我不以為然,由于我也曾經在UrlRewrite時使用過UpdatePanel,沒有出現過問題。但是收到對方打包的代碼后,發現這個問題的確重現了,如果直接訪問目標頁面就不會有任何問題。因為當時在公司,沒有仔細地去研出錯的原因。在回家的路上,腦子里一遍一遍地模擬著UpdatePanel的實現過程,卻沒有察覺到有任何不妥。最后還是忍不住,坐在公交車上的時候就打開筆記本,仔細的尋找問題所在。公交車實在晃得厲害,還好最終在我嘔吐之前找到了問題,剛才的思考還是棋差一著。
重現問題:
現在我將重現那個問題。在原來的代碼中使用了NBear的UrlRewriteModule,為了簡單起見,我使用了最普通的UrlRewrite的做法來得到相同的效果,盡量避免有些朋友(包括我)因為不熟悉NBear而妨礙文章內容的理解。
首先,新建一個asp.net Ajax Enabled Web Site。創建一個文件~/SubFolder/Target.aspx,內容如下:
~/SubFolder/Target.aspx
<html xmlns="<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://m.survivalescaperooms.com/JeffreyZhao/archive/2006/12/27/updatepanel_with_url_rewrite.html
新聞熱點
疑難解答