Answers:
在我看来,具有内部属性的匿名类型是一个糟糕的.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);
然后在视图中将模型类型设置为动态,@model dynamic
然后可以直接访问属性:
@Model.Profile.Name
@Model.Foo
通常,我建议大多数视图使用强类型视图模型,但是有时这种灵活性很方便。
您可以使用框架即兴界面将匿名类型包装在接口中。
您只需要返回一个IEnumerable<IMadeUpInterface>
,在Linq的末尾使用.AllActLike<IMadeUpInterface>();
此方法就可以了,因为它使用DLR在声明了匿名类型的程序集的上下文中使用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
});
}
上面的代码将从输入args获取程序集文件,并使用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窃取一些东西
如果安装包dynamitey你可以这样做:
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 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也是将结果传递给视图的一种方式。
现在具有递归的味道
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;
}
使用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;
我尝试了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>