再论匿名类在dynamic类型的视图中的使用

在asp.net mvc项目中,通常视图的Model都是强类型的,这个给我们静态检查带来了方便。但是有时为了编程方便(我就比较懒,不想为了简单的Model再去定义一个类),需要给视图传递一个匿名的Model类型。进过google,在博客园中找到了老赵的一篇文章《当类型为dynamic的视图模型遭遇匿名对象》,帮助我解决了问题,总结下来有以下三种方法:

1、使用.NET4.0中提供的类ExpandoObject

2、为视图模型定义一个封装类

3、在运行时生成一个动态类型

其中前两种方法在老赵的文章中已经提到:它只解决了Model本身使用匿名对象的问题,无法解决Model的某个字段返回一个匿名对象。但是按照老赵文章中的第三种方法,也只解决了Model的某个字段返回匿名对象的问题,如果是匿名对象的某个字段仍然返回匿名对象(即嵌套匿名类型)呢?这个时候就就会出现如下错误:

再论匿名类在dynamic类型的视图中的使用_第1张图片

出现这个问题的原因:匿名类型的成员,默认的访问级别就是internal的;而我们只生成了最外一层的动态类型,内部嵌套的匿名类型的字段仍然是internal的。

解决方案

参考老赵的代码,对内部嵌套的匿名类型的字段做同样的处理——生成动态类型,我在这里采用的是递归的方法:

   1: private static Type CreateDynamicType(Type entityType)
   2: {
   3:     var asmName = new AssemblyName("DynamicAssembly_" + Guid.NewGuid());
   4:     var asmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run);
   5:     var moduleBuilder = asmBuilder.DefineDynamicModule("DynamicModule_" + Guid.NewGuid());
   6:  
   7:     var typeBuilder = moduleBuilder.DefineType(
   8:         entityType.GetType() + "$DynamicType_" + Guid.NewGuid(),
   9:         TypeAttributes.Public);
  10:  
  11:     typeBuilder.DefineDefaultConstructor(MethodAttributes.Public);
  12:     
  13:     foreach (var entityProperty in entityType.GetProperties())
  14:     {
  15:         if (!entityProperty.PropertyType.IsAnonymous())
  16:         {
  17:             typeBuilder.DefineField(entityProperty.Name, entityProperty.PropertyType, FieldAttributes.Public);
  18:         }
  19:         else
  20:         {
  21:             typeBuilder.DefineField(entityProperty.Name, CreateDynamicType(entityProperty.PropertyType), FieldAttributes.Public);
  22:         }
  23:     }
  24:  
  25:     return typeBuilder.CreateType();
  26: }

由于只有匿名类型的成员,才需要创建动态类型,所以上面的代码中有需要对类型进行判断的函数PropertyType.IsAnonymous(),这个是一个Type类型的扩展方法:

   1: public static bool IsAnonymous(this Type type)
   2: {
   3:     if (type == null) throw new ArgumentNullException("type");
   4:  
   5:     return Attribute.IsDefined(type, typeof(CompilerGeneratedAttribute), false)
   6:             && type.IsGenericType && type.Name.Contains("AnonymousType")
   7:             && type.Name.StartsWith("<>")
   8:             && (type.Attributes & TypeAttributes.NotPublic) == TypeAttributes.NotPublic;
   9: }

大家应该注意到了,我的CreateDynamicType()方法,并非public的,只是想封装创建嵌套动态类型的逻辑,暴露给外部的public方法如下:

   1: public static object ToDynamic(this object entity)
   2: {
   3:     var entityType = entity.GetType();
   4:     var dynamicType = s_dynamicTypes.GetOrAdd(entityType, s_dynamicTypeCreator);
   5:  
   6:     return entity.ToType(dynamicType);
   7: }

在这个方法中,用到了一个自定义函数ToType(),作用是把一个类型转换成另一个type类型的对象:

   1: public static object ToType(this object obj, Type type)
   2: {
   3:     //create instance of type object:
   4:     var tmp = Activator.CreateInstance(type);
   5:  
   6:     //loop through the properties of the object you want to covert:          
   7:     foreach (var entityProperty in obj.GetType().GetProperties())
   8:     {
   9:         var value = entityProperty.GetValue(obj, null);
  10:  
  11:         try
  12:         {
  13:             //get the value of property and try 
  14:             //to assign it to the property of type object:
  15:             FieldInfo field = type.GetField(entityProperty.Name);
  16:             if (!field.FieldType.Equals(entityProperty.PropertyType))
  17:             {
  18:                 value = value.ToType(field.FieldType);
  19:             }
  20:             field.SetValue(tmp, value);
  21:         }
  22:         catch { }
  23:     }
  24:  
  25:     //return the type object:         
  26:     return tmp;
  27: }

代码中的s_dynamicTypes和s_dynamicTypeCreator成员和老赵文章中的一样,大家可以参考。另外ToType()和IsAnonymous()两个并非只能在这里使用,所以我也定义为了public的。

如何使用

使用方法需要分成两个部分:

1、在Action中,应像如下方式使用:

   1: return View(new
   2: {
   3:     Message = "This is a view with nested dynamic model, implement by dynamic WebViewPage",
   4:     Date = DateTime.Now,
   5:     Nested = new
   6:     {
   7:         Name = "NestedOne",
   8:         Nested = new
   9:         {
  10:             Name = "NestedTwo"
  11:         }
  12:     }
  13: }.ToDynamic());

2、在view中,mvc2和mvc3稍微有点差别:

1)mvc2中

   1: <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<dynamic>" %>
   2:  
   3: <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
   4:     NestedDynamicModel
   5: </asp:Content>
   6:  
   7: <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
   8:  
   9:     <h2>
  10:         <%: Model.Message %></h2>
  11:     <p>
  12:         <%:Model.Date.ToString("yyyy-MM-dd HH:mm:ss") %>        
  13:     </p> 
  14:     <p>
  15:         <%:Model.Nested.Name%>
  16:     </p>
  17:     <p>
  18:         <%:Model.Nested.Nested.Name%>
  19:     </p>
  20:  
  21: </asp:Content>

 

2)mvc3中

   1: 
@model dynamic
   2: @{
   3:     ViewBag.Title = Model.Title;
   4: }
   5: <h2>
   6:     @Model.DynamicModel.Message</h2>
   7: <p>
   8:     @Model.DynamicModel.Date.ToString("yyyy-MM-dd HH:mm:ss")
   9: </p>
  10: <p>
  11:     @Model.DynamicModel.Nested.Name
  12: </p>
  13: <p>
  14:     @Model.DynamicModel.Nested.Nested.Name
  15: </p>

从实例代码可以看出,主要差别是在view的首行(红色部分)。然后我们就可以像正常对象那样使用嵌套匿名类型了,但是唯一的缺陷就是:这样使用动态类型,就无法智能感知了。

优化

很显然上面的代码,达到了功能上的目的,但是性能就成了最大的问题,所以接下来就应该好好进行下优化。优化的方案不外乎可以从如下几个方面着手:

1、Emit(.net 1.0)

2、DynamicMethod(.net 2.0)

3、Expression Tree(.net 3.5)

4、DLR(.net 4.0)

由于时间问题,这里就不进一步展开了。大家可以参考《使用.NET 4.0中的表达式树生成动态方法》,后面我也会博文跟进这部分内容。

你可能感兴趣的:(dynamic)