Câu trả lời nhanh là sử dụng một for()
vòng lặp thay cho foreach()
các vòng lặp của bạn . Cái gì đó như:
@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)
}
}
}
Nhưng điều này làm sáng tỏ lý do tại sao điều này khắc phục được sự cố.
Có ba điều mà ít nhất bạn phải hiểu sơ qua trước khi có thể giải quyết vấn đề này. Tôi phải thừa nhận rằng tôi đã tin tưởng điều này trong một thời gian dài khi bắt đầu làm việc với framework. Và tôi đã mất khá nhiều thời gian để thực sự hiểu được những gì đang diễn ra.
Ba điều đó là:
- Làm thế nào để
LabelFor
các trình ...For
trợ giúp và các trình trợ giúp khác hoạt động trong MVC?
- Cây biểu thức là gì?
- Mô hình Binder hoạt động như thế nào?
Cả ba khái niệm này liên kết với nhau để có câu trả lời.
Làm thế nào để LabelFor
các trình ...For
trợ giúp và các trình trợ giúp khác hoạt động trong MVC?
Vì vậy, bạn đã sử dụng các HtmlHelper<T>
tiện ích mở rộng cho LabelFor
và và các tiện ích mở rộng TextBoxFor
khác, và bạn có thể nhận thấy rằng khi bạn gọi chúng, bạn chuyển cho chúng một lambda và nó tạo ra một số html một cách kỳ diệu . Nhưng bằng cách nào?
Vì vậy, điều đầu tiên cần chú ý là chữ ký cho những người trợ giúp này. Hãy xem quá tải đơn giản nhất cho
TextBoxFor
public static MvcHtmlString TextBoxFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression
)
Thứ nhất, đây là một phương pháp mở rộng cho một mạnh mẽ gõ HtmlHelper
, kiểu <TModel>
. Vì vậy, để đơn giản chỉ ra những gì xảy ra đằng sau hậu trường, khi dao cạo hiển thị chế độ xem này, nó tạo ra một lớp. Bên trong lớp này là một thể hiện của HtmlHelper<TModel>
(là thuộc tính Html
, đó là lý do tại sao bạn có thể sử dụng @Html...
), TModel
kiểu được định nghĩa trong @model
câu lệnh của bạn ở đâu . Vì vậy, trong trường hợp của bạn, khi bạn đang nhìn vào chế độ xem TModel
này sẽ luôn thuộc loại ViewModels.MyViewModels.Theme
.
Bây giờ, lập luận tiếp theo là một chút phức tạp. Vì vậy, chúng ta hãy xem xét một lời kêu gọi
@Html.TextBoxFor(model=>model.SomeProperty);
Có vẻ như chúng ta có một chút lambda, Và nếu người ta đoán chữ ký, người ta có thể nghĩ rằng kiểu cho đối số này sẽ đơn giản là a Func<TModel, TProperty>
, TModel
kiểu của mô hình khung nhìn ở đâu và TProperty
được suy ra là kiểu thuộc tính.
Nhưng điều đó không hoàn toàn đúng, nếu bạn nhìn vào loại đối số thực tế của nó Expression<Func<TModel, TProperty>>
.
Vì vậy, khi bạn thường tạo lambda, trình biên dịch sẽ lấy lambda và biên dịch nó thành MSIL, giống như bất kỳ hàm nào khác (đó là lý do tại sao bạn có thể sử dụng các đại biểu, nhóm phương thức và lambda ít nhiều thay thế cho nhau, vì chúng chỉ là tham chiếu mã .)
Tuy nhiên, khi trình biên dịch thấy rằng kiểu là an Expression<>
, nó sẽ không biên dịch lambda ngay lập tức thành MSIL, thay vào đó nó tạo ra một Cây biểu thức!
Vậy, cây biểu hiện là cái quái gì. Chà, nó không phức tạp nhưng cũng không phải là đi dạo trong công viên. Để trích dẫn ms:
| Cây biểu thức đại diện cho mã trong cấu trúc dữ liệu dạng cây, trong đó mỗi nút là một biểu thức, ví dụ, một lệnh gọi phương thức hoặc một phép toán nhị phân chẳng hạn như x <y.
Nói một cách đơn giản, cây biểu thức là một biểu diễn của một hàm như một tập hợp các "hành động".
Trong trường hợp của model=>model.SomeProperty
, cây biểu thức sẽ có một nút trong đó có nội dung: "Nhận 'Một số Thuộc tính' từ 'mô hình'"
Cây biểu thức này có thể được biên dịch thành một hàm có thể được gọi, nhưng miễn là nó là một cây biểu thức, nó chỉ là một tập hợp các nút.
Vậy điều đó tốt để làm gì?
Vì vậy, Func<>
hoặc Action<>
, một khi bạn có chúng, chúng có khá nhiều nguyên tử. Tất cả những gì bạn thực sự có thể làm là Invoke()
họ, hay nói họ làm công việc mà họ phải làm.
Expression<Func<>>
mặt khác, đại diện cho một tập hợp các hành động, có thể được thêm vào, thao tác, truy cập hoặc biên dịch và gọi.
Vậy tại sao bạn lại nói với tôi tất cả những điều này?
Vì vậy, với sự hiểu biết về an Expression<>
là gì , chúng ta có thể quay trở lại Html.TextBoxFor
. Khi nó hiển thị một hộp văn bản, nó cần tạo ra một vài điều về thuộc tính mà bạn đang cung cấp cho nó. Những thứ như attributes
trên thuộc tính để xác thực và cụ thể là trong trường hợp này, nó cần phải tìm ra cái gì để đặt tên cho <input>
thẻ.
Nó thực hiện điều này bằng cách "đi bộ" cây biểu thức và xây dựng tên. Vì vậy, đối với một biểu thức như model=>model.SomeProperty
, nó đi biểu thức thu thập các thuộc tính mà bạn đang yêu cầu và xây dựng <input name='SomeProperty'>
.
Đối với một ví dụ phức tạp hơn, chẳng hạn như model=>model.Foo.Bar.Baz.FooBar
, nó có thể tạo ra<input name="Foo.Bar.Baz.FooBar" value="[whatever FooBar is]" />
Có lý? Nó không chỉ là công việc mà các Func<>
có, nhưng cách nó làm công việc của mình là quan trọng ở đây.
(Lưu ý rằng các khung công tác khác như LINQ to SQL thực hiện những điều tương tự bằng cách đi bộ một cây biểu thức và xây dựng một ngữ pháp khác, trong trường hợp này là một truy vấn SQL)
Mô hình Binder hoạt động như thế nào?
Vì vậy, khi bạn hiểu được điều đó, chúng ta phải nói ngắn gọn về chất kết dính mô hình. Khi biểu mẫu được đăng, nó đơn giản giống như một căn hộ
Dictionary<string, string>
, chúng tôi đã mất cấu trúc phân cấp mà mô hình xem lồng nhau của chúng tôi có thể đã có. Công việc của chất kết dính mô hình là lấy tổ hợp cặp khóa-giá trị này và cố gắng bù nước cho một đối tượng với một số thuộc tính. Làm thế nào nó làm điều này? Bạn đoán nó, bằng cách sử dụng "khóa" hoặc tên của đầu vào đã được đăng.
Vì vậy, nếu bài đăng biểu mẫu trông giống như
Foo.Bar.Baz.FooBar = Hello
Và bạn đang đăng lên một mô hình được gọi là SomeViewModel
, sau đó nó làm ngược lại những gì người trợ giúp đã làm ngay từ đầu. Nó tìm kiếm một thuộc tính gọi là "Foo". Sau đó, nó tìm kiếm một thuộc tính có tên là "Bar" từ "Foo", sau đó tìm kiếm "Baz" ... và v.v.
Cuối cùng nó cố gắng phân tích cú pháp giá trị thành kiểu "FooBar" và gán nó cho "FooBar".
PHIM !!!
Và thì đấy, bạn có mô hình của mình. Ví dụ mà Model Binder vừa xây dựng được đưa vào Action được yêu cầu.
Vì vậy, giải pháp của bạn không hoạt động vì những Html.[Type]For()
người trợ giúp cần một biểu thức. Và bạn chỉ đang cho họ một giá trị. Nó không biết bối cảnh của giá trị đó là gì và nó không biết phải làm gì với nó.
Bây giờ một số người đã đề xuất sử dụng các thành phần để kết xuất. Bây giờ điều này trên lý thuyết sẽ hoạt động, nhưng có lẽ không phải là cách mà bạn mong đợi. Khi bạn kết xuất một phần, bạn đang thay đổi kiểu TModel
, bởi vì bạn đang ở trong một ngữ cảnh xem khác. Điều này có nghĩa là bạn có thể mô tả thuộc tính của mình bằng một biểu thức ngắn gọn hơn. Điều đó cũng có nghĩa là khi trình trợ giúp tạo tên cho biểu thức của bạn, nó sẽ nông. Nó sẽ chỉ tạo ra dựa trên biểu thức mà nó đã cho (không phải toàn bộ ngữ cảnh).
Vì vậy, giả sử bạn có một phần vừa hiển thị "Baz" (từ ví dụ của chúng tôi trước đây). Bên trong phần đó, bạn chỉ có thể nói:
@Html.TextBoxFor(model=>model.FooBar)
Thay vì
@Html.TextBoxFor(model=>model.Foo.Bar.Baz.FooBar)
Điều đó có nghĩa là nó sẽ tạo một thẻ đầu vào như sau:
<input name="FooBar" />
Mà, nếu bạn đang đăng biểu mẫu này cho một hành động đang mong đợi một ViewModel lớn được lồng sâu vào nhau, thì nó sẽ cố gắng hydrate một thuộc tính được gọi là FooBar
off of TModel
. Tốt nhất là không có, và tệ nhất là một cái gì đó hoàn toàn khác. Nếu bạn đang đăng cho một hành động cụ thể đang chấp nhận một Baz
, chứ không phải là mô hình gốc, thì điều này sẽ hoạt động tốt! Trên thực tế, các phần tử là một cách tốt để thay đổi ngữ cảnh chế độ xem của bạn, ví dụ: nếu bạn có một trang có nhiều biểu mẫu mà tất cả đều đăng các hành động khác nhau, thì việc hiển thị một phần cho từng biểu mẫu sẽ là một ý tưởng tuyệt vời.
Bây giờ khi bạn có tất cả những điều này, bạn có thể bắt đầu làm những điều thực sự thú vị Expression<>
, bằng cách mở rộng chúng theo chương trình và làm những việc gọn gàng khác với chúng. Tôi sẽ không tham gia vào bất kỳ điều đó. Tuy nhiên, hy vọng rằng điều này sẽ giúp bạn hiểu rõ hơn về những gì đang diễn ra ở hậu trường và tại sao mọi thứ lại diễn ra theo cách như vậy.
@
trước tất cảforeach
s? Bạn cũng không nên có lambdas trongHtml.EditorFor
(Html.EditorFor(m => m.Note)
ví dụ) và phần còn lại của các phương thức? Tôi có thể nhầm, nhưng bạn có thể vui lòng dán mã thực của mình được không? Tôi khá mới với MVC, nhưng bạn có thể giải quyết nó khá dễ dàng với các lượt xem một phần hoặc trình chỉnh sửa (nếu đó là tên?).