ASP.NET MVC Multiple ViewModel 的正確使用方式
- 2016-09-10
- 47206
- 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 是複雜型別的時候該怎麼解決有個底了
如果你對以上的程式有興趣的話可以點擊下方連結觀看原始程式








Roslyn 魔法工坊:打造你的 Source Generator [2025-12-20]開課 共7H
回應討論