Відповіді:
Анонімні типи, що мають внутрішні властивості, є поганим рішенням дизайну .NET Framework, на мою думку.
Ось швидке і приємне розширення для вирішення цієї проблеми, тобто шляхом перетворення анонімного об'єкта в 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;
}
Це дуже просто у використанні:
return View("ViewName", someLinq.Select(new { x=1, y=2}.ToExpando());
Звичайно, на ваш погляд:
@foreach (var item in Model) {
<div>x = @item.x, y = @item.y</div>
}
Я знайшов відповідь у спорідненому питанні . Відповідь вказана в публікації блогу Девіда Еббо Передача анонімних об’єктів до MVC-переглядів та доступ до них за допомогою динаміки
Причиною цього є те, що анонімний тип передається у контролер у внутрішній частині, тому доступ до нього можна отримати лише в межах збірки, в якій він оголошений. Оскільки перегляди складаються окремо, динамічне в'яжуче скаржиться, що воно не може перейти цю межу складання.
Але якщо ви задумаєтесь, це обмеження від динамічного в'яжучого насправді є досить штучним, тому що якщо ви використовуєте приватне відображення, ніщо не заважає вам отримати доступ до цих внутрішніх членів (так, це працює навіть у середовищі довіри середнього рівня). Таким чином, динамічне в'яжуче за замовчуванням виходить із шляху, щоб застосувати правила компіляції C # (де ви не можете отримати доступ до внутрішніх членів), а не дозволяти виконувати те, що дозволяє час виконання CLR.
Використання методу ToExpando - найкраще рішення.
Ось версія, яка не потребує збирання 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;
}
Замість того, щоб створити модель з анонімного типу, а потім спробувати перетворити анонімний об’єкт у ExpandoObject
подібний ...
var model = new
{
Profile = profile,
Foo = foo
};
return View(model.ToExpando()); // not a framework method (see other answers)
Ви можете просто створити ExpandoObject
безпосередньо:
dynamic model = new ExpandoObject();
model.Profile = profile;
model.Foo = foo;
return View(model);
Потім у вашому представленні ви встановлюєте тип моделі як динамічний, @model dynamic
і ви можете отримати доступ до властивостей безпосередньо:
@Model.Profile.Name
@Model.Foo
Зазвичай я рекомендую набрати сильно набрані моделі перегляду для більшості представлень, але іноді ця гнучкість зручна.
Ви можете використовувати імпровізований інтерфейс рамки, щоб обернути анонімний тип в інтерфейсі.
Ви просто повернете IEnumerable<IMadeUpInterface>
і в кінці свого Linq використовувати .AllActLike<IMadeUpInterface>();
це працює, оскільки він викликає анонімний ресурс за допомогою DLR з контекстом збірки, яка оголосила анонімний тип.
Написав консольну програму та додай Mono.Cecil в якості посилання (тепер ти можеш додати її з NuGet ), а потім напиши фрагмент коду:
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
});
}
Код, наведений вище, отримає файл складання з вхідних аргументів і використовує Mono.Cecil для зміни доступності від внутрішнього до загальнодоступного, і це вирішить проблему.
Ми можемо запустити програму у події Post Build на веб-сайті. Я написав про це в блозі китайською мовою, але я вважаю, що ви можете просто прочитати код і знімки. :)
Виходячи з прийнятої відповіді, я змінив функцію контролера, щоб він працював загалом і поза кадром.
Ось код:
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");
}
}
}
Тепер ви можете просто передати анонімний об’єкт як модель, і він буде працювати, як очікувалося.
Я зроблю трохи крадіжки з https://stackoverflow.com/a/7478600/37055
Якщо ви встановите динаміт -пакет, ви можете зробити це:
return View(Build<ExpandoObject>.NewObject(RatingName: name, Comment: comment));
І селяни радіють.
Причина RuntimeBinderException викликана, я думаю, що в інших публікаціях є хороша відповідь. Я просто зосереджуюсь, щоб пояснити, як я насправді змушую це працювати.
Звернівшись до відповідей @DotNetWise та прив'язування поглядів із колекції типу Anonymous в ASP.NET MVC ,
По-перше, Створіть статичний клас для розширення
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;
}
}
У контролері
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);
}
У View, @model IEnumerable (динамічний, не модельний клас), це дуже важливо, оскільки ми збираємось зв’язати об'єкт анонімного типу.
@model IEnumerable<dynamic>
@*@foreach (dynamic item in Model)*@
@foreach (var item in Model)
{
<div>x=@item.nric, y=@item.count</div>
}
Тип в foreach, я не маю помилок ні при використанні var, ні в динаміці .
До речі, створити новий ViewModel, який відповідає новим полям, також може стати способом передачі результату перегляду.
Зараз у рекурсивному смаку
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;
}
Використання розширення ExpandoObject працює, але розривається при використанні вкладених анонімних об'єктів.
Як от
var projectInfo = new {
Id = proj.Id,
UserName = user.Name
};
var workitem = WorkBL.Get(id);
return View(new
{
Project = projectInfo,
WorkItem = workitem
}.ToExpando());
Для цього я використовую це.
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 };
}
}
Використання в контролері однакове, за винятком того, що ви використовуєте ToRazorDynamic () замість ToExpando ().
На ваш погляд, щоб отримати весь анонімний об’єкт, ви просто додасте ".AnonValue" до кінця.
var project = @(Html.Raw(JsonConvert.SerializeObject(Model.Project.AnonValue)));
var projectName = @Model.Project.Name;
Я спробував ExpandoObject, але він не працював із вкладеним анонімним складним типом, як це:
var model = new { value = 1, child = new { value = 2 } };
Тож моїм рішенням було повернути JObject до View моделі:
return View(JObject.FromObject(model));
і перетворити в динамічний .cshtml:
@using Newtonsoft.Json.Linq;
@model JObject
@{
dynamic model = (dynamic)Model;
}
<span>Value of child is: @model.child.value</span>