Jenis Dynamic Anonymous di Razor menyebabkan RuntimeBinderException

156

Saya mendapatkan kesalahan berikut:

'objek' tidak mengandung definisi untuk 'RatingName'

Ketika Anda melihat tipe dinamis anonim, itu jelas memiliki RatingName.

Cuplikan layar Kesalahan

Saya menyadari bahwa saya dapat melakukan ini dengan Tuple, tetapi saya ingin memahami mengapa pesan kesalahan terjadi.

JarrettV
sumber

Jawaban:

240

Jenis anonim yang memiliki properti internal adalah keputusan desain kerangka NET buruk, menurut pendapat saya.

Berikut ini adalah ekstensi cepat dan bagus untuk memperbaiki masalah ini yaitu dengan mengubah objek anonim menjadi ExpandoObject segera.

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

Ini sangat mudah digunakan:

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

Tentu saja menurut Anda:

@foreach (var item in Model) {
     <div>x = @item.x, y = @item.y</div>
}
Adaptabi
sumber
2
+1 Saya secara khusus mencari HtmlHelper.AnonymousObjectToHtmlAttribut Saya tahu ini benar-benar harus dipanggang dan tidak ingin menemukan kembali roda dengan kode handrolled serupa.
Chris Marisic
3
Seperti apa kinerjanya pada ini, dibandingkan dengan hanya membuat model dukungan yang sangat diketik?
GONeale
@DotNetWise, Mengapa Anda menggunakan HtmlHelper.AnonymousObjectToHtmlAttributes ketika Anda bisa melakukan IDictionary <string, objek> anonymousDictionary = RouteDictionary baru (objek)?
Jeremy Boyd
Saya telah menguji HtmlHelper.AnonymousObjectToHtmlAttributes dan berfungsi seperti yang diharapkan. Solusi Anda juga dapat bekerja. Gunakan mana yang lebih mudah :)
Adaptabi
Jika Anda ingin itu menjadi solusi permanen, Anda bisa juga hanya menimpa perilaku di controller Anda, tetapi itu memerlukan beberapa solusi lagi, seperti bisa mengidentifikasi jenis anonim dan membuat kamus string / objek dari jenis sendiri. Jika Anda melakukannya, Anda dapat menimpanya di: protected override System.Web.Mvc.ViewResult View (string viewName, string masterName, model objek)
Johny Skovdal
50

Saya menemukan jawabannya dalam pertanyaan terkait . Jawabannya ditentukan pada posting blog David Ebbo. Melewatkan benda anonim ke tampilan MVC dan mengaksesnya menggunakan dinamis

Alasan untuk ini adalah bahwa jenis anonim yang dilewatkan di controller di internal, sehingga hanya dapat diakses dari dalam perakitan di mana ia dinyatakan. Karena pandangan dikompilasi secara terpisah, pengikat dinamis mengeluh bahwa itu tidak dapat melampaui batas perakitan.

Tetapi jika Anda memikirkannya, pembatasan dari pengikat dinamis ini sebenarnya cukup palsu, karena jika Anda menggunakan refleksi pribadi, tidak ada yang menghentikan Anda untuk mengakses anggota internal tersebut (ya, itu bahkan bekerja dengan kepercayaan sedang). Jadi pengikat dinamis default sedang berupaya untuk menegakkan aturan kompilasi C # (di mana Anda tidak dapat mengakses anggota internal), alih-alih membiarkan Anda melakukan apa yang memungkinkan runtime CLR.

JarrettV
sumber
Kocok saya untuk itu :) Saya mengalami masalah ini dengan Razor Engine saya (pendahulu dari yang ada di razorengine.codeplex.com )
Buildstarted
Ini sebenarnya bukan jawaban, tidak mengatakan lebih banyak tentang "jawaban yang diterima"!
Adaptabi
4
@DotNetWise: Ini menjelaskan mengapa kesalahan terjadi, yang merupakan pertanyaan. Anda juga mendapatkan dukungan saya untuk memberikan solusi yang bagus :)
Lucas
FYI: jawaban ini sekarang sudah ketinggalan zaman - karena penulis mengatakan dirinya merah di awal posting blog yang dirujuk
Simon_Weaver
@Simon_Weaver Tapi pembaruan posting tidak menjelaskan bagaimana seharusnya bekerja di MVC3 +. - Saya menemukan masalah yang sama di MVC 4. Adakah petunjuk tentang cara yang saat ini 'diberkati' menggunakan dinamis?
Cristian Diaconescu
24

Menggunakan ToExpando metode adalah solusi terbaik.

