Il tipo Anonimo dinamico in Razor causa RuntimeBinderException

Sto ottenendo il seguente errore:

‘object’ non contiene una definizione per ‘RatingName’

Quando guardi il tipo dinamico anonimo, ha chiaramente RatingName.

Screenshot dell'errore

Mi rendo conto che posso farlo con una tupla, ma mi piacerebbe capire perché si verifica il messaggio di errore.

I tipi anonimi con proprietà interne sono, a mio parere, una decisione di progettazione di framework .NET scadente.

Ecco un’estensione rapida e piacevole per risolvere questo problema, ovvero convertendo immediatamente l’object anonimo in ExpandoObject.

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

È molto facile da usare:

 return View("ViewName", someLinq.Select(new { x=1, y=2}.ToExpando()); 

Naturalmente secondo te:

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

Ho trovato la risposta in una domanda correlata . La risposta è specificata sul post del blog di David Ebbo. Passa oggetti anonimi alle viste MVC e accedendoli utilizzando la dynamic

La ragione di ciò è che il tipo anonimo è passato nel controller in interno, quindi è ansible accedervi solo dall’assembly in cui è stato dichiarato. Poiché le viste vengono compilate separatamente, il raccoglitore dinamico si lamenta di non poter superare il limite dell’assieme.

Ma se ci pensi, questa restrizione dal raccoglitore dinamico è in realtà abbastanza artificiale, perché se usi la riflessione privata, nulla ti impedisce di accedere a quei membri interni (sì, funziona anche con il livello medio di fiducia). Pertanto, il raccoglitore dinamico predefinito si impegna a rispettare le regole di compilazione C # (in cui non è ansible accedere ai membri interni), invece di consentire all’utente di eseguire ciò che consente il runtime CLR.

L’ utilizzo del metodo ToExpando è la soluzione migliore.

Ecco la versione che non richiede l’ assembly 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; } 

Invece di creare un modello da un tipo anonimo e quindi provare a convertire l’object anonimo in un ExpandoObject come questo …

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

Puoi semplicemente creare direttamente ExpandoObject :

 dynamic model = new ExpandoObject(); model.Profile = profile; model.Foo = foo; return View(model); 

Quindi nella tua vista imposti il ​​tipo di modello come dinamico @model dynamic e puoi accedere direttamente alle proprietà:

 @Model.Profile.Name @Model.Foo 

Normalmente raccomanderei modelli di visualizzazione fortemente tipizzati per la maggior parte delle visualizzazioni, ma a volte questa flessibilità è utile.

È ansible utilizzare l’ interfaccia framework estemporanea per includere un tipo anonimo in un’interfaccia.

Dovresti solo restituire un IEnumerable e alla fine del tuo Linq usare. .AllActLike(); questo funziona perché chiama la proprietà anonima usando il DLR con un contesto dell’assembly che ha dichiarato il tipo anonimo.

Scrive un’applicazione console e aggiungi Mono.Cecil come riferimento (ora puoi aggiungerlo da NuGet ), quindi scrivi il codice:

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

Il codice sopra otterrebbe il file assembly dagli argomenti di input e userà Mono.Cecil per cambiare l’accessibilità da interna a pubblica, e ciò risolverebbe il problema.

Possiamo eseguire il programma nell’evento Post Build del sito web. Ho scritto un post sul blog in cinese ma credo che tu possa solo leggere il codice e le istantanee. 🙂

In base alla risposta accettata, ho sostituito il controller per farlo funzionare in generale e dietro le quinte.

Ecco il codice:

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

Ora puoi semplicemente passare un object anonimo come modello e funzionerà come previsto.

Ho intenzione di fare un po ‘di furto da https://stackoverflow.com/a/7478600/37055

Se installi il pacchetto dinamitey puoi farlo:

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

E i contadini si rallegrano.

Il motivo dell’triggerszione di RuntimeBinderException, penso che ci sia una buona risposta in altri post. Mi concentro solo a spiegare come effettivamente lo faccio funzionare.

Fare riferimento alla risposta a @DotNetWise e alle viste di associazione con la raccolta di tipi anonimi in ASP.NET MVC ,

Innanzitutto, crea una class statica per l’estensione

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

Nel controller

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

In Visualizza, @model IEnumerable (dinamico, non una class modello), questo è molto importante in quanto legheremo l’object di tipo anonimo.

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

Il tipo in foreach, non ho errori né usando vardinamici .

A proposito, creare un nuovo ViewModel che corrisponda ai nuovi campi può anche essere il modo per passare il risultato alla vista.

Ora in sapore ricorsivo

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