Tipo anônimo dynamic no Razor causa RuntimeBinderException

Estou recebendo o seguinte erro:

‘object’ não contém uma definição para ‘RatingName’

Quando você olha para o tipo dynamic anônimo, claramente tem RatingName.

Captura de tela do erro

Eu percebo que posso fazer isso com um Tuple, mas gostaria de entender por que a mensagem de erro ocorre.

   

Tipos anônimos com propriedades internas é uma decisão de design de estrutura .NET pobre, na minha opinião.

Aqui está uma extensão rápida e agradável para corrigir este problema, ou seja, convertendo o object anônimo em um ExpandoObject imediatamente.

public static ExpandoObject ToExpando(this object anonymousObject) { IDictionary anonymousDictionary = new RouteValueDictionary(anonymousObject); IDictionary 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()); 

Claro que na sua opinião:

 @foreach (var item in Model) { 
x = @item.x, y = @item.y
}

Eu encontrei a resposta em uma questão relacionada . A resposta está especificada na postagem do blog de David Ebbo Passando objects anônimos para visualizações MVC e acessando-os usando dinâmica

A razão para isso é que o tipo anônimo está sendo passado no controlador internamente, portanto, ele só pode ser acessado de dentro do assembly no qual ele está declarado. Como os modos de exibição são compilados separadamente, o fichário dynamic reclama que não pode ultrapassar esse limite de assembly.

Mas se você pensar sobre isso, essa restrição do fichário dynamic é realmente bastante artificial, porque se você usar a reflection privada, nada o impedirá de acessar esses membros internos (sim, ele até funciona na confiança Média). Portanto, o fichário dynamic padrão está saindo de seu caminho para impor regras de compilation C # (onde você não pode acessar membros internos), em vez de permitir que você faça o que o tempo de execução CLR permite.

Usar o método ToExpando é a melhor solução.

Aqui está a versão que não requer a assembly System.Web :

 public static ExpandoObject ToExpando(this object anonymousObject) { IDictionary 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 de um tipo anônimo e, em seguida, tentar converter o object anônimo em um ExpandoObject assim …

 var model = new { Profile = profile, Foo = foo }; return View(model.ToExpando()); // not a framework method (see other answers) 

Você pode simplesmente criar o ExpandoObject diretamente:

 dynamic model = new ExpandoObject(); model.Profile = profile; model.Foo = foo; return View(model); 

Em seguida, na sua visualização, você configura o tipo de modelo como dynamic @model dynamic e é possível acessar as propriedades diretamente:

 @Model.Profile.Name @Model.Foo 

Eu normalmente recomendo modelos de visualização fortemente tipados para a maioria das visualizações, mas às vezes essa flexibilidade é útil.

Você pode usar a interface improvisada de estrutura para agrupar um tipo anônimo em uma interface.

Você acabou de retornar um IEnumerable e no final do seu uso do Linq .AllActLike(); isso funciona porque chama a propriedade anônima usando o DLR com um contexto do assembly que declarou o tipo anônimo.

Escreveu um aplicativo de console e adicionou Mono.Cecil como referência (agora você pode adicioná-lo do NuGet ) e, em seguida, escreve 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 assembly dos argumentos de input e usaria o Mono.Cecil para alterar a acessibilidade de interno para público, 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ê possa ler o código e os instantâneos. 🙂

Com base na resposta aceita, eu anulei no 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 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 object anônimo como modelo e ele funcionará conforme o esperado.

Eu vou fazer um pouco de roubo de https://stackoverflow.com/a/7478600/37055

Se você instalar a dinamite do pacote, poderá fazer isso:

 return View(Build.NewObject(RatingName: name, Comment: comment)); 

E os camponeses se alegram.

O motivo do RuntimeBinderException disparou, acho que tem uma boa resposta em outros posts. Eu me concentro apenas para explicar como eu realmente faço funcionar.

Por referir-se a responder às exibições @DotNetWise e Binding com a coleção de tipos anônimos no ASP.NET MVC ,

Em primeiro lugar, crie uma class estática para extensão

 public static class impFunctions { //converting the anonymous object into an ExpandoObject public static ExpandoObject ToExpando(this object anonymousObject) { //IDictionary anonymousDictionary = new RouteValueDictionary(anonymousObject); IDictionary anonymousDictionary = HtmlHelper.AnonymousObjectToHtmlAttributes(anonymousObject); IDictionary 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 (dynamic, não uma class de modelo), isso é muito importante, pois vamos vincular o object de tipo anônimo.

 @model IEnumerable @*@foreach (dynamic item in Model)*@ @foreach (var item in Model) { 
x=@item.nric, y=@item.count
}

O tipo em foreach, eu não tenho nenhum erro usando var ou dynamic .

By the way, criar um novo ViewModel que está combinando os novos campos também pode ser o caminho para passar o resultado para a visão.

Agora em sabor recursivo

 public static ExpandoObject ToExpando(this object obj) { IDictionary 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; }