﻿# ASP.NET MVC 5.2.3 的 View 使用 Roslyn (C# 6.0) 編譯時的問題

最近發現目前的 ASP.NET MVC 5 最新版 (v5.2.3) 在搭配 Visual Studio 2015 進行開發時，在 View 頁面中使用 @Html.IdFor() 或 @Html.NameFor() 在搭配使用特定 Lambda 語法時會輸出奇怪的字元，由於所有強型別的 HtmlHeper 表單欄位輸出的內部都會用到 IdFor() 與 Namefor() 這兩個 API，所以這個問題將會導致這些表單欄位 HTML 輸出的時候產生錯誤的 id 與 name 屬性，當表單 POST 回 Controller 時將無法正確執行模型繫結 (Model Binding)，所以會有接不到資料的情況，本篇文章將詳加說明發生的原因與暫時的解決方案。

### 重現問題

我先來重現這個問題，執行環境的基本要求如下：

-   使用 Visual Studio 2015 開發工具
-   必須從 Visual Studio 2015 建立全新的 ASP.NET MVC 5 專案

我特別撰寫了一個重新此問題的專案，有興趣的人可以到以下網址下載我的測試專案：

-   [https://github.com/doggy8088/MVC523ExpressionHelperBug](https://github.com/doggy8088/MVC523ExpressionHelperBug "https://github.com/doggy8088/MVC523ExpressionHelperBug")

這個專案內容很簡單，基本上我只寫了三個檔案：

1\. 定義一個模型類別叫做 **Person**

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/fc1fe1c211c42c21e53d-image_2866.png)

2\. 定義一個 HomeController 也很簡單，只有一個 Index 動作方法， 這裡準備了一個 **List<Person\>** 的清單資料傳給 View 使用

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/ca913c9a6357eea9e7aa-image_2867.png)

而我的 View 則是模擬為了要做多筆資料的模型繫結寫法，先把傳入的模型轉成陣列，再透過一個 **foreach** 輸出每個欄位：

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/a724248581d3beada66e-image_2868.png)

執行的時候你會看到以下畫面：

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/69b17b6f09a34acbf1a2-image_2869.png)

我們從 HTML 原始碼來看，你可以發現欄位的 **id** 與 **name** 屬性都怪怪的，這樣的名稱會導致當表單資料 POST 回 Controller 的時候無法正常執行模型繫結 (Model binding)！

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/77bf9ec9397764ca7e20-image_2870.png)

這個專案有趣的地方就在於，完全相同的程式碼，如果從 Visual Studio 2013 建立一個全新的 ASP.NET MVC 5 專案開始，我們所得到的 HTML 輸出則是正常的，以下是正常的欄位名稱輸出，這才是我們原本預期的輸出結果，而這樣的欄位名稱才能讓我們正確執行模型繫結：

![image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/9b701cade42c5a997211-image_2871.png "image")

