【问题标题】:Dynamic view of anonymous type missing member issue - MVC3匿名类型缺少成员问题的动态视图 - MVC3
【发布时间】:2011-11-17 19:13:09
【问题描述】:

我有一个 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 代码将在构建管理器生成的不同程序集中运行,所以,总而言之拒绝!

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

【问题讨论】:

    标签: .net asp.net-mvc c#-4.0 dynamic anonymous-types


    【解决方案1】:

    好吧,抱歉,我只是不同意这太可怕了等等等等。是的,我永远不会考虑在生产站点上做这些事情——但对于原型设计、非商业、仅限内部、用于测试目的,我认为使用这种方法是完全有效的。

    这就是我所做的(我强调这只会真正充分解决匿名类型的问题);将成员抬出并推入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 团队用于测试真实的东西(其代码当然是完美无瑕的 :))。

    【讨论】:

    • 使用 ExpandoObject 的好决定 - 忘记了这个选项。
    • @Jon - 谢谢!让我们面对现实吧,它是一种几乎与我们之前所有的 C# 概念背道而驰的类型,而且很容易被遗忘。哦,顺便说一句 - C# In Depth Second Edition 摇滚。
    • 我的借口是我很少用动态做任何事情。很高兴听到你喜欢这本书:)
    【解决方案2】:

    显然,“适当”的模型类型可以解决问题 - 但假设 我只是不想这样做,因为那是我的特权(!)

    你不能有这样的特权。抱歉,这绝对没有任何借口:-) 更不用说你想要实现的目标是不可能的,因为动态类型是声明程序集的内部。 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
    • 使用显示模板可避免您在视图中编写丑陋的循环 => 您依赖约定,其余工作由框架完成

    【讨论】:

    • 相信我,正在测试的 MVC3 站点确实遵循所有这些原则;但我不得不说,如果你要为人们想要交互式测试你的网站的场景(这是一个由 web 服务启动的东西)敲击测试应用程序,那么我认为更快速的原型设计方法是完全合理的;无论如何,如果我愿意,我可以让它成为我的特权(无限)!
    • @Andras Zoltan,要实现这一点,您将不得不编写大量的管道代码来对抗编译器生成动态类型作为内部的事实,最终您会浪费很多您将宝贵的时间花在做一些技巧上,而不是专注于实施。如果您想快速制作模型,请使用脚手架。
    • 4 个扩展方法的语句做到了。我完全不同意你的观点;但我认为没有理由拘泥于最后一点最佳实践(旨在实现可维护和高效的专业最终产品),因为它最终是一个辅助的“测试工具”。我可以快速开发它,并且可以轻松地在视图中添加和删除信息,而不必担心类型管理。
    【解决方案3】:

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

    一个选项,作为基于 DynamicObject 的代理,ImpromptuGet,它只会将调用转发到匿名类型,类似于使用动态(它使用相同的 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 的对象,但您也可以选择创建文字 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));
    

    【讨论】:

    • +1 那是一些很酷的东西;没听说过这个;当我遇到这个问题时,我在想,如果 C# 支持像 ExpandoObject 这样的动态初始化器,那么问题将很容易解决;使用命名参数是一种巧妙的解决方法。
    【解决方案4】:

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

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

    【讨论】:

    • 啊,是的,我在这里找到了解决方案:heartysoft.com/anonymous-types-c-sharp-4-dynamic,但我不相信输出程序集的身份在通过 Razor 构建引擎动态生成时是一致的。
    • 是的 - 正如我所想 - 输出 Razor 视图的程序集限定名称表明对标记的任何更改都会强制重新编译并且程序集名称也会更改。
    • 已经做了一些类似的事情(添加了一个答案);不是深度代理,只是有效地将类型的公共属性克隆到 Expando 中。我考虑将其作为动态委托进行,以免克隆引用;但最终是为了使用不需要的匿名类型。
    【解决方案5】:

    如果您只是追求纯粹的快速操作,您可以随时在这种情况下使用元组。

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

    -

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

    【讨论】:

      【解决方案6】:

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-11-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-28
        • 1970-01-01
        相关资源
        最近更新 更多