Respuestas:
Los tipos anónimos que tienen propiedades internas es una mala decisión de diseño de marco .NET, en mi opinión.
Aquí hay una extensión rápida y agradable para solucionar este problema, es decir, convirtiendo el objeto anónimo en un Objeto Expando de inmediato.
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;
}
Es muy fácil de usar:
return View("ViewName", someLinq.Select(new { x=1, y=2}.ToExpando());
Por supuesto en tu opinión:
@foreach (var item in Model) {
<div>x = @item.x, y = @item.y</div>
}
Encontré la respuesta en una pregunta relacionada . La respuesta se especifica en la publicación del blog de David Ebbo. Pasar objetos anónimos a vistas de MVC y acceder a ellos utilizando dinámicas.
La razón de esto es que el tipo anónimo se pasa en el controlador interno, por lo que solo se puede acceder desde el ensamblado en el que se declara. Como las vistas se compilan por separado, la carpeta dinámica se queja de que no puede superar ese límite de ensamblaje.
Pero si lo piensa, esta restricción del aglutinante dinámico es en realidad bastante artificial, porque si utiliza la reflexión privada, nada le impide acceder a esos miembros internos (sí, incluso funciona en la confianza media). Por lo tanto, el aglutinante dinámico predeterminado está haciendo todo lo posible para hacer cumplir las reglas de compilación de C # (donde no puede acceder a los miembros internos), en lugar de permitirle hacer lo que permite el tiempo de ejecución de CLR.
Usar el método ToExpando es la mejor solución.
Aquí está la versión que no requiere el ensamblaje de 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;
}
En lugar de crear un modelo a partir de un tipo anónimo y luego tratar de convertir el objeto anónimo en algo ExpandoObject
así ...
var model = new
{
Profile = profile,
Foo = foo
};
return View(model.ToExpando()); // not a framework method (see other answers)
Simplemente puede crear el ExpandoObject
directamente:
dynamic model = new ExpandoObject();
model.Profile = profile;
model.Foo = foo;
return View(model);
Luego, en su vista, establece el tipo de modelo como dinámico @model dynamic
y puede acceder a las propiedades directamente:
@Model.Profile.Name
@Model.Foo
Normalmente recomendaría modelos de vista fuertemente tipados para la mayoría de las vistas, pero a veces esta flexibilidad es útil.
Puede usar la interfaz improvisada de framework para envolver un tipo anónimo en una interfaz.
Simplemente devolvería un IEnumerable<IMadeUpInterface>
y al final de su uso de Linq .AllActLike<IMadeUpInterface>();
esto funciona porque llama a la propiedad anónima usando el DLR con un contexto del ensamblado que declaró el tipo anónimo.
Escribió una aplicación de consola y agregue Mono.Cecil como referencia (ahora puede agregarlo desde NuGet ), luego escriba el fragmento de código:
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
});
}
El código anterior obtendría el archivo de ensamblaje de los argumentos de entrada y usaría Mono.Cecil para cambiar la accesibilidad de interna a pública, y eso resolvería el problema.
Podemos ejecutar el programa en el evento Post Build del sitio web. Escribí una publicación de blog sobre esto en chino, pero creo que puedes leer el código y las instantáneas. :)
Basado en la respuesta aceptada, he anulado en el controlador para que funcione en general y detrás de escena.
Aquí está el código:
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");
}
}
}
Ahora puede pasar un objeto anónimo como modelo y funcionará como se espera.
Voy a robar un poco de https://stackoverflow.com/a/7478600/37055
Si instala dynamitey de paquete , puede hacer esto:
return View(Build<ExpandoObject>.NewObject(RatingName: name, Comment: comment));
Y los campesinos se regocijan.
La razón de la activación de RuntimeBinderException, creo que hay una buena respuesta en otras publicaciones. Solo me concentro en explicar cómo lo hago funcionar.
Por referirse a responder @DotNetWise y encuadernación con vistas colección de tipo anónimo en ASP.NET MVC ,
En primer lugar, crear una clase estática para extensión
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;
}
}
En el controlador
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);
}
En View, @model IEnumerable (dinámico, no una clase de modelo), esto es muy importante ya que vamos a vincular el objeto de tipo anónimo.
@model IEnumerable<dynamic>
@*@foreach (dynamic item in Model)*@
@foreach (var item in Model)
{
<div>x=@item.nric, y=@item.count</div>
}
El tipo en foreach, no tengo ningún error, ya sea usando var o dynamic .
Por cierto, crear un nuevo ViewModel que coincida con los nuevos campos también puede ser la forma de pasar el resultado a la vista.
Ahora en sabor recursivo
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;
}
Usar la extensión ExpandoObject funciona pero se rompe cuando se usan objetos anónimos anidados.
Como
var projectInfo = new {
Id = proj.Id,
UserName = user.Name
};
var workitem = WorkBL.Get(id);
return View(new
{
Project = projectInfo,
WorkItem = workitem
}.ToExpando());
Para lograr esto, uso esto.
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 };
}
}
El uso en el controlador es el mismo, excepto que usa ToRazorDynamic () en lugar de ToExpando ().
En su vista para obtener el objeto anónimo completo, simplemente agregue ".AnonValue" al final.
var project = @(Html.Raw(JsonConvert.SerializeObject(Model.Project.AnonValue)));
var projectName = @Model.Project.Name;
Probé el ExpandoObject pero no funcionó con un tipo complejo anónimo anidado como este:
var model = new { value = 1, child = new { value = 2 } };
Entonces, mi solución fue devolver un modelo JObject to View:
return View(JObject.FromObject(model));
y convertir a dinámico en .cshtml:
@using Newtonsoft.Json.Linq;
@model JObject
@{
dynamic model = (dynamic)Model;
}
<span>Value of child is: @model.child.value</span>