Câu trả lời:
Theo tôi, các kiểu ẩn danh có một quyết định thiết kế khung .NET kém.
Đây là một phần mở rộng nhanh chóng và tốt đẹp để khắc phục vấn đề này, tức là bằng cách chuyển đổi đối tượng ẩn danh thành ExpandoObject ngay lập tức.
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;
}
Nó rất dễ sử dụng:
return View("ViewName", someLinq.Select(new { x=1, y=2}.ToExpando());
Tất nhiên theo quan điểm của bạn:
@foreach (var item in Model) {
<div>x = @item.x, y = @item.y</div>
}
Tôi tìm thấy câu trả lời trong một câu hỏi liên quan . Câu trả lời được chỉ định trên bài đăng trên blog của David Ebbo Chuyển các đối tượng ẩn danh sang chế độ xem MVC và truy cập chúng bằng cách sử dụng động
Lý do cho điều này là loại ẩn danh được truyền trong bộ điều khiển bên trong, vì vậy nó chỉ có thể được truy cập từ bên trong tổ hợp mà nó được khai báo. Vì các khung nhìn được biên dịch riêng, nên chất kết dính động phàn nàn rằng nó không thể đi qua ranh giới lắp ráp đó.
Nhưng nếu bạn nghĩ về nó, hạn chế này từ chất kết dính động thực sự khá giả tạo, bởi vì nếu bạn sử dụng phản xạ riêng tư, không có gì ngăn bạn truy cập vào các thành viên nội bộ đó (vâng, nó thậm chí hoạt động ở mức tin cậy trung bình). Vì vậy, chất kết dính động mặc định sẽ không sử dụng các quy tắc biên dịch C # (nơi bạn không thể truy cập các thành viên nội bộ), thay vì cho phép bạn làm những gì thời gian chạy CLR cho phép.
Sử dụng phương pháp ToExpando là giải pháp tốt nhất.
Đây là phiên bản không yêu cầu lắp ráp 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;
}
Thay vì tạo một mô hình từ một loại ẩn danh và sau đó cố gắng chuyển đổi đối tượng ẩn danh thành một ExpandoObject
...
var model = new
{
Profile = profile,
Foo = foo
};
return View(model.ToExpando()); // not a framework method (see other answers)
Bạn chỉ có thể tạo ExpandoObject
trực tiếp:
dynamic model = new ExpandoObject();
model.Profile = profile;
model.Foo = foo;
return View(model);
Sau đó, trong chế độ xem của bạn, bạn đặt loại mô hình là động @model dynamic
và bạn có thể truy cập các thuộc tính trực tiếp:
@Model.Profile.Name
@Model.Foo
Tôi thường khuyên dùng các kiểu xem được gõ mạnh cho hầu hết các khung nhìn, nhưng đôi khi tính linh hoạt này rất tiện dụng.
Bạn có thể sử dụng giao diện ngẫu hứng khung để bọc một loại ẩn danh trong giao diện.
Bạn chỉ cần trả lại IEnumerable<IMadeUpInterface>
và ở cuối Linq của bạn sử dụng .AllActLike<IMadeUpInterface>();
công việc này bởi vì nó gọi thuộc tính ẩn danh bằng DLR với ngữ cảnh của hội đồng khai báo loại ẩn danh.
Đã viết một ứng dụng bảng điều khiển và thêm Mono.Cecil làm tài liệu tham khảo (bây giờ bạn có thể thêm nó từ NuGet ), sau đó viết đoạn mã:
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
});
}
Đoạn mã trên sẽ lấy tệp lắp ráp từ các đối số đầu vào và sử dụng Mono.Cecil để thay đổi khả năng truy cập từ nội bộ sang công khai và điều đó sẽ giải quyết vấn đề.
Chúng tôi có thể chạy chương trình trong sự kiện Post Build của trang web. Tôi đã viết một bài đăng trên blog về điều này bằng tiếng Trung Quốc nhưng tôi tin rằng bạn chỉ cần đọc mã và ảnh chụp nhanh. :)
Dựa trên câu trả lời được chấp nhận, tôi đã ghi đè lên bộ điều khiển để làm cho nó hoạt động nói chung và đằng sau hậu trường.
Đây là mã:
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");
}
}
}
Bây giờ bạn có thể chỉ cần vượt qua một đối tượng ẩn danh làm mô hình và nó sẽ hoạt động như mong đợi.
Tôi sẽ ăn cắp một chút từ https://stackoverflow.com/a/7478600/37055
Nếu bạn cài đặt gói động lực, bạn có thể làm điều này:
return View(Build<ExpandoObject>.NewObject(RatingName: name, Comment: comment));
Và nông dân vui mừng.
Lý do của RuntimeBinderException được kích hoạt, tôi nghĩ rằng có câu trả lời tốt trong các bài viết khác. Tôi chỉ tập trung để giải thích làm thế nào tôi thực sự làm cho nó hoạt động.
Bằng cách tham khảo để trả lời các chế độ xem @DotNetWise và Binding với bộ sưu tập kiểu ẩn danh trong ASP.NET MVC ,
Thứ nhất, Tạo một lớp tĩnh để mở rộng
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;
}
}
Trong bộ điều khiển
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);
}
Trong View, @model IEnumerable (động, không phải lớp mô hình), điều này rất quan trọng vì chúng ta sẽ liên kết đối tượng loại ẩn danh.
@model IEnumerable<dynamic>
@*@foreach (dynamic item in Model)*@
@foreach (var item in Model)
{
<div>x=@item.nric, y=@item.count</div>
}
Kiểu trong foreach, tôi không có lỗi khi sử dụng var hoặc động .
Nhân tiện, tạo một ViewModel mới phù hợp với các trường mới cũng có thể là cách để chuyển kết quả cho chế độ xem.
Bây giờ trong hương vị đệ quy
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;
}
Sử dụng tiện ích mở rộng ExpandoObject hoạt động nhưng bị hỏng khi sử dụng các đối tượng ẩn danh lồng nhau.
Nhu la
var projectInfo = new {
Id = proj.Id,
UserName = user.Name
};
var workitem = WorkBL.Get(id);
return View(new
{
Project = projectInfo,
WorkItem = workitem
}.ToExpando());
Để thực hiện điều này tôi sử dụng nó.
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 };
}
}
Cách sử dụng trong bộ điều khiển là như nhau ngoại trừ bạn sử dụng ToRazorDocate () thay vì ToExpando ().
Theo quan điểm của bạn để có được toàn bộ đối tượng ẩn danh, bạn chỉ cần thêm ".AnonValue" vào cuối.
var project = @(Html.Raw(JsonConvert.SerializeObject(Model.Project.AnonValue)));
var projectName = @Model.Project.Name;
Tôi đã thử ExpandoObject nhưng nó không hoạt động với loại phức tạp ẩn danh lồng nhau như thế này:
var model = new { value = 1, child = new { value = 2 } };
Vì vậy, giải pháp của tôi là trả về mô hình JObject cho View:
return View(JObject.FromObject(model));
và chuyển đổi thành động trong .cshtml:
@using Newtonsoft.Json.Linq;
@model JObject
@{
dynamic model = (dynamic)Model;
}
<span>Value of child is: @model.child.value</span>