Odpowiedzi:
Moim zdaniem anonimowe typy posiadające właściwości wewnętrzne to kiepska decyzja dotycząca projektowania frameworka .NET.
Oto szybkie i przyjemne rozszerzenie, które rozwiązuje ten problem, np. 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;
}
Jest bardzo łatwy 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>
}
Znalazłem odpowiedź w pokrewnym pytaniu . Odpowiedź znajduje się w poście na blogu Davida Ebbo. Przekazywanie anonimowych obiektów do widoków MVC i uzyskiwanie do nich dostępu przy użyciu dynamiki
Przyczyną tego jest to, że typ anonimowy jest przekazywany w kontrolerze wewnętrznie, więc można uzyskać do niego dostęp tylko z poziomu zestawu, w którym jest zadeklarowany. Ponieważ widoki są kompilowane osobno, spinacz dynamiczny skarży się, że nie może przekroczyć tej granicy montażu.
Ale jeśli się nad tym zastanowić, to ograniczenie ze strony spoiwa dynamicznego jest w rzeczywistości dość sztuczne, ponieważ jeśli używasz refleksji prywatnej, nic nie stoi na przeszkodzie, aby uzyskać dostęp do tych wewnętrznych członków (tak, działa nawet w średnim zaufaniu). Tak więc domyślny spinacz dynamiczny robi wszystko, co w jego mocy, aby wymusić reguły kompilacji C # (gdzie nie można uzyskać dostępu do wewnętrznych elementów członkowskich), zamiast pozwalać na robienie tego, na co pozwala środowisko uruchomieniowe CLR.
Korzystanie ToExpando metoda jest najlepszym rozwiązaniem.
Oto wersja, która nie wymaga montażu 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;
}
Zamiast tworzyć model z anonimowego typu, a następnie próbować przekonwertować anonimowy obiekt na ExpandoObject
podobny ...
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 swoim widoku ustawiasz typ modelu jako dynamiczny @model dynamic
i masz bezpośredni dostęp do właściwości:
@Model.Profile.Name
@Model.Foo
Zwykle zalecam silnie wpisane modele widoków dla większości widoków, ale czasami ta elastyczność jest przydatna.
Możesz użyć impromptu frameworka do zawijania anonimowego typu w interfejsie.
Po prostu zwróciłbyś IEnumerable<IMadeUpInterface>
i na końcu swojego Linq użyj .AllActLike<IMadeUpInterface>();
tego, co działa, ponieważ wywołuje anonimową właściwość przy użyciu DLR z kontekstem zestawu, który zadeklarował typ anonimowy.
Napisałem aplikację konsolową i dodaj Mono.Cecil jako odniesienie (możesz teraz dodać go z NuGet ), a następnie napisz 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 zestawu z argumentów wejściowych i użyje Mono.Cecil do zmiany dostępności z wewnętrznej na publiczną, a to rozwiązałoby problem.
Program możemy uruchomić w ramach wydarzenia Post Build witryny. Napisałem o tym post na blogu po chińsku, ale wydaje mi się, że możesz po prostu przeczytać kod i migawki. :)
W oparciu o zaakceptowaną odpowiedź zastąpił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.
Zamierzam trochę ukraść z https://stackoverflow.com/a/7478600/37055
Jeśli zainstalujesz pakiet dynamitey, możesz to zrobić:
return View(Build<ExpandoObject>.NewObject(RatingName: name, Comment: comment));
A chłopi się cieszą.
Powód uruchomienia RuntimeBinderException, myślę, że w innych postach jest dobra odpowiedź. Skupiam się tylko na wyjaśnieniu, jak to właściwie działa.
Odwołaj się do odpowiedzi @DotNetWise i widoków Binding z kolekcją typu Anonymous w ASP.NET MVC ,
Po pierwsze, utwórz klasę statyczną do 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;
}
}
W kontrolerze
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, a nie klasa modelu) jest to bardzo ważne, ponieważ zamierzamy powiązać obiekt typu anonimowego.
@model IEnumerable<dynamic>
@*@foreach (dynamic item in Model)*@
@foreach (var item in Model)
{
<div>x=@item.nric, y=@item.count</div>
}
Typ w foreach, nie mam błędu ani przy użyciu var ani dynamic .
Nawiasem mówiąc, utwórz nowy ViewModel, który jest zgodny z nowymi polami, może być również sposobem przekazania wyniku do widoku.
Teraz w stylu rekurencyjnym
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;
}
Korzystanie z rozszerzenia ExpandoObject działa, ale nie działa, gdy używane są zagnieżdżone obiekty anonimowe.
Jak na przykład
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 użycia ToRazorDynamic () zamiast ToExpando ().
Aby uzyskać cały anonimowy obiekt, po prostu dodaj na końcu „.AnonValue”.
var project = @(Html.Raw(JsonConvert.SerializeObject(Model.Project.AnonValue)));
var projectName = @Model.Project.Name;
Wypróbowałem ExpandoObject, ale nie działało to z zagnieżdżonym anonimowym typem złożonym, takim jak ten:
var model = new { value = 1, child = new { value = 2 } };
Więc moim rozwiązaniem było zwrócenie modelu JObject do View:
return View(JObject.FromObject(model));
i przekonwertuj na dynamiczny w .cshtml:
@using Newtonsoft.Json.Linq;
@model JObject
@{
dynamic model = (dynamic)Model;
}
<span>Value of child is: @model.child.value</span>