Razor中的动态匿名类型导致RuntimeBinderException


156

我收到以下错误:

“对象”不包含“ RatingName”的定义

当您查看匿名动态类型时,它显然具有RatingName。

屏幕截图错误

我知道我可以使用元组来执行此操作,但是我想了解为什么会出现错误消息。

Answers:


240

在我看来,具有内部属性的匿名类型是一个糟糕的.NET框架设计决策。

这是解决此问题的快速而不错的扩展,即立即将匿名对象转换为ExpandoObject。

public static ExpandoObject ToExpando(this object anonymousObject)
{
    IDictionary<string, object> anonymousDictionary =  new RouteValueDictionary(anonymousObject);
    IDictionary<string, object> expando = new ExpandoObject();
    foreach (var item in anonymousDictionary)
        expando.Add(item);
    return (ExpandoObject)expando;
}

它很容易使用:

return View("ViewName", someLinq.Select(new { x=1, y=2}.ToExpando());

当然在您看来:

@foreach (var item in Model) {
     <div>x = @item.x, y = @item.y</div>
}

2
+1我专门在寻找HtmlHelper.AnonymousObjectToHtmlAttributes,我知道这绝对已经存在了,并且不想用类似的手动代码重新发明轮子。
克里斯·马里西奇

3
与仅创建强类型支持模型相比,此性能如何?
GONeale

@DotNetWise,当您只可以执行IDictionary <string,object> AnonymousDictionary = new RouteDictionary(object)时,为什么还要使用HtmlHelper.AnonymousObjectToHtmlAttributes?
Jeremy Boyd

我已经测试了HtmlHelper.AnonymousObjectToHtmlAttributes并按预期工作。您的解决方案也可以工作。使用看起来更简单的方法即可:)
Adaptabi

如果您希望它是一个永久性的解决方案,则还可以覆盖控制器中的行为,但是还需要其他一些变通方法,例如能够识别匿名类型并由您自己从该类型创建字符串/对象字典。如果这样做,则可以在以下方法中覆盖它:受保护的覆盖System.Web.Mvc.ViewResult视图(字符串viewName,字符串masterName,对象模型)
Johny Skovdal 2011年

50

我在一个相关的问题中找到了答案。答案在David Ebbo的博客文章中指定。将匿名对象传递给MVC视图并使用动态访问

原因是匿名类型在内部的控制器中传递,因此只能从声明它的程序集中进行访问。由于视图是分别编译的,因此动态活页夹抱怨它不能越过该程序集边界。

但是如果您考虑一下,动态绑定器的这种限制实际上是人为的,因为如果您使用私有反射,那么什么也不会阻止您访问这些内部成员(是的,它甚至在“中等信任”中也有效)。因此,默认的动态绑定程序将强制执行C#编译规则(您无法访问内部成员),而不是让您执行CLR运行时所允许的工作。


击败我:)我遇到了我的Razor Engine(razorengine.codeplex.com上的先驱)的问题
Buildstart

这不是真正的答案,更不用说“可接受的答案”了!
Adaptabi

4
@DotNetWise:它解释了为什么发生错误,这就是问题所在。您也可以通过提供不错的解决方法来获得我的支持:)
Lucas

仅供参考:这个答案现在已经过时了-正如作者在被引用的博客文章开头表示自己是红色的
Simon_Weaver

@Simon_Weaver但是更新后并没有说明它在MVC3 +中应该如何工作。-我在MVC 4中遇到了同样的问题。关于使用动态的当前“祝福”方式的任何指针吗?
Cristian Diaconescu

24

使用ToExpando方法是最好的解决方案。

这是不需要System.Web程序集的版本:

public static ExpandoObject ToExpando(this object anonymousObject)
{
    IDictionary<string, object> expando = new ExpandoObject();
    foreach (PropertyDescriptor propertyDescriptor in TypeDescriptor.GetProperties(anonymousObject))
    {
        var obj = propertyDescriptor.GetValue(anonymousObject);
        expando.Add(propertyDescriptor.Name, obj);
    }

    return (ExpandoObject)expando;
}

1
这是一个更好的答案。不知道HtmlHelper是否对替代答案使用下划线。
2013年

+1为通用答案,这在ASP / MVC之外非常有用
Codenheim 2014年

