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.

Screenshot of Error

je me rends compte que je peux le faire avec un Tuple, mais je voudrais comprendre pourquoi le message d'erreur se produit.

153
demandé sur JarrettV 2011-02-25 20:10:33

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>
}
233
répondu Adaptabi 2013-05-03 19:07:50

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.

48
répondu JarrettV 2017-05-23 12:02:13
La méthode

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;
}
22
répondu alexey 2013-03-29 09:29:16

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.

16
répondu Simon_Weaver 2015-08-26 04:17:14

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.

5
répondu jbtule 2011-03-02 05:11:23

é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. :)

4
répondu Jeffrey Zhao 2011-09-06 01:59:49

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.

2
répondu yoel halb 2013-07-04 17:31:32

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.

0
répondu Chris Marisic 2017-05-23 12:09:36

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.

0
répondu V-SHY 2015-05-27 04:23:27

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;
    }
0
répondu Matas Vaitkevicius 2015-07-13 15:58:57

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;
0
répondu Donny V. 2018-10-09 15:32:04