2011-09-15 53 views
3

我有一个MVC3网站,我已经设置了测试另一个网站 - 大部分是快速和肮脏的,所以我还没有去镇上创建模型和视图所有视图的模型类型 - 仅在用户需要输入的情况下。匿名类型缺少成员问题的动态视图 - MVC3

好吧,让我有一个控制器方法,投影Linq序列并将其设置为ViewBag

ViewBag.SomeData = Enumerable.Range(1,10).Select(i=> new { Value = i }); 

在我看来(剃刀C#),那么我想读这一点 - 很简单:

@foreach(dynamic item in ViewBag.SomeData) 
{ 
    @:Number: @item.i 
} 

除,当然,我得到一个RuntimeBinderException,因为在控制器中创建匿名类型是内部的到web项目的输出程序集,这里的实际Razor代码将运行在由构建管理器生成的不同程序集中,因此,总之DENIED!

很明显,一个'正确的'模型类型可以解决这个问题 - 但是让我们说我根本不想这样做,因为这是我的特权(!) - 如何最好地保持代码最小并保持动态在这里?

回答

4

好抱歉,我只是不同意,这是可怕的等等等等。是的,我永远不会考虑在生产网站上做这些东西 - 但对于原型,非商业,仅限内部,用于测试目的,我认为使用这种方法是完美的有效。

这就是我所做的(我强调这只是真正解决了匿名类型的问题);举起成员并将它们推入ExpandoObject

我最初的变化是使投影多语句返回一个ExpandoObject

ViewBag.SomeData = Enumerable.Range(1,10).Select(i=> { 
    var toReturn = new ExpandoObject(); 
    toReturn.Value = i; 
    return toReturn; 
}); 

这几乎是一样短匿名类型只是没有那么干净。

但后来我想,如果我可能逃脱抓住公开可读的成员了匿名类型的(可能是依赖于编译器内部 - 类型是内部的,但它产生的属性是公共的):

public static class SO7429957 
{ 
    public static dynamic ToSafeDynamic(this object obj) 
    { 
    //would be nice to restrict to anonymous types - but alas no. 
    IDictionary<string, object> toReturn = new ExpandoObject(); 

    foreach (var prop in obj.GetType().GetProperties(
     BindingFlags.Public | BindingFlags.Instance) 
     .Where(p => p.CanRead)) 
    { 
     toReturn[prop.Name] = prop.GetValue(obj, null); 
    } 

    return toReturn; 
    } 
} 

这意味着然后,我可以用我的原代码 - 但标签上月底一点点扩展方法调用:

ViewBag.SomeData=Enumerable.Range(1,10).Select(i=> new { Value = i }.ToSafeDynamic()); 

而且你知道什么 - 我知道这是效率不高,大多数人会说这是丑陋;但它会为我节省时间,并让我专注于在我的测试站点中为我的QA团队编写功能,以用于测试真实的东西(其代码当然是完美无瑕的:))。

+2

使用ExpandoObject的好决定 - 已经忘记了这个选项。 –

+1

@Jon - 谢谢!让我们面对它吧,它是一种违背几乎所有我们以前的C#概念的类型,因此很容易被遗忘。哦,顺便说一句 - C#深度第二版的岩石。 –

+0

我的借口是,我很少做任何动态的事情。很高兴听到你喜欢这本书:) –

0

您是否知道构建管理器生成的程序集的名称?如果是这样,您应该能够在控制器组件中应用InternalsVisibleTo,并且它将一切正常。

编辑:一种可能的解决方案:在模型组件中创建一个扩展DynamicObject的公共类型,它通过反射将任何属性请求代入您的匿名类型。这将是丑陋的,但我认为它应该工作......有效地使匿名类型公开。

+1

是啊,我发现这里的解决方案:http://www.heartysoft.com/anonymous-types-c-sharp-4-dynamic,但我不相信输出程序集的身份在通过Razor Build Engine动态生成时是一致的。 –

+1

是的 - 正如我的想法 - 输出Razor视图的程序集限定名称显示对标记的任何更改都会强制重新编译,并且程序集名称也会更改。 –

+0

@安德拉斯:好的,将编辑... –

1

显然,一个“适当的”模型类型可以解决这个问题 - 但是我们要说 我根本就不想这样做,因为这是我的特权

