Dynamiczny anonimowy typ w Razor powoduje RuntimeBinderException

Dostaję następujący błąd:

'object' nie zawiera definicji 'RatingName'

Kiedy spojrzysz na anonimowy Typ Dynamiczny, wyraźnie ma nazwę RatingName.

Zrzut ekranu błędu

Zdaję sobie sprawę, że mogę to zrobić za pomocą krotki, ale chciałbym zrozumieć, dlaczego pojawia się komunikat o błędzie.

Author: JarrettV, 2011-02-25

11 answers

Typy anonimowe posiadające wewnętrzne właściwości to moim zdaniem kiepska decyzja projektowa.NET framework.

Oto szybkie i ładne rozszerzenie , aby rozwiązać ten problem, tj. poprzez natychmiastową konwersję anonimowego obiektu na 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;
}

To bardzo łatwe w użyciu:

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

Oczywiście Twoim zdaniem:

@foreach (var item in Model) {
     <div>x = @item.x, y = @item.y</div>
}
 233
Author: Adaptabi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-03 19:07:50

Znalazłem odpowiedź w pytanie powiązane . Odpowiedź jest podana na blogu Davida Ebbo przekazywanie anonimowych obiektów do widoków MVC i uzyskiwanie do nich dostępu za pomocą dynamicznych

Powodem tego jest to, że anonimowy Typ przekazywany w kontroler wewnętrzny, więc może tylko być dostępne z wewnątrz zespołu w którym jest zadeklarowana. Od views się skompilować oddzielnie, dynamiczny binder skarży się, że nie może przejść że granica montażu.

Ale jeśli się nad tym zastanowić, to ograniczenie od spoiwa dynamicznego jest właściwie całkiem sztuczny, bo jeśli używasz prywatnej refleksji, nic nie jest powstrzymuje cię przed dostępem do tych członków wewnętrznych (tak, działa nawet w Średnie zaufanie). Więc domyślna dynamika binder wychodzi z drogi do egzekwowanie reguł kompilacji C# (gdzie nie możesz uzyskać dostępu do wewnętrznych członków), zamiast pozwolić ci robić to, co CLR runtime pozwala.

 48
Author: JarrettV,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:13

Użycie metody toexpando jest najlepszym rozwiązaniem.

Oto wersja, która nie wymaga systemu.Web assembly:

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
Author: alexey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-29 09:29:16

Zamiast tworzyć model z anonimowego typu, a następnie próbować przekonwertować anonimowy obiekt na ExpandoObject w ten sposób ...

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

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

Możesz po prostu utworzyć ExpandoObject bezpośrednio:

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

return View(model);

Następnie w widoku ustawiasz typ modelu jako dynamiczny @model dynamic i możesz uzyskać bezpośredni dostęp do właściwości:

@Model.Profile.Name
@Model.Foo

Normalnie zalecałbym mocno wpisane modele widoków dla większości widoków, ale czasami ta elastyczność jest przydatna.

 16
Author: Simon_Weaver,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-26 04:17:14

Możesz użyć frameworka impromptu interface aby zawinąć anonimowy typ w interfejsie.

Po prostu zwrócisz IEnumerable<IMadeUpInterface> i na końcu Linq użyj .AllActLike<IMadeUpInterface>(); to działa, ponieważ wywołuje właściwość anonymous używając DLR z kontekstem zestawu, który zadeklarował Typ anonymous.

 5
Author: jbtule,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-02 05:11:23

Napisał aplikację konsolową i dodał Mono.Cecil jako referencję (możesz teraz dodać go z NuGet), a następnie napisać fragment kodu:

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

Powyższy kod pobierze plik assembly z wejściowych args i użyje Mono.Cecil do zmiany dostępności z wewnętrznej na publiczną, a to rozwiązałoby problem.

Możemy uruchomić program w zdarzeniu Post Build strony internetowej. Napisałem wpis na blogu o tym po chińsku ale wierzę, że można po prostu przeczytać kod i migawki. :)

 4
Author: Jeffrey Zhao,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-06 01:59:49

Bazując na zaakceptowanej odpowiedzi, przesadziłem w kontrolerze, aby działał ogólnie i za kulisami.

Oto kod:

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

Teraz możesz po prostu przekazać anonimowy obiekt jako model i będzie działał zgodnie z oczekiwaniami.

 2
Author: yoel halb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-04 17:31:32

/ Align = "left" / https://stackoverflow.com/a/7478600/37055

Jeśli zainstalujesz pakiet dynamitey możesz to zrobić:

return View(Build<ExpandoObject>.NewObject(RatingName: name, Comment: comment));
I chłopi się radują.
 0
Author: Chris Marisic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:09:36

Powód RuntimeBinderException wywołane, myślę, że nie mają dobrą odpowiedź w innych postach. Skupiam się na wyjaśnieniu, jak to działa.

Przez odniesienie do answer @ DotNetWise i wiążących widoki z anonimowym zbiorem typu w ASP.NET MVC ,

Najpierw Utwórz klasę statyczną dla rozszerzenia

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

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

W widoku @model IEnumerable (dynamiczny, Nie Klasa modelu), jest to bardzo ważne, ponieważ zamierzamy związać anonimowy wpisz obiekt.

@model IEnumerable<dynamic>

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

Typ w foreach, nie mam błędu ani używając var ani dynamic.

Nawiasem mówiąc, utwórz nowy model widoku, który pasuje do nowych pól, może również być sposobem przekazania wyniku do widoku.

 0
Author: V-SHY,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-27 04:23:27

Teraz w rekurencyjnym smaku

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
Author: Matas Vaitkevicius,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-13 15:58:57

Używanie rozszerzenia ExpandoObject działa, ale ulega awarii podczas używania zagnieżdżonych anonimowych obiektów.

Takie jak

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

var workitem = WorkBL.Get(id);

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

Aby to osiągnąć, używam tego.

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

Użycie w kontrolerze jest takie samo, z wyjątkiem tego, że używasz torazordynamic () zamiast ToExpando ().

W Twoim widoku aby uzyskać cały anonimowy obiekt wystarczy dodać ".AnonValue" do końca.

var project = @(Html.Raw(JsonConvert.SerializeObject(Model.Project.AnonValue)));
var projectName = @Model.Project.Name;
 0
Author: Donny V.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-09 15:32:04