คำตอบอย่างรวดเร็วคือการใช้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?
- Expression Tree คืออะไร?
- 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>
มากเกินไป ดังนั้นเพื่อระบุสิ่งที่เกิดขึ้นเบื้องหลังเมื่อมีดโกนแสดงมุมมองนี้มันจะสร้างคลาส ภายในคลาสนี้เป็นอินสแตนซ์ของHtmlHelper<TModel>
(เป็นคุณสมบัติHtml
ซึ่งเป็นเหตุผลที่คุณสามารถใช้ได้@Html...
) TModel
ประเภทที่กำหนดไว้ใน@model
คำสั่งของคุณอยู่ที่ไหน ดังนั้นในกรณีของคุณเมื่อคุณดูมุมมองนี้TModel
จะเป็นประเภทViewModels.MyViewModels.Theme
เสมอ
ตอนนี้การโต้เถียงครั้งต่อไปค่อนข้างยุ่งยาก ดังนั้นมาดูคำวิงวอน
@Html.TextBoxFor(model=>model.SomeProperty);
ดูเหมือนว่าเรามีแลมด้าตัวเล็ก ๆ และถ้ามีใครจะเดาลายเซ็นเราอาจคิดว่าประเภทของอาร์กิวเมนต์นี้จะเป็น a ประเภทของโมเดลมุมมองFunc<TModel, TProperty>
อยู่ที่ไหนTModel
และTProperty
อนุมานเป็นประเภทของคุณสมบัติ
แต่ thats ไม่ถูกต้องถ้าคุณมองไปที่เกิดขึ้นจริงExpression<Func<TModel, TProperty>>
ประเภทของการโต้แย้งของมัน
ดังนั้นเมื่อคุณสร้างแลมบ์ดาตามปกติคอมไพลเลอร์จะนำแลมด้าและรวบรวมลงใน MSIL เช่นเดียวกับฟังก์ชันอื่น ๆ (ซึ่งเป็นเหตุผลที่คุณสามารถใช้ตัวแทนกลุ่มวิธีการและแลมบดาแทนกันได้มากหรือน้อยเพราะเป็นเพียงการอ้างอิงโค้ด .)
อย่างไรก็ตามเมื่อคอมไพเลอร์เห็นว่า type เป็น an Expression<>
มันจะไม่รวบรวม lambda ลงไปที่ MSIL ในทันที แต่มันจะสร้าง Expression Tree ขึ้นมาแทน!
ดังนั้นสิ่งที่ห่าคือต้นไม้แสดงออก ไม่ซับซ้อน แต่ก็ไม่ได้เดินเล่นในสวนสาธารณะเช่นกัน ในการอ้างถึง MS:
| แผนภูมินิพจน์แทนรหัสในโครงสร้างข้อมูลแบบต้นไม้โดยที่แต่ละโหนดเป็นนิพจน์ตัวอย่างเช่นการเรียกใช้เมธอดหรือการดำเนินการไบนารีเช่น x <y
พูดง่ายๆก็คือต้นไม้นิพจน์คือการแสดงฟังก์ชันเป็นชุดของ "การกระทำ"
ในกรณีของmodel=>model.SomeProperty
โครงสร้างนิพจน์จะมีโหนดอยู่ในนั้นซึ่งระบุว่า: "Get 'Some Property' from a 'model'"
แผนภูมินิพจน์นี้สามารถรวบรวมเป็นฟังก์ชันที่สามารถเรียกใช้งานได้ แต่ตราบใดที่เป็นโครงสร้างนิพจน์จะเป็นเพียงชุดของโหนด
แล้วมันดีสำหรับอะไร?
ดังนั้น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 ถึง SQL ทำสิ่งที่คล้ายกันโดยการเดินแผนภูมินิพจน์และสร้างไวยากรณ์ที่แตกต่างกันซึ่งในกรณีนี้คือแบบสอบถาม SQL)
Model Binder ทำงานอย่างไร?
ดังนั้นเมื่อคุณได้รับสิ่งนั้นเราต้องพูดคุยสั้น ๆ เกี่ยวกับตัวยึดแบบจำลอง เมื่อโพสต์แบบฟอร์มก็เหมือนกับแบน
Dictionary<string, string>
เราได้สูญเสียโครงสร้างลำดับชั้นที่โมเดลมุมมองซ้อนกันของเราอาจมี เป็นงานของตัวประสานแบบจำลองที่จะใช้คำสั่งผสมคู่คีย์ - ค่านี้และพยายามทำให้วัตถุที่มีคุณสมบัติบางอย่างกลับคืนมา มันทำอย่างไร? คุณเดาได้โดยใช้ "คีย์" หรือชื่อของอินพุตที่โพสต์
ดังนั้นหากโพสต์แบบฟอร์มดูเหมือน
Foo.Bar.Baz.FooBar = Hello
และคุณกำลังโพสต์ไปยังโมเดลที่เรียกว่าSomeViewModel
จากนั้นมันจะกลับกันสิ่งที่ผู้ช่วยเหลือทำในตอนแรก มันมองหาอสังหาริมทรัพย์ที่เรียกว่า "Foo" จากนั้นมองหาอสังหาริมทรัพย์ที่เรียกว่า "บาร์" จาก "Foo" จากนั้นมองหา "Baz" ... และอื่น ๆ ...
ในที่สุดก็พยายามแยกวิเคราะห์ค่าเป็นประเภท "FooBar" และกำหนดให้ "FooBar"
วุ้ย!!!
และ voila คุณมีแบบจำลองของคุณ อินสแตนซ์ Model Binder ที่เพิ่งสร้างจะถูกส่งไปยัง Action ที่ร้องขอ
ดังนั้นวิธีแก้ปัญหาของคุณจึงไม่ได้ผลเนื่องจากHtml.[Type]For()
ผู้ช่วยเหลือต้องการการแสดงออก และคุณแค่ให้คุณค่าแก่พวกเขา ไม่มีความคิดว่าบริบทสำหรับค่านั้นคืออะไรและไม่รู้ว่าจะทำอย่างไรกับมัน
ตอนนี้มีบางคนแนะนำให้ใช้บางส่วนเพื่อแสดงผล ตอนนี้ในทางทฤษฎีจะได้ผล แต่อาจไม่ใช่วิธีที่คุณคาดหวัง เมื่อคุณแสดงผลบางส่วนคุณกำลังเปลี่ยนประเภทTModel
เนื่องจากคุณอยู่ในบริบทมุมมองที่แตกต่างกัน ซึ่งหมายความว่าคุณสามารถอธิบายคุณสมบัติของคุณด้วยสำนวนสั้น ๆ นอกจากนี้ยังหมายความว่าเมื่อผู้ช่วยเหลือสร้างชื่อสำหรับการแสดงออกของคุณมันจะตื้น จะสร้างตามนิพจน์ที่กำหนดเท่านั้น (ไม่ใช่บริบททั้งหมด)
สมมติว่าคุณมีบางส่วนที่แสดงเพียง "Baz" (จากตัวอย่างก่อนหน้านี้) ภายในบางส่วนคุณสามารถพูดได้ว่า:
@Html.TextBoxFor(model=>model.FooBar)
ค่อนข้างมากกว่า
@Html.TextBoxFor(model=>model.Foo.Bar.Baz.FooBar)
นั่นหมายความว่าจะสร้างแท็กอินพุตดังนี้:
<input name="FooBar" />
ซึ่งถ้าคุณกำลังโพสต์แบบฟอร์มนี้เพื่อการดำเนินการที่คาดว่าจะมีขนาดใหญ่ซ้อนกันอย่างลึกซึ้ง ViewModel แล้วก็จะพยายามให้ความชุ่มชื้นคุณสมบัติที่เรียกว่าออกจากFooBar
TModel
สิ่งที่ดีที่สุดไม่มีอยู่ที่นั่นและที่แย่ที่สุดคืออย่างอื่นทั้งหมด หากคุณกำลังโพสต์ไปยังการดำเนินการเฉพาะที่ยอมรับ a Baz
แทนที่จะเป็นรูปแบบรูทสิ่งนี้จะได้ผลดี! ในความเป็นจริงบางส่วนเป็นวิธีที่ดีในการเปลี่ยนบริบทการดูของคุณเช่นหากคุณมีเพจที่มีหลายรูปแบบที่โพสต์ไปยังการกระทำที่แตกต่างกันการแสดงผลบางส่วนสำหรับแต่ละส่วนจะเป็นความคิดที่ดี
เมื่อคุณได้สิ่งเหล่านี้ทั้งหมดแล้วคุณสามารถเริ่มทำสิ่งที่น่าสนใจได้ด้วยการExpression<>
ขยายโปรแกรมและทำสิ่งอื่น ๆ ที่เป็นระเบียบ ฉันจะไม่เข้าใจอะไรเลย แต่หวังว่านี่จะช่วยให้คุณเข้าใจสิ่งที่เกิดขึ้นเบื้องหลังได้ดีขึ้นและเหตุใดสิ่งต่างๆจึงดำเนินไปอย่างที่เป็นอยู่
@
ก่อนทั้งหมดforeach
? คุณไม่ควรมี lambdas ในHtml.EditorFor
(Html.EditorFor(m => m.Note)
เช่น) และวิธีการอื่น ๆ หรือไม่? ฉันอาจเข้าใจผิด แต่คุณช่วยวางรหัสจริงของคุณได้ไหม ฉันค่อนข้างใหม่กับ MVC แต่คุณสามารถแก้ไขได้ค่อนข้างง่ายด้วยมุมมองบางส่วนหรือบรรณาธิการ (ถ้าเป็นชื่อ?)