Ini adalah versi yang tidak memerlukan perakitan 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;
}
alexey
sumber
1
Itu jawaban yang lebih baik. Tidak yakin apakah seperti apa yang dilakukan HtmlHelper dengan menggarisbawahi jawaban alternatif.
Den
+1 untuk jawaban tujuan umum, ini berguna di luar ASP / MVC
codenheim
bagaimana dengan properti dinamis bersarang? mereka akan terus menjadi dinamis ... misalnya: `{foo:" foo ", nestedDynamic: {blah:" blah "}}
sports
16

Alih-alih membuat model dari jenis anonim dan kemudian mencoba mengonversi objek anonim menjadi ExpandoObjectseperti ini ...

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

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

Anda bisa langsung membuat ExpandoObject:

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

return View(model);

Kemudian dalam tampilan Anda, Anda menetapkan jenis model sebagai dinamis @model dynamicdan Anda dapat mengakses properti secara langsung:

@Model.Profile.Name
@Model.Foo

Saya biasanya merekomendasikan model tampilan yang sangat diketik untuk sebagian besar tampilan, tetapi kadang-kadang fleksibilitas ini berguna.

Simon_Weaver
sumber
@Yohal Anda pasti bisa - Saya kira itu adalah pilihan pribadi. Saya lebih suka menggunakan ViewBag untuk data halaman lain-lain yang umumnya tidak terkait dengan model halaman - mungkin terkait dengan template dan menjaga Model sebagai model utama
Simon_Weaver
2
BTW Anda tidak perlu menambahkan @ model dinamis, karena ini adalah default
yoel halb
persis apa yang saya butuhkan, menerapkan metode untuk mengkonversi objek anon ke objek Expando mengambil terlalu banyak waktu ...... terima kasih heaps
h-rai
5

Anda dapat menggunakan antarmuka kerangka kerja dadakan untuk membungkus tipe anonim dalam sebuah antarmuka.

Anda baru saja mengembalikan IEnumerable<IMadeUpInterface>dan pada akhir Linq Anda menggunakan .AllActLike<IMadeUpInterface>();ini berfungsi karena ini memanggil properti anonim menggunakan DLR dengan konteks perakitan yang menyatakan tipe anonim.

jbtule
sumber
1
Trik kecil yang luar biasa :) Tidak tahu apakah ini lebih baik dari sekadar kelas biasa dengan banyak properti publik, setidaknya dalam kasus ini .
Andrew Backer
4

Menulis aplikasi konsol dan menambahkan Mono.Cecil sebagai referensi (Anda sekarang dapat menambahkannya dari NuGet ), kemudian menulis potongan kode:

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

Kode di atas akan mendapatkan file assembly dari input args dan menggunakan Mono.Cecil untuk mengubah aksesibilitas dari internal ke publik, dan itu akan menyelesaikan masalah.

Kita dapat menjalankan program di acara Post Build situs web. Saya menulis posting blog tentang ini dalam bahasa Cina tetapi saya yakin Anda bisa membaca kode dan snapshot. :)

Jeffrey Zhao
sumber
2

Berdasarkan jawaban yang diterima, saya telah menimpa controller untuk membuatnya bekerja secara umum dan di belakang layar.

Ini kodenya:

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

Sekarang Anda bisa melewatkan objek anonim sebagai model, dan itu akan berfungsi seperti yang diharapkan.

halo yoel
sumber
0

Saya akan melakukan sedikit pencurian dari https://stackoverflow.com/a/7478600/37055

Jika Anda menginstal paket- dynamitey Anda dapat melakukan ini:

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

Dan para petani bersukacita.

Chris Marisic
sumber
0

Alasan RuntimeBinderException dipicu, saya pikir ada jawaban bagus di posting lain. Saya hanya fokus untuk menjelaskan bagaimana sebenarnya saya membuatnya bekerja.

Dengan merujuk ke jawaban @DotNetWise dan Binding dilihat dengan koleksi tipe Anonim di ASP.NET MVC ,

Pertama, Buat kelas statis untuk ekstensi

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

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

Dalam View, @model IEnumerable (dinamis, bukan kelas model), ini sangat penting karena kita akan mengikat objek tipe anonim.

@model IEnumerable<dynamic>

@*@foreach (dynamic item in Model)*@
@foreach (var item in Model)
{
    <div>x=@item.nric, y=@item.count</div>
}

Ketik foreach, saya tidak memiliki kesalahan baik menggunakan var atau dinamis .

Omong-omong, buat ViewModel baru yang cocok dengan bidang baru juga bisa menjadi cara untuk meneruskan hasil ke tampilan.

V-SHY
sumber
0

Sekarang dalam rasa rekursif

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;
    }
Matas Vaitkevicius
sumber
0

Menggunakan ExpandoObject Extension berfungsi tetapi rusak saat menggunakan objek anonim bersarang.

Seperti

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

var workitem = WorkBL.Get(id);

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

Untuk mencapai ini saya menggunakan ini.

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

Penggunaan dalam pengontrol adalah sama kecuali Anda menggunakan ToRazorDynamic () alih-alih ToExpando ().

Dalam pandangan Anda untuk mendapatkan seluruh objek anonim, Anda cukup menambahkan ".AnonValue" di akhir.

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

Saya mencoba ExpandoObject tetapi tidak berhasil dengan tipe kompleks bersarang anonim seperti ini:

var model = new { value = 1, child = new { value = 2 } };

Jadi solusi saya adalah mengembalikan model JObject to View:

return View(JObject.FromObject(model));

dan konversikan ke dinamis dalam .cshtml:

@using Newtonsoft.Json.Linq;
@model JObject

@{
    dynamic model = (dynamic)Model;
}
<span>Value of child is: @model.child.value</span>
Guilherme Muniz
sumber