ASP.NET MVC Multiple ViewModel 的正確使用方式
- 2016-09-10
- 43246
- 0
ViewModel 的使用在 ASP.NET MVC 中是一個很重要的觀念,但是初學者很容易遇到一個問題就是「一個頁面只有一個 ViewModel 怎麼夠用」,大多數的初學者可能就直接使用 ViewData 或 ViewBag 去傳遞第二個資料物件,如果你這樣傳最直接的問題就是喪失了「內建驗證」的方便性,那究竟應該怎麼正確的處理多個 ViewModel 的問題呢?
因為太多人問了,所以 demo 決定寫一個簡單的範例來有效解決初學者的多數 ViewModel 的問題。
最終目的是完成一個頁面,同時支援「使用者註冊」與「使用者登入」的功能,現在就讓我們來一步一步的完成下去
首先就從建立 ViewModel 開始,請分別建立 LoginViewModels 與 RegisterViewModels 作為登入和註冊使用
LoginViewModels.cs
public class LoginViewModels { [EmailAddress] public string Email { get; set; } public string Password { get; set; } }
RegisterViewModels.cs
public class RegisterViewModels { [EmailAddress] public string Email { get; set; } public string Password { get; set; } }
ViewModel 建立完畢後新增對應的 Controller HomeController.cs
public ActionResult Login() { return View(); } [HttpPost] public ActionResult Login(LoginViewModels model) { return View(); } public ActionResult Register() { return View(); } [HttpPost] public ActionResult Register(RegisterViewModels model) { return View(); }
再來就可以利用 ViewModel 搭配 scaffold 建立出 View Login.cshtml
@model GroupViewModelSample.Models.LoginViewModels @{ ViewBag.Title = "Login"; } <h2>Login</h2> @using (Html.BeginForm()) { @Html.AntiForgeryToken() <div class="form-horizontal"> <h4>LoginViewModels</h4> <hr /> @Html.ValidationSummary(true, "", new { @class = "text-danger" }) <div class="form-group"> @Html.LabelFor(model => model.Email, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Email, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Email, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Password, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Password, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Password, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" value="Create" class="btn btn-default" /> </div> </div> </div> } <div> @Html.ActionLink("Back to List", "Index") </div> @section Scripts { @Scripts.Render("~/bundles/jqueryval") }
Register.cshtml
@model GroupViewModelSample.Models.RegisterViewModels @{ ViewBag.Title = "Register"; } <h2>Register</h2> @using (Html.BeginForm()) { @Html.AntiForgeryToken() <div class="form-horizontal"> <h4>RegisterViewModels</h4> <hr /> @Html.ValidationSummary(true, "", new { @class = "text-danger" }) <div class="form-group"> @Html.LabelFor(model => model.Email, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Email, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Email, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Password, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Password, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Password, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" value="Create" class="btn btn-default" /> </div> </div> </div> } <div> @Html.ActionLink("Back to List", "Index") </div> @section Scripts { @Scripts.Render("~/bundles/jqueryval") }
這時候讀者可以試試看資料都是正常
請建立一個新的 ViewModel GroupViewModels.cs
public class GroupViewModels { public LoginViewModels Login { get; set; } public RegisterViewModels Register { get; set; } }
接下來實做頁面 Combine.cshtml
@model GroupViewModelSample.Models.GroupViewModels @{ ViewBag.Title = "Combine"; } <h2>Combine</h2> @using (Html.BeginForm("Login","Home")) { @Html.AntiForgeryToken() <div class="form-horizontal"> <h4>LoginViewModels</h4> <hr /> @Html.ValidationSummary(true, "", new { @class = "text-danger" }) <div class="form-group"> @Html.LabelFor(model => model.Login.Email, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Login.Email, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Login.Email, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Login.Password, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Login.Password, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Login.Password, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" value="Create" class="btn btn-default" /> </div> </div> </div> } @using (Html.BeginForm("Register", "Home")) { @Html.AntiForgeryToken() <div class="form-horizontal"> <h4>RegisterViewModels</h4> <hr /> @Html.ValidationSummary(true, "", new { @class = "text-danger" }) <div class="form-group"> @Html.LabelFor(model => model.Register.Email, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Register.Email, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Register.Email, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Register.Password, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Register.Password, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Register.Password, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" value="Create" class="btn btn-default" /> </div> </div> </div> } @section Scripts { @Scripts.Render("~/bundles/jqueryval") }
- Model 的宣告是 GroupViewModels 這個組合的 ViewModel
- 兩個 Form 的 Action 分別傳入到各自的 Action 並不是傳到 Combine 的Post Action
- 強型別 HtmlHelper 的部分多了 Model 的屬性名稱
- model.Login.Email
- model.Register.Email
這時候您可以再次嘗試使用「登入」或「註冊」功能(本範例選擇登入)
各位讀者可以發現值沒有正確的接到,這主要的問題在於因為我們使用了組合的 ViewModel ,造成實際要對應的屬性名稱中間還多了一層,仔細看原始碼也可以明確的發現
有許多開發者到這裡就卡死了,而轉去選擇讓「登入」與「註冊」都是傳遞到 Combine Action[Post],然後在 後端程式碼判斷使用者要的是哪個功能,這樣不但造成程式碼複雜度增加也嚴重低估了 ASP.NET MVC 的 ModelBinder 能力。正確的解決方式非常簡單,只需要指定 ModelBinder 時的 Prefix 就可以了
[HttpPost] public ActionResult Login([Bind(Prefix = "login")]LoginViewModels model) { return View(); }
口說無憑,來看影片吧
經由以上的示範,各位開發者應該對於 ViewModel 是複雜型別的時候該怎麼解決有個底了
如果你對以上的程式有興趣的話可以點擊下方連結觀看原始程式
回應討論