我曾經試著將一些 ASP.NET 內建的伺服器控制項(Server Control)放到 ASP.NET MVC 的 ViewPage 中,結果我發現大部分的伺服器控制項都無法正常運作。首先,用 ASP.NET 內建的伺服器控制項時,一定要使用 <form runat="server"> 包起來,否則會出現以下錯誤訊息:
當套上 <form runat="server"> 之後才可以正常「顯示」,不過只要有任何 PostBack 的動作 ( 包括任何 Form Post 動作 ),就會發生以下錯誤:
不要看這個錯誤訊息就認為好像很好解決 ( 當ASP.NET 發生Viewstate MAC 的驗證失敗 ) ,我除了自己費勁吃奶的力量嘗試解決外,也到國外論壇到處問過了,我想這問題應該是無解了!
最後幾經測試後證實,只有在有套用 ViewMasterPage 的情況下,才會出現此問題,若不套用 ViewMasterPage 的情況下,就可以正常使用 PostBack 且不會發生例外錯誤,但是這畢竟對 ASP.NET MVC 來說不是個「正常」的使用方式。
我最近也在試用 Telerik 出品的 RadControls for ASP.NET AJAX,賣錢的東西果然不一樣,他們的伺服器控制項不用包 <form runat="server"> 也可以正常執行,雖然少了 ViewState 可以用,但至少資料顯示的功能一樣都不少,各種與 Web UI 顯示有關的控制項都可以正常運作,甚至於他們還用 ASP.NET MVC 技術開發了一個 Telerik MVC Forums 範例網站,這個範例網站幾乎只使用 RadControls for ASP.NET AJAX 控制項進行畫面呈現,這表示他們的控制項對 ASP.NET MVC 的支援非常友善!
我整理出幾個使用 ASP.NET 伺服器控制項的注意事項 (在沒套用 ViewMasterPage 的情況下):
- 使用 ASP.NET 內建的伺服器控制項一定要加上 <form runat="server"> 標籤。
- 若在 ViewPage 裡加上 <form runat="server"> 標籤 不能 利用這個表單 PostBack 到同一頁!
- 放在 <form runat="server"> 裡面的伺服器控制項若要 PostBack 有以下幾種情況可以使用:
- 可以在 Button 控制項上指定 PostBackUrl 屬性指定 PostBack 到其他網頁,其他網頁可以是 WebForm 頁面或 ASP.NET MVC 頁面 ( Controller Action )
- 設法將 __VIEWSTATE 與 __EVENTVALIDATION 這兩個隱藏欄位刪除即可!
- 有錢的人可以考慮購買 Telerik 的 RadControls for ASP.NET AJAX 控制項,我個人覺得還挺好用的!
以下我舉出幾個實際的範例,讓你體驗與 WebForm 共舞的感覺:
1. 執行時會掛掉的狀況 ( Button 控制項沒有加上 PostBackUrl 鐵定掛 )
<form id="form1" runat="server">
請輸入文字:<asp:TextBox runat="server" ID="TextBox2"></asp:TextBox>
<asp:Button runat="server" ID="Button2" Text="確定" />
</form>
2. 透過 PostBackUrl 指定 PostBack 到他網頁 ( WebForm 或 Controller Action 皆可 )
<form id="form2" runat="server">
請輸入文字:<asp:TextBox runat="server" ID="TextBox1"></asp:TextBox>
<asp:Button runat="server" ID="Button1" PostBackUrl="/Home/About" Text="確定" />
</form>
3. 在同一頁裡同時使用兩個 Form,一個 WebForm (也只能有一個),另一個普通的 HTML Form,兩者相安無事,使用上沒有問題!
<form id="form1" runat="server">
請輸入文字:<asp:TextBox runat="server" ID="TextBox1"></asp:TextBox>
<asp:Button runat="server" ID="Button1" PostBackUrl="/Home/About" Text="確定" />
</form>
<form id="form2" method="post">
請輸入文字:<% = Html.TextBox("TextBox1") %>
<input type="submit" value="確定" />
</form>
4. 必殺技!使用 jQuery 將 __VIEWSTATE 與 __EVENTVALIDATION 這兩個隱藏欄位刪除,運用這個技巧將 WebForm 進行「特殊處理」就可以跳過 ASP.NET 對於 ViewState 的驗證程序了。
<script type="text/javascript">
$(function() {
$('#__VIEWSTATE').remove();
$('#__EVENTVALIDATION').remove();
});
</script>
---
最後,若真的要與 WebForm 共舞,而且這支舞要跳得夠漂亮的話,還有一個 NamingContainer 的問題要解決!
熟悉 ASP.NET WebForm 的人一定知道,只要有 NamingContainer 存在,欄位的名稱就會以錢字號 ( $ ) 分隔。如下圖示,這個控制項在 ASPX 網頁中設定的是 <asp:TextBox runat="server" ID="TextBox1"></asp:TextBox> ,但最後變成 HTML 之後欄位名稱就變成 ctl00$MainContent$TextBox1 了,所以透過這個表單 Post 出去的欄位名稱一定是 ctl00$MainContent$TextBox1,不過這樣的欄位名稱實在無法與 ASP.NET MVC 共舞。
這時許願之聲就出現了:「神啊,如果這個表單 Post 出去,可以將 ctl00$MainContent$ 刪除,且只保留 TextBox1 那該有多好啊!」
今天,我就讓你的願望成真!只要在 HttpApplication 實做 BeginRequest 事件,也就是在 Global.asax.cs 中加上一個 Application_BeginRequest 事件,程式碼如下:
void Application_BeginRequest(object sender, EventArgs e)
{
var form = HttpContext.Current.Request.Form;
form.GetType().GetProperty("IsReadOnly",
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.NonPublic)
.SetValue(form, false, null);
foreach (var key in form.AllKeys.Where(key => key.Contains("$")))
{
var value = form[key];
form.Remove(key);
var newKey = key.Substring(key.LastIndexOf("$") + 1);
form.Add(newKey, value);
}
}
只要加上這段程式碼,你的 Controller Action 所接收到 Request.Form 的 Keys 就不會看見任何被 NamingContainer 附加上去的部分,換句話說,你的伺服器控制項設定什麼 ID 在 PostBack 回去後,你的 Controller Action 就會接到這個 ID 當成傳入的參數名稱。喔,天啊,傑克,這真是太神奇了!
祝各位 ASP.NET MVC 開發人員可以舞藝精進,有空沒事大家可以切磋一下舞技,因為目前在華文世界有在學習與分享 ASP.NET MVC 的人實在太少了。
相關連結