MVC Razor view lồng nhau mô hình của foreach


94

Hãy tưởng tượng một kịch bản chung, đây là một phiên bản đơn giản hơn của những gì tôi đang xem. Tôi thực sự có một vài lớp làm tổ tiếp theo ...

Nhưng đây là kịch bản

Chủ đề chứa Danh sách Danh mục chứa Danh sách Sản phẩm chứa Danh sách

Bộ điều khiển của tôi cung cấp một Chủ đề được điền đầy đủ, với tất cả các Danh mục cho chủ đề đó, các Sản phẩm trong danh mục này và đơn đặt hàng của chúng.

Bộ sưu tập đơn đặt hàng có một thuộc tính gọi là Số lượng (trong số nhiều thuộc tính khác) cần được chỉnh sửa.

@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)
      }
   }
}

Nếu tôi sử dụng lambda thay thế thì tôi dường như chỉ nhận được một tham chiếu đến đối tượng Model hàng đầu, "Chủ đề" không phải đối tượng trong vòng lặp foreach.

Liệu những gì tôi đang cố gắng làm ở đó có thể thực hiện được không hay tôi đã đánh giá quá cao hoặc hiểu sai những gì có thể xảy ra?

Với phần trên, tôi gặp lỗi trên TextboxFor, EditorFor, v.v.

CS0411: Không thể suy ra các đối số kiểu cho phương thức 'System.Web.Mvc.Html.InputExtensions.TextBoxFor (System.Web.Mvc.HtmlHelper, System.Linq.Expressions.Expression>)' từ cách sử dụng. Hãy thử chỉ định rõ ràng các đối số kiểu.

Cảm ơn.


1
Bạn không nên có @trước tất cả foreachs? Bạn cũng không nên có lambdas trong Html.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?).
Kobi

category.nameTôi chắc chắn đó là một string...Forkhông hỗ trợ một chuỗi như là tham số đầu tiên
balexandre

vâng, tôi chỉ bỏ lỡ @ 's, bây giờ được thêm vào. Cảm ơn. Tuy nhiên, như đối với lambda, nếu tôi bắt đầu gõ @ Html.TextBoxFor (m => m sau đó tôi chỉ dường như để có được một tham chiếu đến đối tượng mẫu hàng đầu, chứ không phải những người trong vòng lặp foreach..
David C

@DavidC - Tôi chưa biết đủ MVC 3 để trả lời - nhưng tôi nghi ngờ đó là vấn đề của bạn :).
Kobi

2
Tôi đang trên tàu, nhưng nếu câu trả lời này không được trả lời vào lúc tôi đi làm, tôi không nên đăng câu trả lời. Câu trả lời nhanh là sử dụng một thông thường for()thay vì một foreach. Tôi sẽ giải thích tại sao, vì nó cũng khiến tôi bối rối trong một thời gian dài.
J. Holmes

Câu trả lời:


304

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 để LabelForcác trình ...Fortrợ 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 để LabelForcác trình ...Fortrợ 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 LabelForvà và các tiện ích mở rộng TextBoxForkhá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...), TModelkiểu được định nghĩa trong @modelcâ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>, TModelkiể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!

Một là gì Biểu Tree ?

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ư attributestrê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à FooBaroff 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.


4
Câu trả lời tuyệt vời. Tôi hiện đang cố gắng tiêu hóa nó. :) Cũng tội Cargo C sỉ! Giống như mô tả đó.
David C

4
Cảm ơn bạn vì câu trả lời chi tiết này!
Kobi

14
Cần nhiều hơn một phiếu ủng hộ cho việc này. +3 (một cho mỗi lời giải thích), và +1 cho Người theo dõi hàng hóa. Câu trả lời hoàn toàn tuyệt vời!
Kyeotic

3
Đây là lý do tại sao tôi thích SO: câu trả lời ngắn gọn + giải thích chuyên sâu + liên kết tuyệt vời (hàng hóa-sùng bái). Tôi sẽ giới thiệu bài đăng về sự sùng bái hàng hóa cho những ai không nghĩ rằng kiến ​​thức về hoạt động bên trong của các thứ là cực kỳ quan trọng!
user1068352

18

Bạn chỉ có thể sử dụng EditorTemplates để làm điều đó, bạn cần tạo một thư mục có tên "EditorTemplates" trong thư mục chế độ xem của bộ điều khiển và đặt chế độ xem riêng biệt cho từng thực thể lồng nhau của bạn (được đặt tên là tên lớp thực thể)

Quan điểm chính :

@model ViewModels.MyViewModels.Theme

@Html.LabelFor(Model.Theme.name)
@Html.EditorFor(Model.Theme.Categories)

Chế độ xem danh mục (/MyController/EditorTemplates/Category.cshtml):

@model ViewModels.MyViewModels.Category

@Html.LabelFor(Model.Name)
@Html.EditorFor(Model.Products)

Chế độ xem sản phẩm (/MyController/EditorTemplates/Product.cshtml):

@model ViewModels.MyViewModels.Product

@Html.LabelFor(Model.Name)
@Html.EditorFor(Model.Orders)

và như thế

theo cách này, Html.EditorFor helper sẽ tạo tên của phần tử theo cách có thứ tự và do đó bạn sẽ không gặp bất kỳ vấn đề nào khác khi truy xuất toàn bộ thực thể Chủ đề đã đăng


