The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

ASP.NET MVC 開發心得分享 (3):與 WebForm 共舞

我曾經試著將一些 ASP.NET 內建的伺服器控制項(Server Control)放到 ASP.NET MVC 的 ViewPage 中,結果我發現大部分的伺服器控制項都無法正常運作。首先,用 ASP.NET 內建的伺服器控制項時,一定要使用 <form runat="server"> 包起來,否則會出現以下錯誤訊息:

型別 'TextBox' 的控制項 'ctl00_MainContent_TextBox1' 必須置於有 runat=server 的表單標記之中。

當套上 <form runat="server"> 之後才可以正常「顯示」,不過只要有任何 PostBack 的動作 ( 包括任何 Form Post 動作 ),就會發生以下錯誤:

Viewstate MAC 的驗證失敗。如果此應用程式是由 Web 伺服陣列或叢集所裝載,請確定 <machineKey> 組態指定有相同的 validationKey 和驗證演算法。AutoGenerate 無法在叢集中使用。

不要看這個錯誤訊息就認為好像很好解決 ( 當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 的情況下):

  1. 使用 ASP.NET 內建的伺服器控制項一定要加上 <form runat="server"> 標籤。
  2. 若在 ViewPage 裡加上 <form runat="server"> 標籤 不能 利用這個表單 PostBack 到同一頁!
  3. 放在 <form runat="server"> 裡面的伺服器控制項若要 PostBack 有以下幾種情況可以使用:
    1. 可以在 Button 控制項上指定 PostBackUrl 屬性指定 PostBack 到其他網頁,其他網頁可以是 WebForm 頁面或 ASP.NET MVC 頁面 ( Controller Action )
    2. 設法將 __VIEWSTATE 與 __EVENTVALIDATION 這兩個隱藏欄位刪除即可!
  4. 有錢的人可以考慮購買 TelerikRadControls 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 共舞。

 熟悉 ASP.NET WebForm 的人一定知道,只要有 NamingContainer 存在,欄位的名稱就會以錢字號 ( $ ) 分隔。

這時許願之聲就出現了:「神啊,如果這個表單 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 的人實在太少了。

相關連結