当类型为dynamic的视图模型遭遇匿名对象
当年在ASP.NET MVC 1.0时代我提到,在开发时最好将视图的Model定制为强类型的,这样可以充分利用静态检查功能进行排错。不过有人指出,这么做虽然易于静态检查,但是定义强类型的Model类型实在是太麻烦了,因此也出现了基于SmartBag等折衷方案。强类型是一种极端方案,而在C# 4.0中我们又可以使用另一个极端,那就是让Model成为dynamic类型,这样在视图中便可以完全自由地获取数据了。不过,在使用匿名对象的情况下视图会抛出奇怪的“无法找到成员”异常,我们必须解决这个问题。
dynamic类型的视图模型
我们现在先来创建一个Model类型为dynamic的视图,例如Views\Home\Index.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的类库对生成动态方法较之前的版本有了更好的支持。
当类型为dynamic的视图模型遭遇匿名对象的更多相关文章
- .NET:关于数据模型、领域模型和视图模型的一些思考
背景 数据模型.领域模型和视图模型是“模型”的三种角色,一些架构用一种类型表示这三种角色,如:传统三层架构.也有一些架构用两种类型表示这三种角色,如:结合ORM的领域驱动架构.非常少见的场景是用三种类 ...
- [转]架构蓝图--软件架构 "4+1" 视图模型
架构蓝图--软件架构 "4+1" 视图模型 本文基于多个并发视图的使用情况来说明描述软件密集型系统架构的模型.使用多重视图允许独立地处理各"风险承担人":最终用 ...
- DDD:谈谈数据模型、领域模型、视图模型和命令模型
背景 一个类型可以充当多个角色,这个角色可以是显式的(实现了某个接口或基类),也可以是隐式的(承担的具体职责和上下文决定),本文就讨论四个角色:数据模型.领域模型.视图模型和命令模型. 四个角色 数据 ...
- ASP.NET MVC with Entity Framework and CSS一书翻译系列文章之第三章:搜索、高级过滤和视图模型
在这一章中,我们首先添加一个搜索产品的模块以增强站点的功能,然后使用视图模型而不是ViewBag向视图传递复杂数据. 注意:如果你想按照本章的代码编写示例,你必须完成第二章或者直接从www.apres ...
- ThinkPHP第十六天(redirect、join、视图模型)
1.redirect /** * Action跳转(URL重定向) 支持指定模块和延时跳转 * access protected * @param string $url 跳转的URL表达式 * @p ...
- Windows Phone 8初学者开发—第12部分:改进视图模型和示例数据
原文 Windows Phone 8初学者开发—第12部分:改进视图模型和示例数据 第12部分:改进视图模型和示例数据 原文地址:http://channel9.msdn.com/Series/Win ...
- 视图模型-Lambda表达式
EF中通过改变实体对象达到操作数据库表数据的目的,在对数据库实体操作时,肯定少不了和Linq.Lambda打交道,熟悉SQL的话,上手 Linq并不难,from in where select... ...
- 领域模型(DomainModel)与视图模型(ViewModel)
Model-View-Controller(模型-视图-控制器,MVC)模式将你的软件组织并分解成三个截然不同的角色: Model 封装了你的应用数据.应用流程和业务逻辑. View 从 Model ...
- 架构蓝图--软件架构 "4+1" 视图模型
引言 我们已经看到在许多文章和书籍中,作者欲使用单张视图来捕捉所有的系统架构要点.通过仔细地观察这 些图例中的方框和箭头,不难发现作者努力地在单一视图中表达超过其表达限度的蓝图.方框是代表运行的程序吗 ...
随机推荐
- 行为驱动开发(BDD)实践示例
引言 BDD是对TDD理念的扩展.BDD强调有利害关系的技术团体和非技术团队都要参与到软件开发过程中.可以把它看成一种强调团体间合作的敏捷方法.大多数采用某种敏捷方法的团队最终都会遵循BDD的许多原则 ...
- 数百个 HTML5 例子学习 HT 图形组件 – WebGL 3D 篇
<数百个 HTML5 例子学习 HT 图形组件 – 拓扑图篇>一文让读者了解了 HT的 2D 拓扑图组件使用,本文将对 HT 的 3D 功能做个综合性的介绍,以便初学者可快速上手使用 HT ...
- Moon.Orm 入门总指南
注意:下面的pdf文件强烈建议下载或在线查看 1)旗舰版帮助文档点击查看或下载 2)http://pan.baidu.com/s/1hq7krFu(新手手册下载)(强烈推荐) 3)性能及规范下载,网友 ...
- WCF备忘录一:服务端实例的生命周期
示例代码下载地址:WCFDemo1Day 概述 客户端向WCF服务发出请求后,服务端会实例化一个Service对象(实现了契约接口的对象)用来处理请求,实例化Service对象以及维护其生命周期的方式 ...
- [C1] C1ComboBox 的非编辑状态优化
一.前言 先看看 WPF 自带的 ComboBox 在非编辑状态,自定义 ItemTemplate 的情况下,效果如下图所示: 其当前选中的项(红框内)与自定义的 ItemTemplate 一样: 但 ...
- 设计模式(十)组合模式(Composite Pattern)
一.引言 在软件开发过程中,我们经常会遇到处理简单对象和复合对象的情况,例如对操作系统中目录的处理就是这样的一个例子,因为目录可以包括单独的文件,也可以包括文件夹,文件夹又是由文件组成的,由于简单对象 ...
- 分布式文件系统 - FastDFS 在 CentOS 下配置安装部署
少啰嗦,直接装 看过上一篇分布式文件系统 - FastDFS 简单了解一下的朋友应该知道,本次安装是使用目前余庆老师开源的最新 V5.05 版本,是余庆老师放在 Github 上的,和目前你能在网络上 ...
- iframe高度自适应(同域)
今天解决了iframe高度自适应的问题,不过这只是同域下的页面嵌入,以下是代码: function SetCwinHeight(){ var iframeid = document.getElemen ...
- 报表或BI的价值在哪?
报表或者bi的价值在哪?这已经是十几年的问题啦! 为什么要解释这个并不新鲜的问题,是因为当一个两个问我的时候,并没有在意,被问的多了,觉得这问题并没有过时. 数据挖掘和建模分析,其价值很容易说出来,因 ...
- 沙盒解决方案解决SharePoint 2013 以其他身份登陆的问题
众所周知,SharePoint 2013没有像SharePoint 2010那样有一个叫"以其他身份登录"的菜单项. 当然解决方案也很多,比如你可以直接修改Welcome.ascx ...