< ก่อนหน้า: เรื่อง ASP.NET MVC
ในบันทึกฉบับก่อนเป็นการอธิบายถึง MVC และสร้างตัวอย่าง Project แบบ ASP.NET MVC ที่แสดงให้เห็นการทำงานในส่วน Routing, Controller และ View ยังไม่มีการนำ Model มาใช้งานร่วมด้วย บันทึกนี้จึงเก็บข้อมูลเกี่ยวกับ Model เป็นหลัก
จากเอกสารในเว็บของ Microsoft เรื่อง Getting Stated with ASP.NET MVC5 หัวข้อ Adding a View หัวข้อย่อย Passing Data from the Controller to the View อธิบายว่าการส่งข้อมูลจาก Controller ไปยัง View ทำได้ 2 วิธี คือ (1) ViewBag และ (2) ViewModel ซึ่งวิธีที่ 2 เป็นที่นิยมมากกว่า แบ่งออกเป็น (2.1) Dynamic typed view และ (2.2) Strongly typed view
นอกจากนี้ยังพบวิธีการส่งค่าในวิธีอื่นจากบทความภายนอกอีก คือ ViewData(MVC3+) TempData(MVC3+)
ในเอกสารฉบับนี้จะบันทึกวิธีการส่งค่าข้อมูล หรือ Model ไปยัง View ครอบคลุมวิธีการเหล่านี้ คือ
- ใช้ ViewBag (MVC3+)
- ใช้ Model กับ Dynamic typed view (using @model dynamic) (MVC3+)
- ใช้ Model กับ Strongly typed view (MVC1)
- ใช้ ViewData (MVC3+)
- ใช้ TempData (MVC3+)
ViewBag
หลังจากที่เราได้เริ่มสร้างโปรเจ็คแบบ MVC ในบันทึกก่อน การส่ง ข้อมูล (M) ไปยัง หน้าจอ (V) ผ่านทาง HomeController (C) นั้นจะส่งผ่าน ViewBag จากภาพประกอบด้านล่างใน Action ชื่อ About ได้กำหนดค่าที่เป็นข้อความ “Your application description page.” ให้กับ ViewBag.Message เมื่อ return View() ตัว Controller จะเลือก View ที่สัมพันธ์กับชื่อ ของ Controller และ Action นั่นคือ Views/Home/About.cshtml โดยนำข้อความดังกล่าวไปแสดงที่ @ViewBag.Message นั่นเอง
ViewBag ยังอนุญาตให้เราสามารถกำหนด Property อื่นๆ เพิ่มได้ เช่น ที่ HomeController>About เพิ่ม ViewBag.Phone = “1234567890”; และใน View กำหนดการแสดงผล <div>Tel: @ViewBag.Phone</div> เป็นต้น ดังนั้นจึงควรตกลงกันระหว่างทีมทำงานให้ดีว่าจะส่งค่าอะไรบ้าง
Dynamic Typed View
การส่งค่าด้วย ViewModel มี 2 แบบคือ Dynamic และ Strongly Typed View ทั้ง 2 แบบนี้มีความแตกต่างกันที่รูปแบบการอ้างถึงรายละเอียดของ Model ใน View เท่านั้น
ใน Project แบบ MVC มีโฟลเดอร์ชื่อ Models เพื่อให้เราจัดเก็บโครงสร้าง Model ไว้ให้เป็นระเบียบ แต่ในทางปฎิบัติผู้พัฒนาสามารถอ้างถึง Model ผ่านการ Reference จากที่อื่น หรือสร้าง Model ใน Controller ก็ได้
สำหรับบันทึกนี้ผมใช้ตัวอย่าง Model ตามที่คุณ Samak Buachoo ได้บันทึกวิดีโอไว้ในลิงค์ Youtube เป็นโครงสร้างข้อมูลของ Coffee และ Company เนื่องจากในคลิปได้อธิบายถึงการสร้าง Model และการใช้ Attribute หรือ Data Annotation เพื่ออธิบาย Property ต่างๆ ของ Model ว่ามีความจำเป็นอย่างไร มีคุณสมบัติอะไร เป็นต้น ศึกษาเพิ่มเติมที่ MSDN: System.ComponentModel.DataAnnotaions
เพื่อให้ง่ายต่อการทำบันทึกผมจึงให้ Class ของ Coffee และ Company มาอยู่ในไฟล์เดียวกัน
namespace HelloMVC.Models { public class Coffee { [Key] public int ID { get; set; } [Display(Name = "ชื่อกาแฟ")] [Required] public string Name { get; set; } [Range(0, 1000)] [Display(Name = "ขนาดบรรจุ (กรัม)")] public int Volume { get; set; } [ForeignKey("Company")] public int CompanyID { get; set; } } public class Company { [Key] public int ID { get; set; } [Display(Name = "ชื่อบริษัท")] [Required] public string Name { get; set; } } }
สร้าง View แบบ Empty(without model) ภายใต้โฟล์เดอร์ View>Home ชื่อ CoffeeDTyped และ เพิ่ม Action ชื่อ CoffeeDTyped (ตั้งชื่อให้ตรงกับชื่อ View) ใน HomeController ในตัวอย่างจะเป็นการจำลองข้อมูลอย่างง่ายแบบพร้อมใช้ ไม่เชื่อมต่อกับฐานข้อมูลใดๆ ใส่ข้อมูลตัวอย่าง ยี่ห้อกาแฟ ขนาดบรรจุเป็นกรัม แต่ยังไม่สนใจข้อมูลบริษัท
namespace HelloMVC.Controllers { public class HomeController : Controller { public ActionResult CoffeeSTyped() { List<Coffee> coffees = new List<Coffee>() { new Coffee { ID=1, Name= "Doi Chang Professional", Volume=1000, CompanyID=0}, new Coffee { ID=2, Name= "Mezzo Coffee Bean Arabica House Blend", Volume=250, CompanyID=0}, new Coffee { ID=3, Name= "Duchess Espresso Roma Blended Coffee Bean", Volume=200, CompanyID=0} }; return View(coffees); } } }
ในส่วนของ View ที่บรรทัดบนให้ใส่ directive @model dynamic เพื่อบอกให้รู้ว่าเป็น Dynamic Typed View คือ ไม่อ้างถึงโครงสร้างของ Model ทำให้การอ้างถึงชื่อ Property ของ Model ในหน้า View จะไม่มีตัวช่วย (Intellisense) ดังนั้นทีมออกแบบหน้าจอต้องทราบว่าข้อมูลที่ส่งมาให้นั้นเป็นอย่างไร ซึ่งในตัวอย่างนี้เป็น Collection ของ Coffee และใช้การวนลูปแสดงรายละเอียดของกาแฟ
แต่การอ้างชื่อ Property ไม่ถูกต้องทำให้เกิดปัญหาในขั้นตอนการผูกข้อมูลได้ เช่น เรียกข้อมูล Name1 ซึ่งไม่ได้อธิบายไว้ใน Class ของ Coffee เพื่อหลีกเลี่ยงปัญหานี้สามารถไปใช้แนวทางแบบ Strongly Typed View ที่จะอธิบายต่อไป
Strongly Typed View
สำหรับ Strongly Typed View แตกต่างจากวิธีข้างต้นเล็กน้อย ในส่วนการประกาศ @model ที่ส่วนต้นของ View ที่จะระบุถึงโครงสร้าง Model ที่ใช้งาน ซึ่งในขั้นตอนกระบวนการสร้าง View ให้กำหนด Model ที่ต้องการใช้งาน และรูปแบบการใช้งาน (Template) ในตัวอย่างนี้มีข้อมูล Coffee หลายรายการ จึงกำหนด Template แบบ List และใน HomeController ให้สร้าง Action ชื่อ CoffeeSTyped (ตั้งชื่อให้ตรงกับ View เพื่อให้ง่ายต่อการเรียกใช้ และใช้โค๊ดเดียวกับ CoffeeDTyped)
เมื่อสร้าง View จาก Template แบบ List พร้อมระบุ Model เครื่องมือจะทำการสร้างหน้าจอที่มี Property ของ Model พร้อมให้นำไปใช้งานได้ทันที สังเกตุที่บรรทัดบนเปลี่ยนจาก @model dynamic เป็นระบุถึง Model ที่ใช้งาน ในตัวอย่างนี้เป็น Collection ของ Model Coffee จึงประกาศเป็น @model IEnumerable<HelloMVC.Models.Coffee>
หน้า View ที่สร้างแบบ Strongly Typed View (แบบระบุ template และ model) จากเครื่องมือของ Visual Studio นั้นได้สร้างการแสดงผลในรูปแบบของตาราง (มีปุ่ม Create, Edit, Details และ Delete มาให้) ที่หัวตาราง <th> สำหรับคำอธิบายข้อมูลหรือเป็น Label จะใช้ผลลัพธ์จากคำสั่ง @Html.DisplayNameFor(model => model.Name) สำหรับโครงสร้างโค้ดแบบนี้คืออะไรนั้น ผมได้แยกไปบันทึกไว้อีกเรื่องนึง (p2406) คำสั่งนี้จะนำค่าจาก Data Annotation – Display(Name=”ชื่อกาแฟ”) ของ Property Class มาแสดง
ส่วนการแสดงรายละเอียดของข้อมูล ในที่นี่คือ Coffee สามารถใช้คำสั่ง @Html.DisplayFor(modelItem => item.Name) ทั้งนี้ผู้พัฒนาสามารถเปลี่ยนชื่อตัวแปร modelItem เป็น x เหมือนที่ถนัดก็ได้ หรือ สามารถเขียนแบบสั้นๆ ด้วย @item.Name ก็ใช้ได้เช่นกัน
ข้อดีของการใช้ Strong Typed View คือ ผู้ที่รับผิดชอบในส่วนของ View มีตัวช่วย (Intellisense) ในการอ้างถึงชื่อ Property ต่างๆ ของ Model ได้อย่างถูกต้อง (ข้อเสีย!!! น่าจะเป็นเรื่องของการผูกติดกันเหนียวแน่นขึ้นของ Model กับ View ต้องไปหาข้อมูลเพิ่มเติมอีก)
ViewData
ViewData เป็นวิธีการส่งข้อมูลจาก Controller ไปยัง View ที่เสนอใน MVC รุ่น 1 และ 2 (Use View Data and Implement ViewModel Classes) และยังคงใช้ได้ใน MVC5 ด้วยแนวคิดแบบ Dictionary คือ กำหนด Key และ Value ให้กับ ViewData และในหลายๆ บทความได้โน็ตไว้ว่าการทำงานกับ ViewData มีความเร็วสูงกว่าการใช้ ViewBag โดยตัวอย่างตามลิงค์ของ Microsoft ข้างต้น (ได้คัดลอกโค๊ดมาแสดงไว้ด้านล่าง) จะมีการใช้ Model สำหรับข้อมูลหลัก ในตัวอย่างคือ Dinner ซึ่งจะใช้แบบ Strongly หรือ Dynamic Type View ก็ได้ และใช้ ViewData สำหรับข้อมูลประกอบ เช่น ใช้ ViewData[“Countries”] สำหรับรายชื่อประเทศที่จะนำไปใช้เป็นรายการตัวเลือกใน drop-downlist ของ View
// // GET: /Dinners/Edit/5 [Authorize] public ActionResult Edit(int id) { Dinner dinner = dinnerRepository.GetDinner(id); ViewData["Countries"] = new SelectList(PhoneValidator.AllCountries, dinner.Country); return View(dinner); }
<%= Html.DropDownList("Country", ViewData["Countries"] as SelectList) %>
ใน MSDN ได้อธิบายเครื่องหมาย <%= %> ว่าเป็น Embedded Code Block ที่ใช้ใน ASP.NET Web Page หรือ บล็อกของโค๊ดที่ฝังตัวอยู่ในหน้าเว็บแทนที่จะอยู่ใน Code-Behind เทียบกับ บล็อก Script ในภาษาอื่นๆ
แต่การเขียนโค้ดบน View ที่ใช้ Razer Syntax นั้น วิธีการเขียนส่วนส่วนแสดงผลจะมีความแตกต่างกันเล็กน้อย เช่น ใช้ @Html.DropDownList ในการทำ drop-downlist
ให้เตรียมข้อมูลจำลองใน HomeController ส่วนของ Contact เพื่อจำลองรายชื่อประเทศ และ กำหนดค่าให้กับ ViewData
ในส่วนของ View ที่ชื่อ Home.Contact จะใช้คำสั่ง @Html.DropDownList ในการแสดงวัตถุตัวเลือก และ รายการตัวเลือก ที่ต้องมีชนิดข้อมูลเป็น IEnumerable<SelectListItem> เพื่อให้ตัวแปลงคำสั่งแปลงข้อมูลเหล่านี้เป็น Text และ Value ตามมาตรฐาน HTML นั่นเอง
เสริม สมมติว่าใน Model ได้มีการผ่านค่า Country มาด้วย ผมมีตัวอย่างการเขียนโค็ดสำหรับกำหนดค่า Selected ให้กับ drop-downlist มาให้ 2 แบบ ดังนี้
/* แบบที่ 1 ใช้ค่า Country จาก ViewData ที่ส่งมา เลือก country ตามค่าของ Model (สมมติเป็น 2) ด้วยคำสั่ง Single และกำหนดค่า Selected ให้เป็น true */ @{ IEnumerable<SelectListItem> countries = ViewData["Country"] as IEnumerable<SelectListItem>; SelectListItem selectedCountry = countries.Single(x => x.Value == "2"); if (selectedCountry != null) selectedCountry.Selected = true; @Html.DropDownList("ddlCountry", countries); }
/* แบบที่ 2 นำค่า Country จาก ViewData มาแปลงรูปใหม่ให้เป็น SelectList เพื่อให้มี option ในการกำหนดค่า Selected ได้ */ @{ var countries = new SelectList(ViewData["Country"] as IEnumerable<SelectListItem>, "Value", "Text", "2"); @Html.DropDownList("ddlCountry", countries); }
TempData
TempData เป็นพื้นที่สำหรับจัดเก็บข้อมูลแบบ Dictionary (Key, Value) สำหรับใช้งานระหว่าง Action หรือ ระหว่าง Controller ซึ่งในตัวอย่างมักพบกับการกำหนดค่าข้อมูลและมีการ RedirectToAction การทำงานไปยังหน้าอื่นเพื่อนำข้อมูลนี้ไปใช้งาน โดยค่าข้อมูลนี้จะหายไปหลังจากใช้งาน เพราะเทคนิคนี้ทำงานผ่านตัวแปร Session หากต้องการเก็บค่าดังกล่าวเพื่อใช้งานต่อให้เรียกคำสั่ง TempData.Keep()
TempData in ASP.NET MVC can be used to store temporary data which can be used in the subsequent request. TempData will be cleared out after the completion of a subsequent request. TempData is useful when you want to transfer non-sensitive data from one action method to another action method of the same or a different controller as well as redirects. It is dictionary type which is derived from TempDataDictionary. Reference: http://www.tutorialsteacher.com/mvc/tempdata-in-asp.net-mvc
แนะนำให้ดูตารางเปรียบเทียบความแตกต่างของการใช้ Model ในแต่ละแบบที่ http://www.mytecbits.com/microsoft/dot-net/viewmodel-viewdata-viewbag-tempdata-mvc
อ้างอิง
- https://docs.microsoft.com/en-us/aspnet/mvc/overview/views/dynamic-v-strongly-typed-views
- http://codesanook.com/post/details/mvc_ch2/106
- https://sysadmin.psu.ac.th/2016/07/13/asp-net-mvc-part-4-ทำความรู้จักกับ-viewdata-viewbag-แล/
สุดยอดไปเลย อิอิ