Risposte:
I tipi anonimi con proprietà interne sono una cattiva decisione di progettazione del framework .NET, secondo me.
Ecco un'estensione rapida e piacevole per risolvere questo problema, ad esempio convertendo subito l'oggetto anonimo in 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;
}
È molto facile da usare:
return View("ViewName", someLinq.Select(new { x=1, y=2}.ToExpando());
Naturalmente a tuo avviso:
@foreach (var item in Model) {
<div>x = @item.x, y = @item.y</div>
}
Ho trovato la risposta in una domanda correlata . La risposta è specificata sul post del blog di David Ebbo Passare oggetti anonimi alle viste MVC e accedervi utilizzando la dinamica
La ragione di ciò è che il tipo anonimo viene passato nel controller all'interno, quindi è possibile accedervi solo all'interno dell'assembly in cui è dichiarato. Poiché le viste vengono compilate separatamente, il raccoglitore dinamico si lamenta del fatto che non può oltrepassare quel limite di assieme.
Ma se ci pensate, questa restrizione dal raccoglitore dinamico è in realtà abbastanza artificiale, perché se usate la riflessione privata, nulla vi impedisce di accedere a quei membri interni (sì, funziona anche con fiducia Media). Quindi il raccoglitore dinamico predefinito fa di tutto per imporre le regole di compilazione C # (dove non è possibile accedere ai membri interni), invece di lasciarti fare ciò che il runtime CLR consente.
L' uso del metodo ToExpando è la soluzione migliore.
Ecco la versione che non richiede l' assemblaggio di 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;
}
Invece di creare un modello da un tipo anonimo e quindi provare a convertire l'oggetto anonimo in un ExpandoObject
simile ...
var model = new
{
Profile = profile,
Foo = foo
};
return View(model.ToExpando()); // not a framework method (see other answers)
Puoi semplicemente creare ExpandoObject
direttamente:
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 consiglierei modelli di vista fortemente tipizzati per la maggior parte delle viste, ma a volte questa flessibilità è utile.
È possibile utilizzare l' interfaccia improvvisata del framework per racchiudere un tipo anonimo in un'interfaccia.
Restituiresti semplicemente un IEnumerable<IMadeUpInterface>
e alla fine del tuo Linq utilizzi .AllActLike<IMadeUpInterface>();
questo lavoro perché chiama la proprietà anonima utilizzando il DLR con un contesto dell'assembly che ha dichiarato il tipo anonimo.
Ha scritto un'applicazione console e aggiungi Mono.Cecil come riferimento (ora puoi aggiungerlo da NuGet ), quindi scrivi il pezzo di 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 da input args e userebbe Mono.Cecil per cambiare l'accessibilità da interno a pubblico 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 leggere il codice e le istantanee. :)
Sulla base della risposta accettata, ho ignorato 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<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");
}
}
}
Ora puoi semplicemente passare un oggetto 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 dynamitey puoi farlo:
return View(Build<ExpandoObject>.NewObject(RatingName: name, Comment: comment));
E i contadini si rallegrano.
Il motivo di RuntimeBinderException è stato attivato, penso che ci sia una buona risposta in altri post. Mi concentro solo per spiegare come lo faccio effettivamente funzionare.
Facendo riferimento a rispondere alle visualizzazioni @DotNetWise e Binding con raccolta di tipi anonimi in ASP.NET MVC ,
Innanzitutto, crea una classe statica per l'estensione
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;
}
}
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 View, @model IEnumerable (dinamico, non una classe di modello), questo è molto importante in quanto assoceremo l'oggetto di tipo anonimo.
@model IEnumerable<dynamic>
@*@foreach (dynamic item in Model)*@
@foreach (var item in Model)
{
<div>x=@item.nric, y=@item.count</div>
}
Digitando foreach, non ho alcun errore nell'usare var o dynamic .
A proposito, creare un nuovo ViewModel che corrisponda ai nuovi campi può anche essere il modo per passare il risultato alla vista.
Ora dal sapore ricorsivo
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;
}
L'uso dell'estensione ExpandoObject funziona ma si interrompe quando si utilizzano oggetti anonimi nidificati.
Ad esempio
var projectInfo = new {
Id = proj.Id,
UserName = user.Name
};
var workitem = WorkBL.Get(id);
return View(new
{
Project = projectInfo,
WorkItem = workitem
}.ToExpando());
Per ottenere questo, lo uso.
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 };
}
}
L'utilizzo nel controller è lo stesso tranne per l'uso di ToRazorDynamic () anziché ToExpando ().
Nella tua visione per ottenere l'intero oggetto anonimo devi solo aggiungere ".AnonValue" alla fine.
var project = @(Html.Raw(JsonConvert.SerializeObject(Model.Project.AnonValue)));
var projectName = @Model.Project.Name;
Ho provato ExpandoObject ma non ha funzionato con un tipo complesso anonimo nidificato come questo:
var model = new { value = 1, child = new { value = 2 } };
Quindi la mia soluzione era di restituire un JObject al modello View:
return View(JObject.FromObject(model));
e converti in dinamico in .cshtml:
@using Newtonsoft.Json.Linq;
@model JObject
@{
dynamic model = (dynamic)Model;
}
<span>Value of child is: @model.child.value</span>