怪了，到底是什麼東西造成了這樣的差異？為什麼一定要用 Visual Studio 2015 建立專案才會有問題呢？因為我在 [ASP.NET MVC 5 專案如何使用 C# 6.0 進行開發](/post/2015/07/29/Using-CSharp-60-in-ASPNET-MVC-5.aspx) 文章中有提到，從 Visual Studio 2015 建立的 ASP.NET MVC 5 專案，預設就是用 C# 6.0 進行編譯，而 Visual Studio 2015 的 ASP.NET MVC 5 專案範本已經內建 [Microsoft.CodeDom.Providers.DotNetCompilerPlatform](https://www.nuget.org/packages/Microsoft.CodeDom.Providers.DotNetCompilerPlatform) 這個 NuGet 套件，而這個套件的用途是提供 Roslyn 編譯器服務，他可以幫助你 ASP.NET MVC 5 的 View 在執行時可以透過 Roslyn 進行動態編譯，也就是說裝了這個套件後，你就可以在 View 裡面使用 C# 6.0 的語法。

不過，由於 Roslyn 是一套全新的 C# 編譯器，他的編譯行為跟傳統的 C# 編譯器有一點點不太一樣，當你使用 Lambda 語法的時候，當你的 Lambda 語法有用到 Closure 特性，以我們的範例專案來說，在 View 裡面我們透過一個 for 迴圈產生多個欄位，而欄位中使用到的 **@Html.TextBoxFor()** 有傳入 **i** 變數，這個變數傳入 Lambda 之後會導致 C# 編譯器會自動產生一個匿名物件，也就是 **CS$<>8\_\_locals1** 這個物件，而 ASP.NET MVC 5 的 ExpressionHelper 卻把編譯器動態產生的匿名物件名稱也給輸出了，導致這個 Bug 發生。

> **注意事項**：物件或變數名稱 (identifier) 若出現兩個底線 ( \_\_ ) 通常是編譯器或 Razor 這類動態產生程式碼工具會使用的名稱，你自己定義的變數名稱請盡量不要使用兩個底線當成變數名稱。

### 解決方案

我先來說明如何暫時解決這個問題，其實你只要透過 NuGet 套件管理員把 [Microsoft.CodeDom.Providers.DotNetCompilerPlatform](https://www.nuget.org/packages/Microsoft.CodeDom.Providers.DotNetCompilerPlatform) 套件移除，問題就可以迎刃而解，畢竟我們在 View 裡面其實不太需要用到 C# 6.0 語法，這是目前最快的解決方式。

另外一種方案，則是等待微軟推出修正版，目前 ASP.NET MVC 6 beta8 只後的版本已經確認修復了，但尚未發布的 ASP.NET MVC 5.2.4 版我從最新版[原始碼](https://github.com/aspnet/AspNetWebStack)來看 ( 看 master 分支 )，解決這個問題的程式碼還沒有被 commit 進去，所以還能難說在 ASP.NET MVC 5.2.4 是否會有修正版。

不過我自己 Fork 了一版 ASP.NET MVC 5 原始碼，並且參照 ASP.NET MVC 6 beta8 的修改方法 ( [原始碼](https://github.com/aspnet/Mvc/commit/a045324d3a7bd29f030a120d2edd90fedb3fb5be) ) 將此問題修正了，我簡單測試過覺得應該是沒啥問題。我的做法是這樣的，先 Fork 一版 ASP.NET MVC 5 的原始碼，從 **v3.2.3** 標籤 (Tag) 建立一個 ExpressionHelperFixed 分支 (這裡的最新版就是我修正過的版本)，並將相關程式碼修正，我只改了 **src/System.Web.Mvc/ExpressionHelper.cs** 這個檔案而已，修改的內容很簡單，其實就是把欄位名稱輸出過濾掉編譯器自動建立的物件名稱而已，修訂紀錄可參考這裡。

> **額外說明**：由於 ASP.NET MVC 5 搭配的 Razor 版本是 v3 版本，所以在 [ASP.NET MVC 5 原始碼專案](https://github.com/aspnet/AspNetWebStack)上若看到 **v3.2.3** 的標籤，其實就代表這是 ASP.NET MVC 5.2.3 版本的意思，我也是摸索了好久才搞懂的，似乎沒看到有問題說明這個命名規則。

若要取得我的這個原始碼專案，只要連到我 Fork 的專案原始碼頁面，執行 git clone 將專案下載，並切換到 ExpressionHelperFixed 分支 ( git checkout ExpressionHelperFixed ) 即可取得原始碼。

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/25ee541a4ae5e643d91b-image_2872.png)

這個專案是基於 ASP.NET MVC 5.2.3 去修改的，所以你只要在重新編譯之後將 System.Web.Mvc.dll 與 System.Web.Mvc.pdb 複製到你網站的 bin 目錄下，這個 Bug 就算解了。

雖然套用這段程式可以解決，我也只是 Coding for fun 而已，可以透過開源碼自己動手解決問題真的蠻過癮的。但我還是覺得還是移掉 [Microsoft.CodeDom.Providers.DotNetCompilerPlatform](https://www.nuget.org/packages/Microsoft.CodeDom.Providers.DotNetCompilerPlatform) 套件比較簡單啦，哈哈。

**2018/07/14 補充：此問題已經在 ASP.NET MVC 5.2.5 版本修復，只要透過 NuGet 將 [Microsoft.AspNet.Mvc](https://www.nuget.org/packages/Microsoft.AspNet.Mvc/) 套件升級到最新版即可解決。**

**相關連結**

-   [ExpressionHelper contains a compatibility bug with Roslyn compiler · Issue #117 · aspnet/AspNetWebStack](https://github.com/aspnet/AspNetWebStack/issues/117)
-   [The Will Will Web | ASP.NET MVC 5 專案如何使用 C# 6.0 進行開發](/post/2015/07/29/Using-CSharp-60-in-ASPNET-MVC-5.aspx)
-   [Issue with arrays in expression when using DotNetCompilerPlatform · Issue #2890 · aspnet/Mvc](https://github.com/aspnet/Mvc/issues/2890)
-   ASP.NET MVC / Web API / Web Pages - View Issue #2297: Issue with arrays in expression when using DotNetCompilerPlatform
