Respostas:
Tipos anônimos com propriedades internas é uma péssima decisão de design de estrutura .NET, na minha opinião.
Aqui está uma extensão rápida e agradável para corrigir esse problema, ou seja, convertendo o objeto anônimo em um ExpandoObject imediatamente.
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;
}
É muito fácil de usar:
return View("ViewName", someLinq.Select(new { x=1, y=2}.ToExpando());
Obviamente, na sua opinião:
@foreach (var item in Model) {
<div>x = @item.x, y = @item.y</div>
}
Encontrei a resposta em uma pergunta relacionada . A resposta está especificada na postagem do blog de David Ebbo Passando objetos anônimos para visualizações do MVC e acessando-os usando dinâmica
A razão para isso é que o tipo anônimo está sendo passado no controlador em interno, portanto, ele só pode ser acessado de dentro do assembly em que foi declarado. Como as visualizações são compiladas separadamente, o fichário dinâmico reclama que não pode ultrapassar esse limite de montagem.
Mas se você pensar bem, essa restrição do fichário dinâmico é realmente bastante artificial, porque se você usar a reflexão privada, nada o impedirá de acessar esses membros internos (sim, até funciona com confiança Média). Portanto, o fichário dinâmico padrão está se esforçando para impor regras de compilação C # (onde você não pode acessar membros internos), em vez de permitir que você faça o que o tempo de execução do CLR permite.
Usar o método ToExpando é a melhor solução.
Aqui está a versão que não requer a montagem 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;
}
Em vez de criar um modelo a partir de um tipo anônimo e tentar converter o objeto anônimo para um ExpandoObject
como este ...
var model = new
{
Profile = profile,
Foo = foo
};
return View(model.ToExpando()); // not a framework method (see other answers)
Você pode apenas criar o ExpandoObject
diretamente:
dynamic model = new ExpandoObject();
model.Profile = profile;
model.Foo = foo;
return View(model);
Em sua opinião, você define o tipo de modelo como dinâmico @model dynamic
e pode acessar as propriedades diretamente:
@Model.Profile.Name
@Model.Foo
Normalmente, eu recomendaria modelos de visualização com tipos fortes para a maioria das visualizações, mas às vezes essa flexibilidade é útil.
Você pode usar a interface de improviso da estrutura para agrupar um tipo anônimo em uma interface.
Você retornaria um IEnumerable<IMadeUpInterface>
e, no final do Linq, usará .AllActLike<IMadeUpInterface>();
isso porque chama a propriedade anônima usando o DLR com um contexto do assembly que declarou o tipo anônimo.
Escreva um aplicativo de console e adicione Mono.Cecil como referência (agora você pode adicioná-lo no NuGet ) e depois escreva o trecho de código:
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 código acima obteria o arquivo de montagem das entradas args e usaria o Mono.Cecil para alterar a acessibilidade de interna para pública, e isso resolveria o problema.
Podemos executar o programa no evento Post Build do site. Eu escrevi um post sobre isso em chinês, mas acredito que você pode apenas ler o código e os instantâneos. :)
Com base na resposta aceita, substituí o controlador para fazê-lo funcionar em geral e nos bastidores.
Aqui está o código:
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");
}
}
}
Agora você pode simplesmente passar um objeto anônimo como modelo, e ele funcionará conforme o esperado.
Vou roubar um pouco de https://stackoverflow.com/a/7478600/37055
Se você instalar dinamicamente o pacote, poderá fazer o seguinte:
return View(Build<ExpandoObject>.NewObject(RatingName: name, Comment: comment));
E os camponeses se alegram.
O motivo de RuntimeBinderException acionado, acho que tem uma boa resposta em outras postagens. Eu apenas me concentro em explicar como eu realmente faço isso funcionar.
Consulte as respostas às visualizações @DotNetWise e Binding com coleção de tipos anônimos no ASP.NET MVC ,
Em primeiro lugar, crie uma classe estática para extensão
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;
}
}
No controlador
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);
}
Em View, @model IEnumerable (dinâmico, não uma classe de modelo), isso é muito importante, pois vamos vincular o objeto de tipo anônimo.
@model IEnumerable<dynamic>
@*@foreach (dynamic item in Model)*@
@foreach (var item in Model)
{
<div>x=@item.nric, y=@item.count</div>
}
O tipo no foreach, não tenho erro usando var ou dinâmico .
A propósito, criar um novo ViewModel que corresponda aos novos campos também pode ser o caminho para passar o resultado para a visualização.
Agora com sabor recursivo
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;
}
O uso da extensão ExpandoObject funciona, mas é interrompido ao usar objetos anônimos aninhados.
Tal como
var projectInfo = new {
Id = proj.Id,
UserName = user.Name
};
var workitem = WorkBL.Get(id);
return View(new
{
Project = projectInfo,
WorkItem = workitem
}.ToExpando());
Para conseguir isso, eu uso isso.
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 };
}
}
O uso no controlador é o mesmo, exceto que você usa ToRazorDynamic () em vez de ToExpando ().
Na sua opinião, para obter todo o objeto anônimo, basta adicionar ".AnonValue" ao final.
var project = @(Html.Raw(JsonConvert.SerializeObject(Model.Project.AnonValue)));
var projectName = @Model.Project.Name;
Eu tentei o ExpandoObject, mas não funcionou com um tipo complexo anônimo aninhado como este:
var model = new { value = 1, child = new { value = 2 } };
Portanto, minha solução foi retornar um modelo JObject para View:
return View(JObject.FromObject(model));
e converta para dinâmico em .cshtml:
@using Newtonsoft.Json.Linq;
@model JObject
@{
dynamic model = (dynamic)Model;
}
<span>Value of child is: @model.child.value</span>