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

时间:2011-02-25 17:10:34

标签: dynamic asp.net-mvc-3 razor anonymous-types

我收到以下错误:

  

'object'不包含'RatingName'的定义

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

Screenshot of Error

我意识到我可以用元组做到这一点,但我想理解为什么会出现错误信息。

12 个答案:

答案 0 :(得分:237)

在我看来,具有内部属性的匿名类型是一个糟糕的.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>
}

答案 1 :(得分:50)

我在related question找到了答案。答案在David Ebbo的博客文章 Passing anonymous objects to MVC views and accessing them using dynamic

中指定
  

原因在于   在。中传递的匿名类型   控制器在内部,所以它只能   可以从程序集中访问   在其中宣布。由于意见   得到单独编译,动态   粘合剂抱怨它不能过去   装配边界。

     

但如果你考虑一下,这个   来自动态活页夹的限制是   实际上非常人为,因为如果   你使用私人反思,没有   阻止你访问这些   内部成员(是的,它甚至可以工作   中信任)。所以默认动态   粘合剂正在走向它   强制执行C#编译规则(其中   你不能访问内部成员),   而不是让你做CLR   运行时允许。

答案 2 :(得分:23)

使用 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;
}

答案 3 :(得分: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

我通常会为大多数视图推荐强类型视图模型,但有时这种灵活性很方便。

答案 4 :(得分:5)

您可以使用框架impromptu interface在界面中包装匿名类型。

你只需要返回一个IEnumerable<IMadeUpInterface>,并且在你的Linq结尾使用.AllActLike<IMadeUpInterface>();这是有效的,因为它使用DLR调用匿名属性,并使用声明匿名类型的程序集的上下文。

答案 5 :(得分: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
    });
}

上面的代码将从输入参数获取汇编文件,并使用Mono.Cecil将可访问性从内部更改为公共,这将解决问题。

我们可以在网站的Post Build活动中运行该程序。我写了a blog post about this in Chinese,但我相信你可以阅读代码和快照。 :)

答案 6 :(得分: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");
       }
    }
}

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

答案 7 :(得分:0)

我要从https://stackoverflow.com/a/7478600/37055

做一点偷窃

如果你安装包dynamitey,你可以这样做:

return View(Build<ExpandoObject>.NewObject(RatingName: name, Comment: comment));

农民们欢欣鼓舞。

答案 8 :(得分:0)

触发RuntimeBinderException的原因,我认为在其他帖子中有很好的答案。我只是专注于解释我是如何实现它的。

参考答案@DotNetWise和Binding views with Anonymous type collection in 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中的类型,我没有使用 var dynamic 的错误。

顺便说一下,创建一个与新字段匹配的新ViewModel也可以将结果传递给视图。

答案 9 :(得分: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;
    }

答案 10 :(得分: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;

答案 11 :(得分: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>