重現問題:
現在我將重現那個問題。在原來的代碼中使用了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)