1
Mặc dù câu trả lời được chấp nhận là một câu trả lời rất tốt (tôi cũng đã ủng hộ nó), nhưng câu trả lời này là lựa chọn dễ bảo trì hơn.
Aaron

4

Bạn có thể thêm một phần Danh mục và một phần Sản phẩm, mỗi phần sẽ lấy một phần nhỏ hơn của mô hình chính làm mô hình của chính nó, tức là loại mô hình của Danh mục có thể là IEnumerable, bạn sẽ chuyển vào Model.Theme. Một phần của Sản phẩm có thể là một IEnumerable mà bạn chuyển Model.Products vào (từ một phần của Danh mục).

Tôi không chắc đó có phải là con đường đúng đắn về phía trước hay không, nhưng tôi muốn biết.

BIÊN TẬP

Kể từ khi đăng câu trả lời này, tôi đã sử dụng EditorTemplates và thấy đây là cách dễ nhất để xử lý các nhóm hoặc mục đầu vào lặp lại. Nó xử lý tất cả các vấn đề về thông báo xác thực của bạn và tự động gửi biểu mẫu / tai ương ràng buộc mô hình.


Điều đó đã xảy ra với tôi, chỉ không chắc nó sẽ xử lý như thế nào khi tôi đọc lại để cập nhật.
David C

1
Nó gần đúng, nhưng vì đây là một biểu mẫu được đăng như một đơn vị nên nó sẽ không hoạt động đúng. Khi bên trong một phần, ngữ cảnh khung nhìn đã thay đổi và nó không còn có biểu thức được lồng sâu nữa. Việc đăng lại Thememô hình sẽ không được hydrat hóa đúng cách.
J. Holmes

Đó cũng là mối quan tâm của tôi. Tôi thường làm như trên như một cách tiếp cận chỉ đọc để hiển thị các sản phẩm và sau đó cung cấp một liên kết trên mỗi sản phẩm để có thể là một phương pháp hành động / Product / Edit / 123 để chỉnh sửa từng sản phẩm trên biểu mẫu của riêng nó. Tôi nghĩ rằng bạn có thể hoàn tác khi cố gắng làm quá nhiều trên một trang trong MVC.
Adrian Thompson Phillips

@AdrianThompsonPhillips vâng, rất có thể tôi có. Tôi đến từ nền tảng Biểu mẫu, vì vậy tôi vẫn không thể luôn quen với ý tưởng phải rời khỏi trang để thực hiện chỉnh sửa. :(
David C

2

Khi bạn đang sử dụng vòng lặp foreach trong chế độ xem cho mô hình liên kết ... Mô hình của bạn phải ở định dạng được liệt kê.

I E

@model IEnumerable<ViewModels.MyViewModels>


        @{
            if (Model.Count() > 0)
            {            

                @Html.DisplayFor(modelItem => Model.Theme.FirstOrDefault().name)
                @foreach (var theme in Model.Theme)
                {
                   @Html.DisplayFor(modelItem => theme.name)
                   @foreach(var product in theme.Products)
                   {
                      @Html.DisplayFor(modelItem => product.name)
                      @foreach(var order in product.Orders)
                      {
                          @Html.TextBoxFor(modelItem => order.Quantity)
                         @Html.TextAreaFor(modelItem => order.Note)
                          @Html.EditorFor(modelItem => order.DateRequestedDeliveryFor)
                      }
                  }
                }
            }else{
                   <span>No Theam avaiable</span>
            }
        }

Tôi ngạc nhiên rằng mã trên thậm chí còn được biên dịch. @ Html.LabelFor đòi hỏi một hoạt động FUNC như tham số, bạn không phải là
Jenna Leaf

Tôi không biết liệu đoạn mã trên có biên dịch hay không, nhưng @foreach lồng nhau có hiệu quả với tôi. MVC5.
antonio

0

Rõ ràng là khỏi lỗi.

HtmlHelpers được nối thêm với "For" mong đợi biểu thức lambda làm tham số.

Nếu bạn đang chuyển trực tiếp giá trị, tốt hơn hãy sử dụng Giá trị bình thường.

ví dụ

Thay vì TextboxFor (....), hãy sử dụng Textbox ()

cú pháp cho TextboxFor sẽ giống như Html.TextBoxFor (m => m.Property)

Trong trường hợp của bạn, bạn có thể sử dụng vòng lặp for cơ bản, vì nó sẽ cung cấp cho bạn chỉ mục để sử dụng.

@for(int i=0;i<Model.Theme.Count;i++)
 {
   @Html.LabelFor(m=>m.Theme[i].name)
   @for(int j=0;j<Model.Theme[i].Products.Count;j++) )
     {
      @Html.LabelFor(m=>m.Theme[i].Products[j].name)
      @for(int k=0;k<Model.Theme[i].Products[j].Orders.Count;k++)
          {
           @Html.TextBoxFor(m=>Model.Theme[i].Products[j].Orders[k].Quantity)
           @Html.TextAreaFor(m=>Model.Theme[i].Products[j].Orders[k].Note)
           @Html.EditorFor(m=>Model.Theme[i].Products[j].Orders[k].DateRequestedDeliveryFor)
      }
   }
}

0

Một khả năng khác đơn giản hơn nhiều là một trong các tên thuộc tính của bạn bị sai (có thể là tên bạn vừa thay đổi trong lớp). Đây là những gì nó dành cho tôi trong RazorPages .NET Core 3.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.