匿名类型缺少成员问题的动态视图-MVC3



我有一个MVC3站点,我已经为测试另一个站点而设置了它——大多数站点都很快,而且很脏,所以我没有去城里为所有视图创建模型和视图模型类型——只有在需要用户输入的地方。

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

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

在我看来(Razor C#(我想读这篇文章——很简单:

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

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

显然,一个"合适"的模型类型可以解决这个问题,但我只是不想这样做,因为这是我的特权(!(-如何最好地将代码保持在最低限度并保持动态?

这就是我所做的(我强调这只是真正解决了匿名类型的问题(;将构件抬出并将它们推入CCD_ 3。

我最初的更改是使投影成为一个返回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)) // watch out for types with indexers 
    {
      toReturn[prop.Name] = prop.GetValue(obj, null);
    }
    return toReturn;
  }
}

这意味着我可以使用我的原始代码,但最后添加了一个小的扩展方法调用:

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

它是高效的,而且它绝对应该不要用于严肃的生产代码。但这是一个很好的时间节省。

显然,一个"合适"的模型类型可以解决这个问题,但让我们说我只是不想那样做,因为这是我的特权(!(

你不能有这样的特权。很抱歉,这绝对没有任何借口:-(更不用说你试图实现的是不可能的,因为动态类型是声明程序集的内部。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)

我们从最初的版本改进的东西:

  • 我们有带Intellisense的强类型视图(如果您激活视图的编译,甚至编译时安全(
  • 使用适合视图特定要求的强类型视图模型
  • 消除暗示弱类型的ViewBag/ViewData
  • 使用显示模板可以避免在视图中编写难看的循环=>您依赖约定,框架完成其余工作

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

一个选项是基于DynamicObject的代理ImpromptuGet,它只会将调用转发到匿名类型,类似于使用dynamic(它使用相同的api,只是它将上下文设置为匿名类型的自身,因此internal访问无关紧要(。

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

这个选项看起来不像ToSafeDynamic那样干净,但它有一个小区别,那就是它只在使用属性时调用属性,而不是预先复制所有数据。

然而,ImpromptuInterface的一个更好的解决方案是它用于创建原型动态对象的快速语法。

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

这将创建一个类似expando的对象,但您也可以选择创建字面ExpandoObjects(在我的测试中,它在getter上提供的性能与转换为动态的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));

如果你只是追求纯粹的快速死亡,那么在这种情况下你总是可以使用元组。

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

-

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

您知道生成管理器生成的程序集的名称吗?如果是这样,您应该能够在控制器部件中应用InternalsVisibleTo,并且一切都会正常工作。

编辑:一个可能的解决方案是:在模型程序集中创建一个扩展DynamicObject的公共类型,该类型通过反射将对属性的任何请求代理到您的匿名类型。这会很难看,但我认为它应该有效。。。有效地公开了匿名类型。

ASP.NET MVC动态视图部分

这个怎么样?

最新更新