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

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

![型別 'TextBox' 的控制項 'ctl00\_MainContent\_TextBox1' 必須置於有 runat=server 的表單標記之中。](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/243d3b8d6609f815296b-image_5.png)

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

![Viewstate MAC 的驗證失敗。如果此應用程式是由 Web 伺服陣列或叢集所裝載，請確定 <machineKey> 組態指定有相同的 validationKey 和驗證演算法。AutoGenerate 無法在叢集中使用。 ](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/237c034fccb513c8154f-image_6.png)

不要看這個錯誤訊息就認為好像很好解決 ( [當ASP.NET 發生Viewstate MAC 的驗證失敗](/post/2007/11/07/ASPNET-20-and-Validation-of-ViewState-Mac-failed-exception) ) ，我除了自己費勁吃奶的力量嘗試解決外，也到國外論壇到處問過了，我想這問題應該是無解了！

最後幾經測試後證實，只有在有套用 ViewMasterPage 的情況下，才會出現此問題，若不套用 ViewMasterPage 的情況下，就可以正常使用 PostBack 且不會發生例外錯誤，但是這畢竟對 ASP.NET MVC 來說不是個「正常」的使用方式。

我最近也在試用 [Telerik](http://www.telerik.com/) 出品的 [RadControls for ASP.NET AJAX](http://www.telerik.com/products/aspnet-ajax.aspx)，賣錢的東西果然不一樣，他們的伺服器控制項不用包 <form runat="server"> 也可以正常執行，雖然少了 ViewState 可以用，但至少資料顯示的功能一樣都不少，各種與 Web UI 顯示有關的控制項都可以正常運作，甚至於他們還用 ASP.NET MVC 技術開發了一個 [Telerik MVC Forums](http://demos.telerik.com/mvc/forums) 範例網站，這個範例網站幾乎只使用 [RadControls for ASP.NET AJAX](http://www.telerik.com/products/aspnet-ajax.aspx) 控制項進行畫面呈現，這表示他們的控制項對 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.  有錢的人可以考慮購買 [Telerik](http://www.telerik.com/) 的 [RadControls for ASP.NET AJAX](http://www.telerik.com/products/aspnet-ajax.aspx) 控制項，我個人覺得還挺好用的！

以下我舉出幾個實際的範例，讓你體驗與 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 的驗證程序了。

```javascript
<script type="text/javascript">
    $(function() {
        $('#__VIEWSTATE').remove();
        $('#__EVENTVALIDATION').remove();
    });
</script>
```

\---

最後，若真的要與 WebForm 共舞，而且這支舞要跳得夠漂亮的話，還有一個 [NamingContainer](http://msdn.microsoft.com/zh-tw/library/system.web.ui.control.namingcontainer\(VS.80\).aspx) 的問題要解決！

熟悉 ASP.NET WebForm 的人一定知道，只要有 [NamingContainer](http://msdn.microsoft.com/zh-tw/library/system.web.ui.control.namingcontainer\(VS.80\).aspx) 存在，欄位的名稱就會以錢字號 ( $ ) 分隔。如下圖示，這個控制項在 ASPX 網頁中設定的是 <asp:TextBox runat="server" ID="TextBox1"\></asp:TextBox> ，但最後變成 HTML 之後欄位名稱就變成 ctl00$MainContent$TextBox1 了，所以透過這個表單 Post 出去的欄位名稱一定是 ctl00$MainContent$TextBox1，不過這樣的欄位名稱實在無法與 ASP.NET MVC 共舞。

 ![熟悉 ASP.NET WebForm 的人一定知道，只要有 NamingContainer 存在，欄位的名稱就會以錢字號 ( $ ) 分隔。](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/0557c6204284b6db2681-image_12.png)

這時許願之聲就出現了：「神啊，如果這個表單 Post 出去，可以將 ctl00$MainContent$ 刪除，且只保留 TextBox1 那該有多好啊！」

今天，我就讓你的願望成真！只要在 HttpApplication 實做 BeginRequest 事件，也就是在 Global.asax.cs 中加上一個 Application\_BeginRequest 事件，程式碼如下：

```csharp
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 的人實在太少了。

**相關連結**

-   [System.Reflection 命名空間](http://msdn.microsoft.com/zh-tw/library/system.reflection.aspx)
-   [.NET Framework 開發人員手冊 - 反映](http://msdn.microsoft.com/zh-tw/library/cxz4wk15.aspx)
-   [Control.NamingContainer 屬性](http://msdn.microsoft.com/zh-tw/library/system.web.ui.control.namingcontainer\(VS.80\).aspx)
-   [Enable using Server controls in ASP.NET MVC](https://web.archive.org/web/20080315230005/http://code.msdn.microsoft.com:80/ServerControlsInMVC)
