使用Mono.Cecil輔助ASP.NET MVC使用dynamic類型Model

作者: 老趙  發布時間: 2011-10-20 17:10  閱讀: 6748 次  推薦: 2   原文鏈接   [收藏]  

  這也是之前在珠三角技術沙龍上的示例之一,解決的是在ASP.NET MVC使用dynamic類型Model時遇到的一個真實問題。C# 4編譯器支持dynamic類型,因此在編寫頁面模板的時候自然就可以把它作為視圖的Model類型。表現層的需求很容易改變,因此dynamic類型的Model可以減少我們反復修改強類型Model的麻煩,再配合匿名類型的使用,可謂是動靜相宜,如魚得水。不過,如果把一個匿名類型直接作為Model交給視圖去使用,在默認情況下會拋出異常。我們可以用Mono.Cecil來改變這一情況。

  在視圖中使用dynamic類型Model

  我們先來重現這個問題。創建一個使用C# 4的ASP.NET MVC網站,添加如下的Controller,其中把匿名類型作為視圖Model:

public class HomeController : Controller
{
    
public ActionResult Index(string title = "<<Default>>")
    {  
        
return View(new { Title = title });
    }
}

  并定義一個Index.aspx作為視圖模板,Model類型作為dynamic,并用到Title:

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<dynamic>"%>
<!DOCTYPE html>
<html>
<head runat="server">
<title>Index</title>
</head>
<body>
<h1><%: Model.Title %></h1>
</body>
</html>

  按理來說,這么做應該一切正常,但是運行之后便會提示說Model上找不到Title成員:

  這又是什么原因呢?

  訪問級別與成員

  在C# 4出現之前,我們也完全可以構造一個Model類型作為視圖的模型,例如:

public class IndexModel
{
    
public string Title { get; set; }
}

  使用這種做法便完全可以正常運行通過了。那么為什么具體類型能夠正常工作,而匿名類型卻失敗了呢?“按常理推斷”它們不都是普通的類型,然后訪問它們的屬性嗎?我們用ILSpy查看使用匿名類型編譯后的結果,可以發現匿名類型與上面的IndexModel有一個重要的不同之處:

  由于是“匿名類型”,顯然它的訪問級別應該是internal的,這樣它就能對外“隱藏”起來了。但是這就給ASP.NET MVC的視圖帶來了麻煩。因為ASP.NET MVC的視圖會在運行時動態地編譯aspx為額外的dll,因此它是無法訪問到Controller所在程序集的internal成員的。經試驗,如果我們將之前的IndexModel的訪問級別修改為internal便會得到相同的結果。

  額外提一句,類似的代碼在Mono下卻可以運行通過。這意味著在動態訪問對象成員的時候,Mono和.NET在訪問級別方面的檢查是有所不同的。雖然在這個情景里Mono更方便,但理論上說,.NET的做法實則更合理。

  使用NuGet安裝Mono.Cecil

  Mono.Cecil是Mono的組件之一,用來編輯.NET程序集文件。我們可以用它來打探一個.NET程序集內部的結構,就像反射那樣,只不過并不需要將程序集加載進來,Mono.Cecil只是讀取文件物理內容而已。例如,上圖所用的ILSpy便用到了Mono.Cecil。更重要的是,Mono.Cecil可以修改并保存程序集,這便可以讓我們實現各種奇形怪狀的要求。像這篇文章所提到的,只不過是小試牛刀而已。

  Mono和.NET是二進制兼容的,因此我們可以直接把Mono下的Mono.Cecil.dll復制并引用到.NET程序里。不過這么做還是麻煩了,如今在.NET平臺上使用各種組件已經有更方便的做法:使用包管理器。.NET平臺下的包管理器叫做NuGet,是由SubText的作者,后來被微軟聘用作ASP.NET MVC程序經理的Phil Haack帶頭開發的開源項目。NuGet提供了Visual Studio的擴展,同時也有基于PowerShell的命令行。這里我們就從Visual Studio的擴展開始使用吧。

  創建一個名為PublicAnonymous的控制臺項目,并選擇Reference - Manage NuGet Packages:

  搜索Mono.Cecil,并安裝即可:

  NuGet會自動處理組件之間的依賴及項目的配置,您也可以自己把玩一番。

  使用Mono.Cecil修改程序集

  有了Mono.Cecil我們便可以修改程序集了,只需數行代碼:

static void Main(string[] args)
{
    var asmFile = args[0];
    Console.WriteLine("Making anonymous types public for '{0}'.", asmFile);

    var asmDef = AssemblyDefinition.ReadAssembly(asmFile, new ReaderParameters
    {
        ReadSymbols = true
    });

    var anonymousTypes = asmDef.Modules
        .SelectMany(m => m.Types)
        .Where(t => t.Name.Contains("<>f__AnonymousType"));

    foreach (var type in anonymousTypes)
    {
        type.IsPublic = true;
    }

    asmDef.Write(asmFile, new WriterParameters
    {
        WriteSymbols = true
    });
}

  首先,從參數中獲取需要修改的程序集名稱,找到所有的匿名類型,并將其訪問級別設為Public后保存。保存的時候將WriteSymbols參數設為true,這樣它也會同時修改pdb文件——這很重要,否則修改后的程序集無法和pdb文件內容相對應,便無法調試了。換句話說,Mono.Cecil也能正確處理pdb文件。

  最后,只要在ASP.NET MVC網站編譯時使用這個項目即可,只需配置一下它的Post Build事件:

  再次編譯并運行程序,即可得到正確結果。再拿ILSpy來檢查一番:

  總結

  在沙龍上,有朋友問我怎么樣可以成為一個高級.NET技術人員。我不知道“如何成為”,但我想,了解整個生態環境的發展情況,了解.NET的優勢及不足,甚至能夠了解相關領域其他技術方向的發展態勢,應該是優秀.NET程序員的特質之一吧。

  而Mono便是.NET生態環境的重要組成部分。

2
0
 
標簽:MVC Mono dynamic
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()