在asp.net mvc项目中,通常视图的Model都是强类型的,这个给我们静态检查带来了方便。但是有时为了编程方便(我就比较懒,不想为了简单的Model再去定义一个类),需要给视图传递一个匿名的Model类型。进过google,在博客园中找到了老赵的一篇文章《当类型为dynamic的视图模型遭遇匿名对象》,帮助我解决了问题,总结下来有以下三种方法:
1、使用.NET4.0中提供的类ExpandoObject
3、在运行时生成一个动态类型
其中前两种方法在老赵的文章中已经提到:它只解决了Model本身使用匿名对象的问题,无法解决Model的某个字段返回一个匿名对象。但是按照老赵文章中的第三种方法,也只解决了Model的某个字段返回匿名对象的问题,如果是匿名对象的某个字段仍然返回匿名对象(即嵌套匿名类型)呢?这个时候就就会出现如下错误:
出现这个问题的原因:匿名类型的成员,默认的访问级别就是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中的表达式树生成动态方法》,后面我也会博文跟进这部分内容。