Réponses:
À mon avis, les types anonymes ayant des propriétés internes sont une mauvaise décision de conception du framework .NET.
Voici une extension rapide et agréable pour résoudre ce problème, c'est-à-dire en convertissant immédiatement l'objet anonyme en 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;
}
C'est très simple à utiliser:
return View("ViewName", someLinq.Select(new { x=1, y=2}.ToExpando());
Bien sûr, à votre avis:
@foreach (var item in Model) {
<div>x = @item.x, y = @item.y</div>
}
J'ai trouvé la réponse dans une question connexe . La réponse est spécifiée sur le billet de blog de David Ebbo Passer des objets anonymes aux vues MVC et y accéder en utilisant des
La raison en est que le type anonyme étant passé dans le contrôleur en interne, il n'est donc accessible qu'à partir de l'assembly dans lequel il est déclaré. Étant donné que les vues sont compilées séparément, le classeur dynamique se plaint de ne pas pouvoir dépasser cette limite d'assemblage.
Mais si vous y réfléchissez bien, cette restriction du classeur dynamique est en fait assez artificielle, car si vous utilisez la réflexion privée, rien ne vous empêche d'accéder à ces membres internes (oui, cela fonctionne même en confiance moyenne). Ainsi, le classeur dynamique par défaut fait tout son possible pour appliquer les règles de compilation C # (où vous ne pouvez pas accéder aux membres internes), au lieu de vous laisser faire ce que le runtime CLR permet.
L' utilisation de la méthode ToExpando est la meilleure solution.
Voici la version qui ne nécessite pas l' assemblage 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;
}
Au lieu de créer un modèle à partir d'un type anonyme, puis d'essayer de convertir l'objet anonyme en un objet ExpandoObject
similaire ...
var model = new
{
Profile = profile,
Foo = foo
};
return View(model.ToExpando()); // not a framework method (see other answers)
Vous pouvez simplement créer ExpandoObject
directement:
dynamic model = new ExpandoObject();
model.Profile = profile;
model.Foo = foo;
return View(model);
Ensuite, dans votre vue, vous définissez le type de modèle comme dynamique @model dynamic
et vous pouvez accéder directement aux propriétés:
@Model.Profile.Name
@Model.Foo
Je recommande normalement des modèles de vue fortement typés pour la plupart des vues, mais cette flexibilité est parfois pratique.
Vous pouvez utiliser l' interface impromptue du framework pour envelopper un type anonyme dans une interface.
Vous venez de retourner un IEnumerable<IMadeUpInterface>
et à la fin de votre utilisation Linq, .AllActLike<IMadeUpInterface>();
cela fonctionne car il appelle la propriété anonyme en utilisant le DLR avec un contexte de l'assembly qui a déclaré le type anonyme.
A écrit une application console et ajoutez Mono.Cecil comme référence (vous pouvez maintenant l'ajouter à partir de NuGet ), puis écrivez le morceau de code:
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
});
}
Le code ci-dessus obtiendrait le fichier d'assembly à partir des arguments d'entrée et utiliserait Mono.Cecil pour modifier l'accessibilité de interne à public, ce qui résoudrait le problème.
Nous pouvons exécuter le programme dans l'événement Post Build du site Web. J'ai écrit un article de blog à ce sujet en chinois, mais je pense que vous pouvez simplement lire le code et les instantanés. :)
Sur la base de la réponse acceptée, j'ai remplacé le contrôleur pour le faire fonctionner en général et dans les coulisses.
Voici le code:
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");
}
}
}
Maintenant, vous pouvez simplement passer un objet anonyme comme modèle, et cela fonctionnera comme prévu.
Je vais faire un peu de vol sur https://stackoverflow.com/a/7478600/37055
Si vous installez dynamitey, vous pouvez faire ceci:
return View(Build<ExpandoObject>.NewObject(RatingName: name, Comment: comment));
Et les paysans se réjouissent.
La raison de RuntimeBinderException déclenchée, je pense qu'il y a une bonne réponse dans d'autres articles. Je me concentre simplement pour expliquer comment je le fais fonctionner.
En vous référant à la réponse @DotNetWise et vues de liaison avec collection de types anonyme dans ASP.NET MVC ,
Tout d'abord, créez une classe statique pour l'extension
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;
}
}
Dans le contrôleur
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);
}
Dans View, @model IEnumerable (dynamique, pas une classe de modèle), c'est très important car nous allons lier l'objet de type anonyme.
@model IEnumerable<dynamic>
@*@foreach (dynamic item in Model)*@
@foreach (var item in Model)
{
<div>x=@item.nric, y=@item.count</div>
}
Le type dans foreach, je n'ai aucune erreur en utilisant var ou dynamic .
En passant, créer un nouveau ViewModel qui correspond aux nouveaux champs peut également être le moyen de transmettre le résultat à la vue.
Maintenant en saveur récursive
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'utilisation de l'extension ExpandoObject fonctionne mais s'arrête lors de l'utilisation d'objets anonymes imbriqués.
Tel que
var projectInfo = new {
Id = proj.Id,
UserName = user.Name
};
var workitem = WorkBL.Get(id);
return View(new
{
Project = projectInfo,
WorkItem = workitem
}.ToExpando());
Pour ce faire, j'utilise ceci.
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'utilisation dans le contrôleur est la même sauf que vous utilisez ToRazorDynamic () au lieu de ToExpando ().
À votre avis, pour obtenir l'intégralité de l'objet anonyme, ajoutez simplement ".AnonValue" à la fin.
var project = @(Html.Raw(JsonConvert.SerializeObject(Model.Project.AnonValue)));
var projectName = @Model.Project.Name;
J'ai essayé ExpandoObject mais cela ne fonctionnait pas avec un type complexe anonyme imbriqué comme celui-ci:
var model = new { value = 1, child = new { value = 2 } };
Ma solution était donc de renvoyer un modèle JObject to View:
return View(JObject.FromObject(model));
et convertir en dynamique en .cshtml:
@using Newtonsoft.Json.Linq;
@model JObject
@{
dynamic model = (dynamic)Model;
}
<span>Value of child is: @model.child.value</span>