解決的是在ASP.NET MVC使用dynamic類型Model時遇到的一個真實問題。C# 4編譯器支持dynamic類型,因此在編寫頁面模板的時候自然就可以把它作為視圖的Model類型。表現層的需求很容易改變,因此dynamic類型的Model可以減少我們反復修改強類型Model的麻煩,再配合匿名類型的使用,可謂是動靜相宜,如魚得水。不過,如果把一個匿名類型直接作為Model交給視圖去使用,在默認情況下會拋出異常。
模擬問題:
建立Controller:
public class HomeController : Controller
{
internal string Content { get; set; }
public ActionResult Index()
{
//動態實體模型,交給View顯示
return View(new {Title="你好"});
}
public ActionResult About()
{
return View();
}
}
添加View:
@{ ViewBag.Title = "Home Page"; } <h2>標題為:@Model.Title</h2>
按理來說,這么做應該一切正常,但是運行之后便會提示說Model上找不到Title成員,如圖:
問題分析:
動態類型與靜態類型有可能在生成dll過程中有細微的區別,因為通常我們用的都是靜態對象,在MVC中也多數用強類型做為視圖模型,一直過的都很太平,所以這種問題很少遇到,今天我們研究一下,自娛自樂嘛!
既然懷疑動態類型在生成的過程中有什么不為人知的東西,哪我們就得想辦法驗證一下,以證清白。動態類型編譯過程咱們是不可能看到的,也不可能跟蹤的,但我們可以從結果入手,反編譯回去,看一看它的廬山真面目。
反編譯利器我選擇ILSpy,主要我就會用這個利器,呵呵,用ILSpy查看MVC生成程序集,我們看到了驚人的一幕。
只到現在我們才豁然開朗,由於是“匿名類型”,顯然它的訪問級別應該是internal的,這樣它就能對外“隱藏”起來了。但是這就給ASP.NET MVC的視圖帶來了麻煩。因為ASP.NET MVC的視圖會在運行時動態地編譯cshtml為額外的dll,因此它是無法訪問到Controller所在程序集的internal成員的。
對症下葯
Mono.Cecil是Mono的組件之一,用來編輯.NET程序集文件。我們可以用它來打探一個.NET程序集內部的結構,就像反射那樣,只不過並不需要將程序集加載進來,Mono.Cecil只是讀取文件物理內容而已。例如,上圖所用的ILSpy便用到了Mono.Cecil。更重要的是,Mono.Cecil可以修改並保存程序集,這便可以讓我們實現各種奇形怪狀的要求。
創建一個名為DynamicBuilderApplication的控制台項目,並選擇Reference - Manage NuGet Packages
有了Mono.Cecil我們便可以修改程序集了,只需數行代碼:

1 class Program 2 { 3 static void Main(string[] args) 4 { 5 var asmFile = args[0]; 6 Console.WriteLine("Making anonymous types public for '{0}'.", asmFile); 7 var asmDef = AssemblyDefinition.ReadAssembly(asmFile, new ReaderParameters { ReadSymbols = true }); 8 var anonymousTypes = asmDef.Modules.SelectMany(m => m.Types).Where(t => t.Name.Contains("<>f__AnonymousType")); 9 foreach (var type in anonymousTypes) 10 { 11 type.IsPublic = true; 12 } 13 asmDef.Write(asmFile, new WriterParameters { WriteSymbols = true }); 14 } 15 }
首先,從參數中獲取需要修改的程序集名稱,找到所有的匿名類型,並將其訪問級別設為Public后保存。保存的時候將WriteSymbols參數設為true,這樣它也會同時修改pdb文件——這很重要,否則修改后的程序集無法和pdb文件內容相對應,便無法調試了。換句話說,Mono.Cecil也能正確處理pdb文件。
最后,只要在ASP.NET MVC網站編譯時使用這個項目即可,只需配置一下它的Post Build事件:
再次編譯並運行程序:
再拿ILSpy來檢查一番:
到此看來這個問題被我們解決了。