你不能有这样的特权(!)。对不起,这绝对没有任何借口:-)更不用说,你试图实现的是不可能的,因为动态类型是声明程序集的内部。 Razor视图由ASP.NET引擎在运行时编译成单独的动态程序集。

因此,回到话题:永远不要将匿名对象作为模型传递给视图。始终定义使用视图模型。像这样:

public class MyViewModel 
{ 
    public int Value { get; set; } 
} 

然后:

public ActionResult Index() 
{ 
    var model = Enumerable.Range(1, 10).Select(i => new MyViewModel { Value = i }); 
    return View(model); 
} 

,然后使用强类型视图:

@model IEnumerable<MyViewModel> 
@Html.DisplayForModel() 

并且将自动被渲染的每个元素对应的显示模板内该集合,以便您不必在视图中编写任何循环(~/Views/Shared/DisplayTemplates/MyViewModel.cshtml):

@model MyViewModel 
@:Number: @Html.DisplayFor(x => x.Value) 

的事情,我们已经从最初的版本改进:

与智能感知
  • 我们强类型的意见(如果你激活编译的意见,甚至编译时的安全性)强类型的视图模型的
  • 用法适应您的意见的具体要求。
  • 摆脱ViewBag /的ViewData这意味着弱类型的,其避免在你的看法写丑循环显示模板
  • 用法=>你依靠惯例和框架没有休息
+1

相信我这个测试的MVC3网站确实遵循了所有这些原则;但是我不得不说,如果你想要测试应用程序以便人们想要交互地测试你的网站(这是一个web服务启动的事情),那么我认为更快速的原型方法是完全合理的;无论如何,如果我想(无限),我可以把它作为我的特权! –

+2

@Andras Zoltan,为了实现这个目标,你必须编写很多管道代码来对抗编译器将内部动态类型生成为内部的事实,在一天结束的时候,你会浪费你很多宝贵的时间来做一些黑客而不是侧重于实施。如果你想做快速模型,请使用脚手架。 –

+1

4个扩展方法的语句做到了。我根本不反对你的观点;但我认为没有理由被束缚于每一个最佳实践(旨在实现可维护和高效的专业最终产品)的最后一点,而这最终是一种辅助“测试工具”。我可以快速开发它,并且可以轻松地添加和删除我的视图中的信息,而无需担心类型管理。 –

1

我认为ToSafeDynamic是一个很好的解决方案。但我想分享一些其他选项,使用开源的ImpromptuInterface(在nuget中),在这种情况下可以很好地工作。

一个选项,作为基于DynamicObject的代理,ImpromptuGet它只是将调用转发给annonymous类型,类似于使用动态(它使用相同的api,除了它将上下文设置为匿名类型的自我,所以internal访问不会'问题)。

ViewBag.SomeData = Enumerable.Range(1,10) 
          .Select(i => ImpromptuGet.Create(new { Value = i })); 

此选项看起来并不ToSafeDynamic干净,但它的,因为它只是调用属性使用它们时,而不是复制所有数据前期小的区别。

但是,ImpromptuInterface的更好的解决方案是创建原型动态对象的quick syntax

ViewBag.SomeData = Enumerable.Range(1,10).Select(i => Build.NewObject(Value:i)); 

,将创建一个的expando状物体,但你也必须创建文字ExpandoObject秒(这在我的测试提供的干将铸态动态POCO对象相同的性​​能)的选项。

ViewBag.SomeData = Enumerable.Range(1,10) 
          .Select(i => Build<ExpandoObject>.NewObject(Value:i)); 

此外,创建设置部分可以存储在一个或多个临时变量中,以更多地缩短lambda。

var Expando =Build<ExpandoObject>.NewObject; 
ViewBag.SomeData = Enumerable.Range(1,10).Select(i => Expando(Value:i)); 
+1

+1这是一些很酷的东西,没有听说过这个;我在想,当我遇到这个问题时,如果C#支持动态如ExpandoObject的初始化器,那么问题就很容易解决;使用命名参数是解决这个问题的一个聪明方法。 –

0

如果你只是为了纯粹的quick'n'dirty,你总是可以在这种情况下使用元组。

ViewBag.SomeData = Enumerable.Range(1,10).Select(i=> new Tuple<int>(i); 

-

@foreach(dynamic item in ViewBag.SomeData) 
{ 
    @:Number: @item.Item1 
} 
相关问题