빠른 대답은 for()
루프 대신 루프 를 사용하는 것 foreach()
입니다. 다음과 같은 것 :
@for(var themeIndex = 0; themeIndex < Model.Theme.Count(); themeIndex++)
{
@Html.LabelFor(model => model.Theme[themeIndex])
@for(var productIndex=0; productIndex < Model.Theme[themeIndex].Products.Count(); productIndex++)
{
@Html.LabelFor(model=>model.Theme[themeIndex].Products[productIndex].name)
@for(var orderIndex=0; orderIndex < Model.Theme[themeIndex].Products[productIndex].Orders; orderIndex++)
{
@Html.TextBoxFor(model => model.Theme[themeIndex].Products[productIndex].Orders[orderIndex].Quantity)
@Html.TextAreaFor(model => model.Theme[themeIndex].Products[productIndex].Orders[orderIndex].Note)
@Html.EditorFor(model => model.Theme[themeIndex].Products[productIndex].Orders[orderIndex].DateRequestedDeliveryFor)
}
}
}
그러나 이것은 이것이 문제를 해결하는 이유를 설명 합니다.
이 문제를 해결하기 전에 최소한 대략적으로 이해 한 세 가지가 있습니다. 프레임 워크 작업을 시작했을 때 오랫동안 이것을 카고 컬트했음을 인정해야 합니다. 그리고 실제로 무슨 일이 일어나고 있는지 이해하는 데 꽤 오랜 시간이 걸렸습니다.
그 세 가지는 다음과 같습니다.
- : 방법을
LabelFor
다른 ...For
헬퍼는 MVC에서 작동?
- 식 트리 란 무엇입니까?
- Model Binder는 어떻게 작동합니까?
이 세 가지 개념이 모두 연결되어 답을 얻습니다.
: 방법을 LabelFor
다른 ...For
헬퍼는 MVC에서 작동?
그래서, 당신은 사용했습니다 HtmlHelper<T>
에 대한 확장 LabelFor
및 TextBoxFor
다른 사람을, 그리고 당신은 아마 당신이 그들을 호출 당신이 그들에게 람다를 통과 때 것으로 나타났습니다 마술 일부 HTML을 생성합니다. 하지만 어떻게?
따라서 가장 먼저 주목해야 할 것은 이러한 도우미의 서명입니다. 가장 간단한 과부하를 살펴 보겠습니다.
TextBoxFor
public static MvcHtmlString TextBoxFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression
)
첫째, 이것은 강력한 형식의 HtmlHelper
, 형식에 대한 확장 메서드입니다 <TModel>
. 따라서 장면 뒤에서 일어나는 일을 간단히 설명하기 위해 razor가이 뷰를 렌더링 할 때 클래스를 생성합니다. 이 클래스 내부에는의 인스턴스가 있습니다 HtmlHelper<TModel>
(property로 Html
사용할 수있는 이유입니다 @Html...
). 여기서는 문에 TModel
정의 된 유형 @model
입니다. 따라서 귀하의 경우에는이 뷰를 볼 때 TModel
항상 유형이 ViewModels.MyViewModels.Theme
됩니다.
자, 다음 논쟁은 조금 까다 롭습니다. 그래서 호출을 살펴 보겠습니다.
@Html.TextBoxFor(model=>model.SomeProperty);
약간의 람다가있는 것 같습니다. 그리고 서명을 추측한다면이 인수의 유형은 단순히라고 생각할 수 있습니다. Func<TModel, TProperty>
여기서는 TModel
뷰 모델 TProperty
의 유형이며 속성의 유형으로 추론됩니다.
그러나 인수 의 실제 유형을 살펴보면 Expression<Func<TModel, TProperty>>
.
따라서 일반적으로 람다를 생성 할 때 컴파일러는 다른 함수와 마찬가지로 람다를 가져 와서 MSIL로 컴파일합니다 (대리자, 메서드 그룹 및 람다는 코드 참조 일 뿐이므로 대체적으로 사용할 수있는 이유입니다.) .)
그러나 컴파일러가 유형이임을 확인 Expression<>
하면 람다를 MSIL로 즉시 컴파일하지 않고 대신 식 트리를 생성합니다!
식 트리 란 무엇입니까 ?
그래서 도대체 표현 트리입니다. 글쎄, 그것은 복잡하지는 않지만 공원에서 산책하는 것도 아닙니다. ms를 인용하려면 :
| 식 트리는 트리와 유사한 데이터 구조의 코드를 나타냅니다. 여기서 각 노드는 식 (예 : 메서드 호출 또는 x <y와 같은 이진 연산)입니다.
간단히 말해서 표현식 트리는 "액션"모음으로서 함수를 표현한 것입니다.
의 경우 model=>model.SomeProperty
표현식 트리에는 " '모델'에서 '일부 속성'가져 오기"라는 노드가 포함됩니다.
이 표현식 트리는 호출 할 수있는 함수 로 컴파일 할 수 있지만 표현식 트리 인 한 노드 모음 일뿐입니다.
그래서 그게 뭔데?
그래서 Func<>
또는 Action<>
일단 당신이 그것들을 가지고 있으면, 그것들은 거의 원자 적입니다. 당신이 정말로 할 수있는 것은 Invoke()
그들에게 일명 그들이해야 할 일을하도록 지시하는 것뿐입니다.
Expression<Func<>>
반면에 추가, 조작, 방문 또는 컴파일 및 호출 할 수있는 작업 모음을 나타냅니다 .
그럼 왜 나에게이 모든 것을 말합니까?
그래서 의가 무엇인지 이해 Expression<>
하면 Html.TextBoxFor
. 텍스트 상자를 렌더링 할 때 제공하는 속성 에 대해 몇 가지 사항을 생성해야 합니다. attributes
유효성 검사를위한 속성 과 같은 것, 특히이 경우 태그 이름 을 <input>
지정할 필요가 있습니다.
표현식 트리를 "걷고"이름을 만들어이를 수행합니다. 따라서와 같은 model=>model.SomeProperty
식의 경우 요청하고 빌드하는 속성을 수집하는 식을 안내합니다 <input name='SomeProperty'>
.
와 같은 더 복잡한 예의 경우 다음을 model=>model.Foo.Bar.Baz.FooBar
생성 할 수 있습니다.<input name="Foo.Bar.Baz.FooBar" value="[whatever FooBar is]" />
말이 되나? 그것은 그 바로 작업하지 Func<>
않지만, 어떻게 그것이 수행의 작업은 여기에 중요하다.
(LINQ to SQL과 같은 다른 프레임 워크는 식 트리를 걷고 다른 문법 (이 경우 SQL 쿼리)을 작성하여 유사한 작업을 수행합니다.)
Model Binder는 어떻게 작동합니까?
그러니 일단 이해하면 모델 바인더에 대해 간략하게 이야기해야합니다. 양식이 게시되면 단순히 평면과 같으며
Dictionary<string, string>
중첩 된 뷰 모델이 가질 수있는 계층 구조를 잃어 버렸습니다. 이 키-값 쌍 콤보를 가져와 일부 속성으로 개체를 다시 수화하는 것이 모델 바인더의 역할입니다. 어떻게하나요? 게시 된 입력의 "키"또는 이름을 사용하여 추측했습니다.
따라서 양식 게시물이
Foo.Bar.Baz.FooBar = Hello
그리고라는 모델에 게시 SomeViewModel
하면 도우미가 처음에 한 작업과 반대입니다. "Foo"라는 속성을 찾습니다. 그런 다음 "Foo"에서 "Bar"라는 속성을 찾은 다음 "Baz"... 등을 찾습니다.
마지막으로 값을 "FooBar"유형으로 구문 분석하고 "FooBar"에 할당하려고합니다.
헉 !!!
그리고 짜잔, 당신은 당신의 모델을 가지고 있습니다. Model Binder가 방금 생성 한 인스턴스는 요청 된 Action에 전달됩니다.
따라서 Html.[Type]For()
도우미에게 표현식이 필요 하기 때문에 솔루션이 작동하지 않습니다 . 그리고 당신은 그들에게 가치를 부여하고 있습니다. 그 가치에 대한 컨텍스트가 무엇인지 알지 못하며 어떻게해야할지 모릅니다.
이제 일부 사람들은 렌더링에 부분을 사용하도록 제안했습니다. 이제 이론적으로는 작동하지만 예상하는 방식은 아닐 것입니다. 부분을 렌더 할 때 TModel
다른 뷰 컨텍스트에 있기 때문에 의 유형을 변경하게 됩니다. 즉, 더 짧은 표현으로 속성을 설명 할 수 있습니다. 또한 도우미가 표현식의 이름을 생성 할 때 얕다는 것을 의미합니다. 주어진 표현에 따라서 만 생성됩니다 (전체 컨텍스트가 아님).
따라서 방금 "Baz"를 렌더링 한 부분이 있다고 가정 해 보겠습니다 (이전 예제에서). 그 부분 안에 다음과 같이 말할 수 있습니다.
@Html.TextBoxFor(model=>model.FooBar)
보다는
@Html.TextBoxFor(model=>model.Foo.Bar.Baz.FooBar)
즉, 다음과 같은 입력 태그가 생성됩니다.
<input name="FooBar" />
어느, 당신은 큰 깊이 중첩 된 뷰 모델을 기대하는 행동이 양식을 게시하는 경우, 다음이라는 속성 수분 위해 노력할 것 FooBar
의 오프 TModel
. 기껏해야 거기에 있지 않고 최악의 경우 완전히 다른 것입니다. Baz
루트 모델이 아닌 을 수락하는 특정 작업에 게시하는 경우이 방법이 잘 작동합니다! 실제로 부분은보기 컨텍스트를 변경하는 좋은 방법입니다. 예를 들어, 모두 다른 작업에 게시되는 여러 양식이있는 페이지가있는 경우 각 작업에 대한 부분을 렌더링하는 것이 좋습니다.
이제이 모든 것을 얻으면 Expression<>
프로그램 적으로 확장하고 다른 깔끔한 작업을 수행 하여 를 사용하여 정말 흥미로운 작업을 시작할 수 있습니다. 나는 그것에 대해 다루지 않을 것입니다. 그러나 바라건대, 이것은 당신이이면에서 일어나는 일과 사물이 왜 그렇게 행동하는지에 대한 더 나은 이해를 제공 할 것입니다.
@
모든 전에 가져야하지foreach
않습니까?Html.EditorFor
(Html.EditorFor(m => m.Note)
예 :) 및 나머지 메서드 에도 람다가 있어야하지 않습니까? 착각 할 수 있지만 실제 코드를 붙여 넣어 주시겠습니까? 저는 MVC를 처음 접했지만 부분보기 나 편집자 (이름이 맞다면?)로 쉽게 해결할 수 있습니다.