Imagine um cenário comum, esta é uma versão mais simples do que estou encontrando. Na verdade, tenho algumas camadas de aninhamento adicional na minha ...
Mas este é o cenário
O tema contém a lista A categoria contém a lista O produto contém a lista
Meu controlador fornece um tema totalmente preenchido, com todas as categorias desse tema, os produtos dentro dessas categorias e seus pedidos.
A coleção de pedidos possui uma propriedade chamada Quantidade (entre muitas outras) que precisa ser editável.
@model ViewModels.MyViewModels.Theme
@Html.LabelFor(Model.Theme.name)
@foreach (var category in Model.Theme)
{
@Html.LabelFor(category.name)
@foreach(var product in theme.Products)
{
@Html.LabelFor(product.name)
@foreach(var order in product.Orders)
{
@Html.TextBoxFor(order.Quantity)
@Html.TextAreaFor(order.Note)
@Html.EditorFor(order.DateRequestedDeliveryFor)
}
}
}
Se eu usar lambda em vez disso, então pareço obter apenas uma referência ao objeto Model superior, "Theme", não aqueles dentro do loop foreach.
O que estou tentando fazer lá é mesmo possível ou superestimei ou não entendi o que é possível?
Com o acima, recebo um erro no TextboxFor, EditorFor, etc
CS0411: Os argumentos de tipo para o método 'System.Web.Mvc.Html.InputExtensions.TextBoxFor (System.Web.Mvc.HtmlHelper, System.Linq.Expressions.Expression>)' não podem ser inferidos do uso. Tente especificar os argumentos de tipo explicitamente.
Obrigado.
@
antes de tudoforeach
? Você também não deveria ter lambdas emHtml.EditorFor
(Html.EditorFor(m => m.Note)
, por exemplo) e no resto dos métodos? Posso estar enganado, mas você pode colar seu código real? Eu sou muito novo no MVC, mas você pode resolver isso facilmente com visualizações parciais ou editores (se esse é o nome?).category.name
Tenho certeza que é umstring
e...For
não suporta uma string como o primeiro parâmetro:)
.for()
vez de umforeach
. Vou explicar o porquê, porque isso me confundiu por muito tempo também.Respostas:
A resposta rápida é usar um
for()
loop no lugar de seusforeach()
loops. Algo como:Mas isso ignora por que isso corrige o problema.
Há três coisas que você tem pelo menos um entendimento superficial antes de resolver esse problema. Tenho que admitir que cultivei isso por muito tempo quando comecei a trabalhar com a estrutura. E demorei um pouco para entender realmente o que estava acontecendo.
Essas três coisas são:
LabelFor
e outros...For
ajudantes funcionam no MVC?Todos os três conceitos se conectam para obter uma resposta.
Como o
LabelFor
e outros...For
ajudantes funcionam no MVC?Então, você já usou as
HtmlHelper<T>
extensões paraLabelFor
eTextBoxFor
entre outros, e você deve ter notado que quando você invocá-los, você passá-los um lambda e magicamente gera algum html. Mas como?Portanto, a primeira coisa a notar é a assinatura desses ajudantes. Vejamos a sobrecarga mais simples para
TextBoxFor
Em primeiro lugar, este é um método de extensão para um tipo forte
HtmlHelper
, do tipo<TModel>
. Portanto, para simplesmente declarar o que acontece nos bastidores, quando o razor renderiza essa visualização, ele gera uma classe. Dentro dessa classe está uma instância deHtmlHelper<TModel>
(como a propriedadeHtml
, que é o motivo pelo qual você pode usar@Html...
), ondeTModel
é o tipo definido em sua@model
instrução. Portanto, no seu caso, quando você estiver olhando para essa vista,TModel
será sempre do tipoViewModels.MyViewModels.Theme
.Agora, o próximo argumento é um pouco complicado. Então, vamos dar uma olhada em uma invocação
Parece que temos um pequeno lambda, e se alguém fosse adivinhar a assinatura, poderia pensar que o tipo para este argumento seria simplesmente um
Func<TModel, TProperty>
, ondeTModel
é o tipo do modelo de visualização eTProperty
é inferido como o tipo da propriedade.Mas isso não está certo, se você olhar para o tipo real do argumento, é
Expression<Func<TModel, TProperty>>
.Então, quando você normalmente gera um lambda, o compilador pega o lambda e o compila no MSIL, assim como qualquer outra função (é por isso que você pode usar delegados, grupos de métodos e lambdas mais ou menos indistintamente, porque são apenas referências de código .)
No entanto, quando o compilador vê que o tipo é um
Expression<>
, ele não compila imediatamente o lambda para MSIL, em vez disso, gera uma árvore de expressão!O que é uma árvore de expressão ?
Então, o que diabos é uma árvore de expressão. Bem, não é complicado, mas também não é um passeio no parque. Para citar ms:
| Árvores de expressão representam o código em uma estrutura de dados semelhante a uma árvore, em que cada nó é uma expressão, por exemplo, uma chamada de método ou uma operação binária, como x <y.
Simplificando, uma árvore de expressão é uma representação de uma função como uma coleção de "ações".
No caso de
model=>model.SomeProperty
, a árvore de expressão teria um nó que diz: "Obtenha 'Alguma Propriedade' de um 'modelo'"Esta árvore de expressão pode ser compilada em uma função que pode ser invocada, mas contanto que seja uma árvore de expressão, é apenas uma coleção de nós.
Então, para que isso é bom?
Então ,
Func<>
ouAction<>
, uma vez que você os tenha, eles são praticamente atômicos. Tudo o que você pode realmente fazer éInvoke()
eles, ou , dizer-lhes para fazer o trabalho que devem fazer.Expression<Func<>>
por outro lado, representa uma coleção de ações, que podem ser anexadas, manipuladas, visitadas ou compiladas e invocadas.Então por que você está me contando tudo isso?
Assim, com esse entendimento do que
Expression<>
é, podemos voltarHtml.TextBoxFor
. Quando ele renderiza uma caixa de texto, ele precisa gerar algumas coisas sobre a propriedade que você está atribuindo a ele. Coisas comoattributes
a propriedade para validação e, especificamente, neste caso, ele precisa descobrir como nomear a<input>
tag.Ele faz isso "percorrendo" a árvore de expressão e construindo um nome. Portanto, para uma expressão como
model=>model.SomeProperty
, ele percorre a expressão reunindo as propriedades que você está solicitando e constrói<input name='SomeProperty'>
.Para um exemplo mais complicado, como
model=>model.Foo.Bar.Baz.FooBar
, pode gerar<input name="Foo.Bar.Baz.FooBar" value="[whatever FooBar is]" />
Faz sentido? Não é apenas o trabalho que
Func<>
ele faz, mas como ele faz seu trabalho é importante aqui.(Observe que outras estruturas, como LINQ to SQL, fazem coisas semelhantes percorrendo uma árvore de expressão e construindo uma gramática diferente, que neste caso é uma consulta SQL)
Como funciona o Model Binder?
Assim que você conseguir isso, teremos que falar brevemente sobre o fichário de modelos. Quando o formulário é postado, é simplesmente como um plano
Dictionary<string, string>
, perdemos a estrutura hierárquica que nosso modelo de visualização aninhado pode ter tido. É função do fichário do modelo pegar esse combo de par de valores-chave e tentar reidratar um objeto com algumas propriedades. Como isso faz? Você adivinhou, usando a "chave" ou nome da entrada que foi postada.Então, se a postagem do formulário parecer
E você está postando em um modelo chamado
SomeViewModel
, então ele faz o inverso do que o ajudante fez inicialmente. Procura uma propriedade chamada "Foo". Em seguida, procura uma propriedade chamada "Bar" fora de "Foo", em seguida, procura "Baz" ... e assim por diante ...Finalmente, ele tenta analisar o valor no tipo de "FooBar" e atribuí-lo a "FooBar".
PHEW !!!
E voila, você tem seu modelo. A instância que o Model Binder acabou de construir é entregue na ação solicitada.
Portanto, sua solução não funciona porque os
Html.[Type]For()
ajudantes precisam de uma expressão. E você está apenas dando a eles um valor. Ele não tem ideia de qual é o contexto para esse valor e não sabe o que fazer com ele.Agora, algumas pessoas sugeriram o uso de parciais para renderizar. Em teoria, isso funcionará, mas provavelmente não da maneira que você espera. Ao renderizar um parcial, você está alterando o tipo de
TModel
, porque está em um contexto de visualização diferente. Isso significa que você pode descrever sua propriedade com uma expressão mais curta. Também significa que quando o auxiliar gerar o nome para sua expressão, ele será superficial. Ele só será gerado com base na expressão fornecida (não em todo o contexto).Digamos que você tenha um parcial que acabou de renderizar "Baz" (do nosso exemplo anterior). Dentro dessa parcial, você poderia apenas dizer:
Ao invés de
Isso significa que ele irá gerar uma tag de entrada como esta:
Que, se você estiver postando este formulário para uma ação que espera um ViewModel grande e profundamente aninhado, ele tentará hidratar uma propriedade chamada
FooBar
deTModel
. Que, na melhor das hipóteses, não está lá e, na pior, é algo totalmente diferente. Se você estivesse postando para uma ação específica que estava aceitando umBaz
, em vez do modelo raiz, isso funcionaria muito bem! Na verdade, os parciais são uma boa maneira de alterar o contexto de visualização, por exemplo, se você tiver uma página com vários formulários que postam em ações diferentes, renderizar um parcial para cada um seria uma ótima ideia.Agora, depois de obter tudo isso, você pode começar a fazer coisas realmente interessantes com o
Expression<>
, estendendo-os programaticamente e fazendo outras coisas legais com eles. Não vou entrar em nada disso. Mas, com sorte, isso lhe dará uma melhor compreensão do que está acontecendo nos bastidores e por que as coisas estão agindo da maneira que estão.fonte
Você pode simplesmente usar EditorTemplates para fazer isso, você precisa criar um diretório chamado "EditorTemplates" na pasta de visualização de seu controlador e colocar uma visualização separada para cada uma de suas entidades aninhadas (nomeado como nome de classe de entidade)
Vista principal :
Visualização de categoria (/MyController/EditorTemplates/Category.cshtml):
Visualização do produto (/MyController/EditorTemplates/Product.cshtml):
e assim por diante
desta forma, o helper Html.EditorFor irá gerar os nomes dos elementos de uma maneira ordenada e, portanto, você não terá mais nenhum problema para recuperar a entidade Theme postada como um todo
fonte
Você poderia adicionar um parcial de Categoria e um parcial de Produto, cada um pegaria uma parte menor do modelo principal como seu próprio modelo, ou seja, o tipo de modelo de Categoria pode ser um IEnumerable, você passaria Model.Theme para ele. O parcial do produto pode ser um IEnumerable para o qual você passa Model.Products (de dentro do parcial da categoria).
Não tenho certeza se esse seria o caminho certo a seguir, mas estou interessado em saber.
EDITAR
Desde a publicação desta resposta, usei EditorTemplates e considero a maneira mais fácil de lidar com grupos ou itens de entrada repetidos. Ele lida com todos os problemas de mensagem de validação e problemas de envio de formulário / vinculação de modelo automaticamente.
fonte
Theme
modelo não seria hidratada corretamente.Quando você está usando o loop foreach dentro da visualização para o modelo vinculado ... Seu modelo deve estar no formato listado.
ie
fonte
O erro está claro.
O HtmlHelpers com "For" espera a expressão lambda como um parâmetro.
Se você estiver passando o valor diretamente, é melhor usar um Normal.
por exemplo
Em vez de TextboxFor (....), use Textbox ()
sintaxe para TextboxFor será como Html.TextBoxFor (m => m.Property)
Em seu cenário, você pode usar o loop for básico, pois ele fornecerá um índice para usar.
fonte
Outra possibilidade muito mais simples é que um dos nomes de sua propriedade esteja errado (provavelmente um que você acabou de alterar na classe). Isso é o que era para mim no RazorPages .NET Core 3.
fonte