zoukankan      html  css  js  c++  java
  • 当类型为dynamic的视图模型遭遇匿名对象

    当年在ASP.NET MVC 1.0时代我提到,在开发时最好将视图的Model定制为强类型的,这样可以充分利用静态检查功能进行排错。不过有人指出,这么做虽然易于静态检查,但是定义强类型的Model类型实在是太麻烦了,因此也出现了基于SmartBag等折衷方案。强类型是一种极端方案,而在C# 4.0中我们又可以使用另一个极端,那就是让Model成为dynamic类型,这样在视图中便可以完全自由地获取数据了。不过,在使用匿名对象的情况下视图会抛出奇怪的“无法找到成员”异常,我们必须解决这个问题。

    dynamic类型的视图模型

    我们现在先来创建一个Model类型为dynamic的视图,例如ViewsHomeIndex.aspx,我们要在一开始的Page标记中设置它的基类类型:

    <%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<dynamic>" %>

    我们将这里的范型参数设为dynamic。这一做法可能会让某些朋友感到新奇,不过这其实十分正常。事实上,dynamic关键字在C#的概念中(不论实现),其实就是和int、string或是Controller一样,都是一种“类型”,只不过对于这样一个类型会有些动态分发等特殊处理。这样定义视图类型之后,便可以在Controller中自由提供视图模型了,例如:

    public ActionResult Index()
    {
        dynamic model = new ExpandoObject();
        model.Hello = "World";
    
        return View(model);
    }

    ExpandoObject是.NET 4.0在BCL中提供的类,它的作用便是利用.NET中定义的动态分发功能,定义了一个可以任意扩展的类型。例如在上面的代码中,我们可以直接赋予它Hello属性。然后在视图模板中读出:

    <%= Model.Hello %>

    这样看起来是不是很方便?这样虽然没有了任何的静态教研,倒也带来了十足的自由性——就像是Ruby on Rails等动态语言的框架一样。

    奇怪的“无法找到成员”异常

    不过,这种dynamic有时候会有非常奇怪的表现,例如我们把上面Index的代码改成使用匿名类型对象之后:

    public ActionResult Index()
    {
        return View(new { Hello = "World" });
    }

    再次执行就会抛出异常:

    简单说来,便是指Model对象没有找到合适的Hello成员,因此绑定失败。但是,我们明明有这个属性,不是吗?更奇怪的是,如果是在一个Console应用程序中这么写则是没有任何问题的:

    dynamic model = new { Hello = "World" };
    Console.WriteLine(model.Hello);

    问题似乎就是出在ASP.NET的页面上。说到原因,我也不知道,一开始我以为是由于只读属性造成的,但是在尝试了显式定义类型之后发现也不是这个原因。我相信仔细分析之后一定可以得出结果,不过现在最重要的是想个办法解决它。毕竟这个情况很容易遇到,ExpandoObject只能解决极小部分的问题。因为在Controller中的常见需求之一,便是使用如LINQ表达式等方式生成一些供视图使用的匿名对象。

    生成动态类型

    这个问题以前也有人遇到过,他的做法是为视图模型定义一个封装类。可惜这么做其实并没有解决问题,事实上它只解决了Model本身使用匿名对象的问题,但是如果是Model的某个字段返回一个匿名对象呢?再访问这个匿名对象还是有相同问题出现。幸好我们知道一个显示定义的类型是可以正常使用的,于是一个比较容易想到的方法便是在运行时生成动态类型。例如:

    public static class DynamicFactory
    {
        private static ConcurrentDictionary<Type, Type> s_dynamicTypes = new ConcurrentDictionary<Type, Type>();
    
        private static Func<Type, Type> s_dynamicTypeCreator = new Func<Type, Type>(CreateDynamicType);
    
        public static object ToDynamic(this object entity)
        {
            var entityType = entity.GetType();
            var dynamicType = s_dynamicTypes.GetOrAdd(entityType, s_dynamicTypeCreator);
    
            var dynamicObject = Activator.CreateInstance(dynamicType);
            foreach (var entityProperty in entityType.GetProperties())
            {
                var value = entityProperty.GetValue(entity, null);
                dynamicType.GetField(entityProperty.Name).SetValue(dynamicObject, value);
            }
    
            return dynamicObject;
        }
    
        private static Type CreateDynamicType(Type entityType)
        {
            var asmName = new AssemblyName("DynamicAssembly_" + Guid.NewGuid());
            var asmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run);
            var moduleBuilder = asmBuilder.DefineDynamicModule("DynamicModule_" + Guid.NewGuid());
    
            var typeBuilder = moduleBuilder.DefineType(
                entityType.GetType() + "$DynamicType",
                TypeAttributes.Public);
    
            typeBuilder.DefineDefaultConstructor(MethodAttributes.Public);
    
            foreach (var entityProperty in entityType.GetProperties())
            {
                typeBuilder.DefineField(entityProperty.Name, entityProperty.PropertyType, FieldAttributes.Public);
            }
    
            return typeBuilder.CreateType();
        }
    }

    我在这里定义了一个ToDynamic扩展方法,用于从任意对象扩展出一个……动态类型的对象。这个动态类型会根据输入对象中的属性信息,生成对应的公有字段,然后使用反射进行赋值。有了这个方法以后,遇到匿名类型也就只需稍多一步就够了:

    return View(new { Hello = "World" }.ToDynamic());

    即便像我之前所说的那样,使用LINQ语句为视图准备一些可用的匿名对象,也可以这样:

    var categories = ...; // get categories;
    
    dynamic model = new ExpandoObject();
    model.PrivateCategories =
        from c in categories
        where c.AccessLevel == AccessLevel.Private
        orderby c.IsDefault descending, c.Name
        select new
        {
            CategoryID = c.NoteCategoryID,
            CategoryName = c.Name,
            IsDefault = c.IsDefault,
            Count = c.NoteCount
        }.ToDynamic();
    
    model.PublicCategories =
        from c in categories
        where c.AccessLevel == AccessLevel.Public
        orderby c.IsDefault descending, c.Name
        select new
        {
            CategoryID = c.NoteCategoryID,
            CategoryName = c.Name,
            IsDefault = c.IsDefault,
            Count = c.NoteCount
        }.ToDynamic();
    
    return View(model);

    问题就这样解决了。

    改进

    很显然,上面的实现只是个雏形,其中最大的问题应该就是“性能”了。现在的代码中反复使用了反射,对此我们可以使用如Fast Reflection Library那样的方式来改善反射读写字段的执行效率。当然,可能最好的办法是动态生成出这样的代码了:

    public class DynamicType
    {
        public DynamicType(object entity)
        {
            var strongTyped = (EntityType)entity;
    
            this.Abc = strongTyped.Abc;
            this.Ijk = strongTyped.Ijk;
            this.Xyz = strongTyped.Xyz;
            ...
        }
    }

    这其实也很简单,有兴趣的话您也可以试试看。一会儿我也会去实现一下类似的功能,顺便尝试一下.NET 4.0——据说.NET 4.0的类库对生成动态方法较之前的版本有了更好的支持。

  • 相关阅读:
    Nodejs exec和spawn的区别
    VC++每个版本对应的库
    在cmd启动一个win32程序,printf把信息输出到启运它的那个CMD窗口
    window 控制台解决中文乱码
    NW.js 桌面应用程序
    C++ Addon Async 异步机制
    Node bak
    nodejs electron 创建桌面应用
    跨平台桌面程序框架Electron
    js post 下载文件
  • 原文地址:https://www.cnblogs.com/xdot/p/6056662.html
Copyright © 2011-2022 走看看