Jawaban:
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>
}
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.
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;
}
Alih-alih membuat model dari jenis anonim dan kemudian mencoba mengonversi objek anonim menjadi ExpandoObject
seperti 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 dynamic
dan 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.
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.
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. :)
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.
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.
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.
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;
}
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;
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>