Razor中的动态匿名类型会导致RuntimeBinderException
我收到以下错误:
'object'不包含'RatingName'的定义
当你看看匿名动态类型时,它显然有RatingName。
我意识到我可以用一个元组来做到这一点,但我想知道为什么会出现错误信息。
具有内部属性的匿名类型是一个糟糕的.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> }
我在一个相关的问题中找到了答案。 答案在David Ebbo的博客文章中指定将匿名对象传递给MVC视图并使用动态访问它们
原因是匿名类型在内部传递给控制器,所以只能从声明的程序集中访问。 由于视图是单独编译的,因此动态绑定程序会报告它无法遍历该程序集边界。
但是如果你仔细想一下,这个来自动态联编程序的限制实际上是非常人为的,因为如果你使用私人反射,没有任何东西阻止你访问这些内部成员(是的,它甚至在中等信任下工作)。 因此,默认的动态联编程序正在执行C#编译规则(无法访问内部成员),而不是让您执行CLR运行时允许的方式。
使用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; }
而不是从匿名类型创建模型,然后尝试将匿名对象转换为像这样的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);
然后在您的视图中,将模型类型设置为dynamic @model dynamic
,您可以直接访问属性:
@Model.Profile.Name @Model.Foo
我通常会推荐大多数视图强类型的视图模型,但有时这种灵活性是方便的。
您可以使用框架即兴接口在接口中包装匿名类型。
你只要返回一个IEnumerable<IMadeUpInterface>
并在你的Linq结尾使用.AllActLike<IMadeUpInterface>();
这是有效的,因为它使用声明了匿名类型的程序集的上下文使用DLR来调用匿名属性。
编写一个控制台应用程序并添加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事件中运行程序。 我用中文写了一篇关于这个的博客文章,但是我相信你可以阅读代码和快照。 🙂
基于接受的答案,我已经在控制器中重写,使其在一般和幕后工作。
这里是代码:
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引发的原因,我觉得在其他帖子里有很好的答案。 我只是集中解释我是如何实际工作的。
通过在ASP.NET MVC中使用匿名类型集合引用answer @DNetNet和Binding视图 ,
首先,为扩展创建一个静态类
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); }
在视图中,@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或动态 。
顺便说一句,创建一个新的视图模型是匹配新的领域也可以将结果传递到视图的方式。
现在在递归的味道
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; }
- Android 1.6:“android.view.WindowManager $ BadTokenException:无法添加窗口 – 标记null不适用于应用程序”
- jQuery:获取jQuery隐藏元素的高度