it-swarm.com.de

Dynamischer anonymer Typ in Razor verursacht RuntimeBinderException

Ich erhalte folgende Fehlermeldung:

'Objekt' enthält keine Definition für 'RatingName'

Wenn Sie sich den anonymen dynamischen Typ anschauen, hat er eindeutig RatingName.

Screenshot of Error

Mir ist klar, dass ich dies mit einem Tupel machen kann, aber ich möchte verstehen, warum die Fehlermeldung auftritt.

154
JarrettV

Anonyme Typen mit internen Eigenschaften sind meiner Meinung nach eine schlechte Entscheidung für das .NET-Framework.

Hier ist eine schnelle und schöne Erweiterung, um dieses Problem zu beheben, d.

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

Es ist sehr easy zu verwenden:

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

Natürlich aus Ihrer Sicht:

@foreach (var item in Model) {
     <div>x = @item.x, y = @item.y</div>
}
237
Adaptabi

Ich fand die Antwort in einer verwandten Frage . Die Antwort finden Sie in David Ebbos Blog-Post Anonyme Objekte an MVC-Ansichten übergeben und mithilfe von Dynamic darauf zugreifen

Der Grund dafür ist, dass der anonyme Typ im Controller intern übergeben wird und daher nur innerhalb der Assembly zugegriffen werden kann, in der er deklariert wurde. Da Ansichten separat kompiliert werden, beschwert sich der dynamische Ordner, dass er diese Baugruppengrenze nicht überschreiten kann.

Aber wenn Sie darüber nachdenken, ist diese Einschränkung des dynamischen Ordners tatsächlich ziemlich künstlich, denn wenn Sie private Reflektion verwenden, hindert Sie nichts daran, auf diese internen Mitglieder zuzugreifen (ja, es funktioniert sogar in mittlerer Vertrauenswürdigkeit). Das dynamische Standardbindemittel gibt sich also alle Mühe, C # -Kompilierungsregeln durchzusetzen (auf die Sie nicht zugreifen können), anstatt zuzulassen, was die CLR-Laufzeit zulässt.

50
JarrettV

Die beste Lösung ist die Verwendung von ToExpando .

Hier ist die Version, für die kein System.Web Assembly benötigt:

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
alexey

Anstatt ein Modell aus einem anonymen Typ zu erstellen und dann zu versuchen, das anonyme Objekt in eine ExpandoObject wie folgt zu konvertieren ...

var model = new 
{
    Profile = profile,
    Foo = foo
};

return View(model.ToExpando());  // not a framework method (see other answers)

Sie können einfach die ExpandoObject direkt erstellen:

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

return View(model);

Dann setzen Sie in Ihrer Ansicht den Modelltyp als dynamisch @model dynamic und können direkt auf die Eigenschaften zugreifen:

@Model.Profile.Name
@Model.Foo

Normalerweise würde ich stark typisierte Ansichtsmodelle für die meisten Ansichten empfehlen, aber manchmal ist diese Flexibilität praktisch.

16
Simon_Weaver

Sie können das Framework impromptu interface verwenden, um einen anonymen Typ in ein Interface einzubinden.

Sie würden nur einen IEnumerable<IMadeUpInterface> zurückgeben und am Ende Ihrer Linq-Funktion .AllActLike<IMadeUpInterface>(); verwenden, da dies die anonyme Eigenschaft mithilfe des DLR mit einem Kontext der Assembly aufruft, der den anonymen Typ deklariert.

5
jbtule

Schrieb eine Konsolenanwendung und fügte Mono.Cecil als Referenz hinzu (Sie können es jetzt über NuGet hinzufügen), und dann den Code schreiben:

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

Der obige Code würde die Assembly-Datei von den Eingabearggs abrufen und Mono.Cecil verwenden, um den Zugriff von intern auf public zu ändern. Dadurch würde das Problem gelöst.

Wir können das Programm im Post Build-Event der Website ausführen. Ich habe einen Blogbeitrag darüber auf Chinesisch geschrieben aber ich glaube, Sie können den Code und die Momentaufnahmen einfach lesen. :)

4
Jeffrey Zhao

Basierend auf der akzeptierten Antwort habe ich den Controller überschrieben, damit er allgemein und hinter den Kulissen funktioniert.

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

Jetzt können Sie einfach ein anonymes Objekt als Modell übergeben, und es funktioniert wie erwartet.

2
yoel halb

Jetzt im rekursiven Geschmack

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

Die Verwendung der ExpandoObject-Erweiterung funktioniert, bricht jedoch, wenn verschachtelte anonyme Objekte verwendet werden.

Sowie 

var projectInfo = new {
 Id = proj.Id,
 UserName = user.Name
};

var workitem = WorkBL.Get(id);

return View(new
{
  Project = projectInfo,
  WorkItem = workitem
}.ToExpando());

Um dies zu erreichen, benutze ich das.

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

Die Verwendung im Controller ist dieselbe, außer dass Sie ToRazorDynamic () anstelle von ToExpando () verwenden.

Um das gesamte anonyme Objekt zu erhalten, fügen Sie am Ende einfach ".AnonValue" hinzu.

var project = @(Html.Raw(JsonConvert.SerializeObject(Model.Project.AnonValue)));
var projectName = @Model.Project.Name;
0
Donny V.

Der Grund der RuntimeBinderException ausgelöst hat, finde ich gute Antwort in anderen Beiträgen. Ich konzentriere mich nur darauf, zu erklären, wie ich es tatsächlich zum Laufen bringen kann.

Siehe Antwort @DotNetWise und Binden von Ansichten mit der anonymen Typauflistung in ASP.NET MVC ,

Erstellen Sie zunächst eine statische Klasse für die Erweiterung

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

Im 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 (dynamisch, keine Modellklasse) ist dies sehr wichtig, da wir das anonyme Typobjekt binden werden.

@model IEnumerable<dynamic>

@*@foreach (dynamic item in Model)*@
@foreach (var item in Model)
{
    <div>[email protected], [email protected]</div>
}

Der Typ in foreach, ich habe keine Fehler mit var oder dynamic.

Erstellen Sie übrigens ein neues ViewModel, das mit den neuen Feldern übereinstimmt. Außerdem können Sie das Ergebnis an die Ansicht übergeben.

0
V-SHY

Ich werde ein wenig stehlen von https://stackoverflow.com/a/7478600/37055

Wenn Sie das Paket dynamitey installieren, können Sie Folgendes tun:

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

Und die Bauern freuen sich.

0
Chris Marisic