zoukankan      html  css  js  c++  java
  • 使用Mono.Cecil辅助ASP.NET MVC使用dynamic类型Model

    http://blog.zhaojie.me/

    使用Mono.Cecil辅助ASP.NET MVC使用dynamic类型Model

    2011-09-05 16:21 by 老赵, 4552 visits

    这也是之前在珠三角技术沙龙上的示例之一,解决的是在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成员:

    dynamic model load failed

    这又是什么原因呢?

    访问级别与成员

    在C# 4出现之前,我们也完全可以构造一个Model类型作为视图的模型,例如:

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

    使用这种做法便完全可以正常运行通过了。那么为什么具体类型能够正常工作,而匿名类型却失败了呢?“按常理推断”它们不都是普通的类型,然后访问它们的属性吗?我们用ILSpy查看使用匿名类型编译后的结果,可以发现匿名类型与上面的IndexModel有一个重要的不同之处:

    internal anonymous types

    由于是“匿名类型”,显然它的访问级别应该是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:

    manage nuget packages

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

    install mono.cecil via nuget

    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事件:

    post build scripts

    再次编译并运行程序,即可得到正确结果。再拿ILSpy来检查一番:

    public anonymous types

    总结

    在沙龙上,有朋友问我怎么样可以成为一个高级.NET技术人员。我不知道“如何成为”,但我想,了解整个生态环境的发展情况,了解.NET的优势及不足,甚至能够了解相关领域其他技术方向的发展态势,应该是优秀.NET程序员的特质之一吧。

    而Mono便是.NET生态环境的重要组成部分。

  • 相关阅读:
    PAT 1036 Boys vs Girls (25分) 比大小而已
    idea创建maven项目慢的原因以及解决方案
    git diff 理解
    git status 命令详解
    java注解
    单例模式
    io分类
    数据库设计的范式
    mysql约束
    mysql去重复关键字distinct的用法
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2610488.html
Copyright © 2011-2022 走看看