El tipo dynamic anónimo en Razor causa la excepción RuntimeBinderException

Me aparece el siguiente error:

‘objeto’ no contiene una definición para ‘Nombre de clasificación’

Cuando observa el tipo dynamic anónimo, claramente tiene RatingName.

Captura de pantalla del error

Me doy cuenta de que puedo hacer esto con un Tuple, pero me gustaría entender por qué aparece el mensaje de error.

Los tipos anónimos que tienen propiedades internas son, en mi opinión, una decisión de diseño de .NET pobre.

Aquí hay una extensión rápida y agradable para solucionar este problema, es decir, convirtiendo el objeto anónimo en un ExpandoObject de inmediato.

public static ExpandoObject ToExpando(this object anonymousObject) { IDictionary anonymousDictionary = new RouteValueDictionary(anonymousObject); IDictionary 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 su opinión:

 @foreach (var item in Model) { 
x = @item.x, y = @item.y
}

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 las vistas de MVC y acceder a ellas usando la dinámica

La razón para esto es que el tipo anónimo que se pasa en el controlador en interno, por lo que solo se puede acceder desde el ensamblaje en el que se ha declarado. Dado que las vistas se comstackn por separado, el enlazador dynamic se queja de que no puede pasar ese límite de ensamblaje.

Pero si lo piensas, esta restricción de la carpeta dinámica es en realidad bastante artificial, porque si usas la reflexión privada, nada te impide acceder a esos miembros internos (sí, incluso funciona en confianza media). Por lo tanto, la carpeta dinámica predeterminada se está esforzando para aplicar las reglas de comstackció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.

El uso del método ToExpando es la mejor solución.

Aquí está la versión que no requiere el ensamblado System.Web :

 public static ExpandoObject ToExpando(this object anonymousObject) { IDictionary 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 de un tipo anónimo y luego tratar de convertir el objeto anónimo a un ExpandoObject como este …

 var model = new { Profile = profile, Foo = foo }; return View(model.ToExpando()); // not a framework method (see other answers) 

Puedes simplemente 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 dynamic @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 impromptu del marco para envolver un tipo anónimo en una interfaz.

Simplemente devolverá un IEnumerable y al final de su uso de Linq .AllActLike(); esto funciona porque llama a la propiedad anónima utilizando el DLR con un contexto del ensamblado que declaró el tipo anónimo.

Escribió una aplicación de consola y agrega Mono.Cecil como referencia (ahora puede agregarlo desde NuGet ) y luego escribe 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 args de entrada y usaría Mono.Cecil para cambiar la accesibilidad de interno a público, y eso resolvería el problema.

Podemos ejecutar el progtwig 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 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 el paquete dynamitey , puede hacer esto:

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

Y los campesinos se regocijan.

La razón de RuntimeBinderException activada, creo que hay una buena respuesta en otras publicaciones. Solo me enfoco en explicar cómo realmente lo hago funcionar.

Consulte referir a @DotNetWise y vistas de enlace con la colección de tipos anónimos en ASP.NET MVC ,

En primer lugar, crea una clase estática para la extensión

 public static class impFunctions { //converting the anonymous object into an ExpandoObject public static ExpandoObject ToExpando(this object anonymousObject) { //IDictionary anonymousDictionary = new RouteValueDictionary(anonymousObject); IDictionary anonymousDictionary = HtmlHelper.AnonymousObjectToHtmlAttributes(anonymousObject); IDictionary 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 (dynamic, no una clase de modelo), esto es muy importante, ya que vamos a vincular el objeto de tipo anónimo.

 @model IEnumerable @*@foreach (dynamic item in Model)*@ @foreach (var item in Model) { 
x=@item.nric, y=@item.count
}

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