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

我得到了以下错误:

“ object”不包含“ RatingName”的定义

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

Screenshot of Error

我知道我可以使用 Tuple 来完成这项工作,但是我想了解为什么会出现错误消息。

62617 次浏览

我在 相关问题中找到了答案,答案在 David Ebbo 的博客文章 将匿名对象传递给 MVC 视图并使用动态访问它们中有详细说明

原因是 类中传递的匿名类型 控制器的内部,所以它只能 从程序集内访问 它的声明。因为观点 单独编译,动态的 活页夹抱怨它不能过去 那个集合边界。

但如果你仔细想想 来自动态绑定器的限制是 实际上是人造的,因为如果 你使用私人反射,没有什么是 阻止你访问这些数据 内部成员(是的,它甚至工作在 中等信任)。因此默认的动态 活页夹正在竭尽全力 执行 C # 编译规则(其中 不能访问内部成员) , 而不是让你做什么 CLR 运行时允许。

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

您只需返回一个 IEnumerable<IMadeUpInterface>,在 Linq 结束时使用 .AllActLike<IMadeUpInterface>();即可,因为它使用 DLR 和声明匿名类型的程序集的上下文调用匿名属性。

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

这里有一个快速的 不错的延伸来解决这个问题,即通过立即将匿名对象转换成一个 ExpendoObject。

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>
}

写了个控制台应用加上 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。塞西尔将可访问性从内部改为公共,这将解决问题。

我们可以在网站的 Post Build 事件中运行程序。我写了 一篇关于这个的中文博客文章,但我相信你可以只读代码和快照。:)

而不是从匿名类型创建模型,然后尝试将匿名对象转换为像这样的 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

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

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

下面是 不需要系统汇编的版本:

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

根据已接受的答案,我已经在控制器中重写了它,以使其在一般情况下和幕后都能工作。

密码如下:

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");
}
}
}

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

我要从 https://stackoverflow.com/a/7478600/37055偷点东西

如果你安装 活力四射软件包,你可以这样做:

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

农民们欢欣鼓舞。

引发 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 IEnumable (动态的,而不是模型类) ,这非常重要,因为我们要绑定匿名类型对象。

@model IEnumerable<dynamic>


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

Foreach 中的类型,使用 Var充满活力都没有错误。

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

现在是递归风格

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

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

比如

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 ()而不是 ToExpado ()。

在您的视图中,要获得整个匿名对象,只需在末尾添加“ . AnonValue”即可。

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

我尝试使用 expandObject,但它不能处理嵌套的匿名复杂类型,比如:

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

所以我的解决方案是返回一个 JObject to View 模型:

return View(JObject.FromObject(model));

并在. cshtml 中转换为 Dynamic:

@using Newtonsoft.Json.Linq;
@model JObject


@{
dynamic model = (dynamic)Model;
}
<span>Value of child is: @model.child.value</span>