Dynamique Anonyme de type de Rasoir causes RuntimeBinderException
j'obtiens l'erreur suivante:
'"objet" ne contient pas une définition pour 'RatingName'
quand vous regardez le type de dynamique anonyme, il a clairement un nom de notation.
je me rends compte que je peux le faire avec un Tuple, mais je voudrais comprendre pourquoi le message d'erreur se produit.
11 réponses
les types anonymes ayant des propriétés internes est une mauvaise décision. NET framework design, à mon avis.
voici un rapide et belle extension pour corriger ce problème i.e. en convertissant l'objet anonyme en un objet ExpandoObject immédiatement.
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 facile utiliser:
return View("ViewName", someLinq.Select(new { x=1, y=2}.ToExpando());
bien sûr, de votre point de vue:
@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 blog de David Ebbo passer des objets anonymes aux vues MVC et y accéder en utilisant dynamique
la raison en est que le le type anonyme étant passé dans le contrôleur en interne, donc il ne peut que être accessible à partir de l'Assemblée dans lequel elle est déclarée. Depuis vues obtenir compilé séparément, la dynamique binder se plaint que ça ne peut pas passer l'assemblée de la frontière.
mais si vous y pensez, ce restriction de la dynamique liant en fait tout à fait artificiel, parce que si vous utilisez la réflexion, rien n'est vous empêcher d'accéder à ces membres internes (Oui, cela fonctionne même dans Un niveau de confiance moyen). Donc la dynamique par défaut classeur est en train de sortir de sa façon de appliquer les règles de compilation C# (où vous ne pouvez pas y accéder membres internes), au lieu de vous laisser faire ce que le CLR runtime permet.
utilisant ToExpando est la meilleure solution.
Voici la version que ne nécessite pas de système.Web Assemblée:
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 et ensuite essayer de convertir l'objet anonyme en un ExpandoObject
comme ceci ...
var model = new
{
Profile = profile,
Foo = foo
};
return View(model.ToExpando()); // not a framework method (see other answers)
vous pouvez créer le ExpandoObject
directement:
dynamic model = new ExpandoObject();
model.Profile = profile;
model.Foo = foo;
return View(model);
puis dans votre vue vous définissez le type de modèle comme dynamique @model dynamic
et vous pouvez accéder aux propriétés directement :
@Model.Profile.Name
@Model.Foo
je recommande normalement des modèles de vue fortement typés pour la plupart des vues, mais parfois, cette souplesse est pratique.
vous pouvez utiliser le framework impromptu interface pour envelopper un type anonyme dans une interface.
il vous suffit de retourner un IEnumerable<IMadeUpInterface>
et à la fin de votre Linq utiliser .AllActLike<IMadeUpInterface>();
cela fonctionne parce qu'il appelle la propriété anonyme en utilisant le DLR avec un contexte de l'assemblée qui a déclaré le type anonyme.
écrit une application de console et ajoute Mono.Cecil comme référence (vous pouvez maintenant l'ajouter de NuGet ), puis écrire 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'assemblage à partir de l'entrée args et utiliser Mono.Cecil de changer l'accessibilité de l'intérieur au public, et cela résoudrait le problème.
nous pouvons exécuter le programme dans L'événement post Build du site. J'ai écrit un billet de blog à propos de cela en chinois mais je crois que vous pouvez juste lire le code et des instantanés. :)
basé sur la réponse acceptée, j'ai dépassé dans 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 que le modèle, et il fonctionne comme prévu.
je vais faire un peu de vol de https://stackoverflow.com/a/7478600/37055
si vous installez-package 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é, je pense qu'il ya une bonne réponse dans les autres messages. Je me concentre juste pour expliquer comment je fais marcher ça.
Par référence à réponse @DotNetWise et Liaison de vues avec Anonyme de type collection ASP.NET MVC ,
tout D'abord, créer 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);
}
en vue, @model IEnumerable (dynamique, n'est pas un modèle de classe), c'est très important comme nous allons lier l'anonyme de type objet.
@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 pas d'erreur en utilisant var ou dynamique .
par ailleurs, créer un nouveau modèle de vue qui correspond aux nouveaux champs peut aussi être le moyen de passer le résultat à la vue.
Maintenant récursive saveur
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;
}
utilisant L'Extension ExpandoObject fonctionne mais casse en utilisant des 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 accomplir ceci 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 de dans le controller est la même sauf que vous utilisez ToRazorDynamic() au lieu de ToExpando().
dans votre point de vue pour obtenir l'objet anonyme entier que vous venez d'ajouter".AnonValue" à la fin.
var project = @(Html.Raw(JsonConvert.SerializeObject(Model.Project.AnonValue)));
var projectName = @Model.Project.Name;