嵌套动态属性如何?他们将继续保持动态……例如:`{foo:“ foo”,nestedDynamic:{blah:“ blah”}}
运动

16

而不是从匿名类型创建模型,然后尝试将匿名对象转换为这样的对象ExpandoObject...

var model = new 
{
    Profile = profile,
    Foo = foo
};

return View(model.ToExpando());  // not a framework method (see other answers)

您可以直接创建ExpandoObject直接:

dynamic model = new ExpandoObject();
model.Profile = profile;
model.Foo = foo;

return View(model);

然后在视图中将模型类型设置为动态,@model dynamic然后可以直接访问属性:

@Model.Profile.Name
@Model.Foo

通常,我建议大多数视图使用强类型视图模型,但是有时这种灵活性很方便。


@yohal当然可以-我想这是个人喜好。我更喜欢使用ViewBag来处理通常与页面模型无关的其他页面数据-可能与模板有关,并保持模型为主要模型
Simon_Weaver

2
顺便说一句,您不必添加@model dynamic,因为它是默认设置
yoel halb

正是我需要的,实现将anon objs转换为expando对象的方法花费了太多时间……感谢堆
h-rai 2015年

5

您可以使用框架即兴界面将匿名类型包装在接口中。

您只需要返回一个IEnumerable<IMadeUpInterface>,在Linq的末尾使用.AllActLike<IMadeUpInterface>();此方法就可以了,因为它使用DLR在声明了匿名类型的程序集的上下文中使用DLR来调用匿名属性。


1
很棒的小把戏:)至少在这种情况下,不知道它是否比仅带有一堆公共属性的普通类更好。
Andrew Backer

4

编写一个控制台应用程序,并添加Mono.Cecil作为参考(您现在可以从NuGet中添加它),然后编写以下代码:

static void Main(string[] args)
{
    var asmFile = args[0];
    Console.WriteLine("Making anonymous types public for '{0}'.", asmFile);

    var asmDef = AssemblyDefinition.ReadAssembly(asmFile, new ReaderParameters
    {
        ReadSymbols = true
    });

    var anonymousTypes = asmDef.Modules
        .SelectMany(m => m.Types)
        .Where(t => t.Name.Contains("<>f__AnonymousType"));

    foreach (var type in anonymousTypes)
    {
        type.IsPublic = true;
    }

    asmDef.Write(asmFile, new WriterParameters
    {
        WriteSymbols = true
    });
}

上面的代码将从输入args获取程序集文件,并使用Mono.Cecil将可访问性从内部更改为公共,从而解决了问题。

我们可以在网站的Post Build事件中运行该程序。我用中文写了一篇博客文章,但我相信您可以阅读代码和快照。:)


2

基于已接受的答案,我已重写控制器以使其能够在一般情况下和在幕后工作。

这是代码:

protected override void OnResultExecuting(ResultExecutingContext filterContext)
{
    base.OnResultExecuting(filterContext);

    //This is needed to allow the anonymous type as they are intenal to the assembly, while razor compiles .cshtml files into a seperate assembly
    if (ViewData != null && ViewData.Model != null && ViewData.Model.GetType().IsNotPublic)
    {
       try
       {
          IDictionary<string, object> expando = new ExpandoObject();
          (new RouteValueDictionary(ViewData.Model)).ToList().ForEach(item => expando.Add(item));
          ViewData.Model = expando;
       }
       catch
       {
           throw new Exception("The model provided is not 'public' and therefore not avaialable to the view, and there was no way of handing it over");
       }
    }
}

现在,您只需传递一个匿名对象作为模型,它将按预期工作。



0

引发RuntimeBinderException的原因,我认为其他帖子中也有很好的答案。我只是专注于解释我实际上是如何工作的。

通过参考答案@DotNetWise和ASP.NET MVC中具有匿名类型集合的绑定视图

首先,为扩展创建一个静态类

public static class impFunctions
{
    //converting the anonymous object into an ExpandoObject
    public static ExpandoObject ToExpando(this object anonymousObject)
    {
        //IDictionary<string, object> anonymousDictionary = new RouteValueDictionary(anonymousObject);
        IDictionary<string, object> anonymousDictionary = HtmlHelper.AnonymousObjectToHtmlAttributes(anonymousObject);
        IDictionary<string, object> expando = new ExpandoObject();
        foreach (var item in anonymousDictionary)
            expando.Add(item);
        return (ExpandoObject)expando;
    }
}

在控制器中

    public ActionResult VisitCount()
    {
        dynamic Visitor = db.Visitors
                        .GroupBy(p => p.NRIC)
                        .Select(g => new { nric = g.Key, count = g.Count()})
                        .OrderByDescending(g => g.count)
                        .AsEnumerable()    //important to convert to Enumerable
                        .Select(c => c.ToExpando()); //convert to ExpandoObject
        return View(Visitor);
    }

在View中,@ model IEnumerable(动态的,不是模型类)非常重要,因为我们将绑定匿名类型对象。

@model IEnumerable<dynamic>

@*@foreach (dynamic item in Model)*@
@foreach (var item in Model)
{
    <div>x=@item.nric, y=@item.count</div>
}

在foreach中的类型,使用vardynamic我都没有错误

顺便说一句,创建与新字段匹配的新ViewModel也是将结果传递给视图的一种方式。


0

现在具有递归的味道

public static ExpandoObject ToExpando(this object obj)
    {
        IDictionary<string, object> expandoObject = new ExpandoObject();
        new RouteValueDictionary(obj).ForEach(o => expandoObject.Add(o.Key, o.Value == null || new[]
        {
            typeof (Enum),
            typeof (String),
            typeof (Char),
            typeof (Guid),

            typeof (Boolean),
            typeof (Byte),
            typeof (Int16),
            typeof (Int32),
            typeof (Int64),
            typeof (Single),
            typeof (Double),
            typeof (Decimal),

            typeof (SByte),
            typeof (UInt16),
            typeof (UInt32),
            typeof (UInt64),

            typeof (DateTime),
            typeof (DateTimeOffset),
            typeof (TimeSpan),
        }.Any(oo => oo.IsInstanceOfType(o.Value))
            ? o.Value
            : o.Value.ToExpando()));

        return (ExpandoObject) expandoObject;
    }

0

使用ExpandoObject Extension可以工作,但是在使用嵌套匿名对象时会中断。

var projectInfo = new {
 Id = proj.Id,
 UserName = user.Name
};

var workitem = WorkBL.Get(id);

return View(new
{
  Project = projectInfo,
  WorkItem = workitem
}.ToExpando());

为此,我使用它。

public static class RazorDynamicExtension
{
    /// <summary>
    /// Dynamic object that we'll utilize to return anonymous type parameters in Views
    /// </summary>
    public class RazorDynamicObject : DynamicObject
    {
        internal object Model { get; set; }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            if (binder.Name.ToUpper() == "ANONVALUE")
            {
                result = Model;
                return true;
            }
            else
            {
                PropertyInfo propInfo = Model.GetType().GetProperty(binder.Name);

                if (propInfo == null)
                {
                    throw new InvalidOperationException(binder.Name);
                }

                object returnObject = propInfo.GetValue(Model, null);

                Type modelType = returnObject.GetType();
                if (modelType != null
                    && !modelType.IsPublic
                    && modelType.BaseType == typeof(Object)
                    && modelType.DeclaringType == null)
                {
                    result = new RazorDynamicObject() { Model = returnObject };
                }
                else
                {
                    result = returnObject;
                }

                return true;
            }
        }
    }

    public static RazorDynamicObject ToRazorDynamic(this object anonymousObject)
    {
        return new RazorDynamicObject() { Model = anonymousObject };
    }
}

除了使用ToRazorDynamic()而不是ToExpando()外,控制器中的用法相同。

在获取整个匿名对象的视图中,只需在最后添加“ .AnonValue”。

var project = @(Html.Raw(JsonConvert.SerializeObject(Model.Project.AnonValue)));
var projectName = @Model.Project.Name;

0

我尝试了ExpandoObject,但是它不适用于嵌套的匿名复杂类型,如下所示:

var model = new { value = 1, child = new { value = 2 } };

所以我的解决方案是将JObject返回到View模型:

return View(JObject.FromObject(model));

并在.cshtml中转换为动态:

@using Newtonsoft.Json.Linq;
@model JObject

@{
    dynamic model = (dynamic)Model;
}
<span>Value of child is: @model.child.value</span>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.