上一篇整理到:
網址
↓
Route
↓
Controller
↓
Action
↓
return View()
↓
找到對應的 .cshtml
但實際畫面不可能永遠只是固定文字。
企業專案裡常常要顯示:
使用者姓名
部門
角色
查詢結果
狀態
時間
列表資料
這些資料通常要先在 Controller 準備好,再交給 View 顯示。
所以這篇就來整理最常見的三種方式:
ViewData
ViewBag
Model
假設 Controller 裡有:
public IActionResult Index()
{
string userName = "Abbie";
return View();
}
這時候:
userName
只存在 Index() 這個方法裡。
Index.cshtml 不會自動知道它。
也就是:
Controller 裡有資料
≠
View 自動拿得到
還需要明確把資料交出去。
最直接的一種方式是:
public IActionResult Index()
{
ViewData["UserName"] = "Abbie";
return View();
}
View 裡就可以寫:
<h1>你好,@ViewData["UserName"]</h1>
最後顯示:
你好,Abbie
可以先把 ViewData 想成一個:
用文字當 Key 的資料盒子
Controller 放資料:
ViewData["UserName"] = "Abbie";
View 用同一個 Key 取:
@ViewData["UserName"]
所以:
Controller
ViewData["UserName"]
↓
View
@ViewData["UserName"]
就是 Key 寫錯。
例如 Controller:
ViewData["UserName"] = "Abbie";
View 卻寫:
@ViewData["Name"]
UserName 和 Name 不一樣。
這樣就拿不到原本那筆資料。
所以使用 ViewData 時,我會注意:
Key 名稱一定要對
前面看 _Layout.cshtml 時,可能已經出現過:
<title>@ViewData["Title"]</title>
View 裡則可能寫:
@{
ViewData["Title"] = "首頁";
}
這時流程就是:
Index.cshtml
設定 ViewData["Title"]
↓
_Layout.cshtml
讀取 ViewData["Title"]
所以 ViewData 很常拿來放:
頁面標題
簡單訊息
少量額外資料
第二種常見方式是:
public IActionResult Index()
{
ViewBag.UserName = "Abbie";
return View();
}
View 裡:
<h1>你好,@ViewBag.UserName</h1>
最後一樣會顯示:
你好,Abbie
跟 ViewData 比起來,寫法看起來比較像一般物件:
ViewBag.UserName
而不是:
ViewData["UserName"]
所以我一開始會覺得 ViewBag 比較直覺。
兩者用途很接近。
可以先這樣比較:
ViewData
ViewData["UserName"] = "Abbie";
↓
@ViewData["UserName"]
ViewBag
ViewBag.UserName = "Abbie";
↓
@ViewBag.UserName
最大的感受差異就是寫法。
ViewData 用:
["Key"]
ViewBag 用:
.屬性名稱
而且 ViewBag 底層其實是建立在 ViewData 上的。
所以它們不是兩套完全無關的資料。
有。
例如 Controller:
ViewBag.UserName = "Abbie";
View 不小心寫:
@ViewBag.UserNmae
拼錯了。
因為 ViewBag 是 dynamic,編譯器不一定能像強型別 Model 那樣提早幫你抓到問題。
所以資料一多,我就不會很想全部塞進:
ViewBag
如果資料不是一個簡單字串,而是一整組有結構的資料,就很常用 Model。
例如先建立:
public class UserViewModel
{
public string Name { get; set; }
public string Email { get; set; }
}
Controller:
public IActionResult Index()
{
UserViewModel user = new UserViewModel
{
Name = "Abbie",
Email = "abbie@example.com"
};
return View(user);
}
這次不是:
return View();
而是:
return View(user);
意思就是:
回傳目前 View
+
把 user 一起交給 View
View 上方則要宣告:
@model UserViewModel
然後使用:
<h1>@Model.Name</h1>
<p>@Model.Email</p>
完整流程就是:
Controller 建立 UserViewModel
↓
return View(user)
↓
View 宣告 @model UserViewModel
↓
使用 Model.Name、Model.Email
@model 和 Model 又不一樣?這兩個大小寫很像,但用途不同。
@model UserViewModel
代表:
這個 View 預期收到的資料型別是 UserViewModel
而:
@Model.Name
是在使用實際收到的資料。
可以先記成:
@model
宣告資料型別
Model
使用資料
例如:
@model UserViewModel
<h1>@Model.Name</h1>
前面告訴 Razor:
Model 是 UserViewModel
所以後面才能知道它有:
Name
Email
下一篇會再把 @model、ViewModel、Entity、DTO 一起整理。
假設現在頁面需要:
姓名
Email
部門
角色
建立時間
是否可以編輯
全部用 ViewBag 可能變成:
ViewBag.Name = user.Name;
ViewBag.Email = user.Email;
ViewBag.Department = user.Department;
ViewBag.Role = user.Role;
ViewBag.CreateTime = user.CreateTime;
ViewBag.CanEdit = true;
View 裡再:
@ViewBag.Name
@ViewBag.Email
@ViewBag.Department
@ViewBag.Role
資料一多,很容易開始混亂。
如果改成 ViewModel:
public class UserDetailViewModel
{
public string Name { get; set; }
public string Email { get; set; }
public string Department { get; set; }
public string Role { get; set; }
public DateTime CreateTime { get; set; }
public bool CanEdit { get; set; }
}
Controller:
public IActionResult Detail()
{
UserDetailViewModel model = new UserDetailViewModel
{
Name = "Abbie",
Email = "abbie@example.com",
Department = "資訊部",
Role = "使用者",
CreateTime = DateTime.Now,
CanEdit = true
};
return View(model);
}
View:
@model UserDetailViewModel
<h1>@Model.Name</h1>
<p>@Model.Email</p>
<p>@Model.Department</p>
<p>@Model.Role</p>
@if (Model.CanEdit)
{
<button type="button">編輯</button>
}
這樣 View 很清楚知道:
自己會收到哪些資料
可以先用最簡單的方式判斷:
只是少量簡單資料
↓
ViewData 或 ViewBag
一整組有結構的頁面資料
↓
Model
例如:
頁面標題
成功訊息
簡單提示
可能會用:
ViewData
ViewBag
但像:
使用者詳細資料
查詢結果清單
表單內容
Dashboard
通常比較適合:
Model / ViewModel
這不是絕對規定,還是要看專案原本的寫法。
Model 不一定只能是一筆資料。
例如 Controller:
public IActionResult Index()
{
List<UserViewModel> users = new List<UserViewModel>
{
new UserViewModel
{
Name = "Abbie",
Email = "abbie@example.com"
},
new UserViewModel
{
Name = "Amy",
Email = "amy@example.com"
}
};
return View(users);
}
View 就可以寫:
@model List<UserViewModel>
然後:
@foreach (var user in Model)
{
<div>
<h2>@user.Name</h2>
<p>@user.Email</p>
</div>
}
這時:
Controller 傳的是 List<UserViewModel>
View 也要接 List<UserViewModel>
所以 Model 還有一個重要觀念:
兩邊型別要對得上
假設 View 寫:
@model UserViewModel
<h1>@Model.Name</h1>
但是 Controller:
public IActionResult Index()
{
return View();
}
沒有傳入 UserViewModel。
這時 Model 可能是 null。
如果直接:
@Model.Name
就可能發生錯誤。
所以看到:
Model 是 null
我會先往 Controller 查:
有沒有取得資料?
↓
return View() 有沒有把資料傳進去?
↓
View 的 @model 型別有沒有一致?
不要只是在 View 裡一直補:
@if (Model != null)
因為真正問題可能是 Controller 根本漏傳資料。
假設 View:
@model UserViewModel
但是 Controller 傳:
ProductViewModel product = new ProductViewModel();
return View(product);
那就會變成:
View 預期
UserViewModel
Controller 實際傳入
ProductViewModel
型別對不上,執行時就可能出錯。
所以我會先記:
Controller 傳入的 Model 型別
要和 View 的 @model 對得上
這篇先不深入 Service 和資料庫。
現在先假設 Controller 已經有資料。
例如:
UserViewModel model = new UserViewModel
{
Name = "Abbie"
};
但實際企業專案更常看到:
var model = _userService.GetUser(id);
return View(model);
也就是:
Controller
不一定自己建立資料
可能先去呼叫 Service
↓
Service 取得、處理資料
↓
Controller 再傳給 View
這部分後面會再往下追。
目前先把:
Controller → View
這段弄懂。
以前寫 React 時,可能會有:
<UserCard
name="Abbie"
email="abbie@example.com"
/>
然後 Component:
function UserCard({ name, email }) {
return (
<>
<h1>{name}</h1>
<p>{email}</p>
</>
);
}
可以先把概念對照成:
React
Component 把資料透過 Props 傳給另一個 Component
MVC
Controller 把 Model 傳給 View
例如:
return View(model);
然後:
@model UserViewModel
@Model.Name
兩邊不是完全相同的技術,但共通點都是:
畫面需要資料
就要有人明確把資料交給它
假設 View 裡看到:
@model UserDetailViewModel
<h1>@Model.Name</h1>
但我不知道 Name 從哪裡來。
我現在會往回追:
第一步
看 @model 是哪個型別
↓
第二步
回到對應 Controller Action
↓
第三步
找 return View(...)
↓
第四步
看傳進 View 的是哪個變數
↓
第五步
再找這個變數從哪裡取得
例如:
public IActionResult Detail(int id)
{
var model = _userService.GetDetail(id);
return View(model);
}
我就知道:
View 裡的 Model
↓
來自 Controller 的 model
↓
model 又來自 _userService.GetDetail(id)
這時再繼續往 Service 追。
所以實際追資料時,可以從 View 反推:
@Model.Name
↓
@model 是什麼型別?
↓
Controller 傳了什麼?
↓
return View(model)
↓
model 從哪裡來?
Controller 要把資料交給 View,最常見可以先認識三種方式:
ViewData
ViewBag
Model
ViewData:
ViewData["UserName"] = "Abbie";
View:
@ViewData["UserName"]
ViewBag:
ViewBag.UserName = "Abbie";
View:
@ViewBag.UserName
Model:
return View(model);
View:
@model UserViewModel
@Model.Name
可以先這樣選:
少量、簡單的額外資料
↓
ViewData / ViewBag
一整組結構明確的頁面資料
↓
Model
實際追企業專案時,看到:
@Model.Name
不要只盯著 View。
可以反著追:
View 的 @model
↓
Controller
↓
return View(model)
↓
model 從哪裡來
這樣就開始能找到「畫面上的資料到底是哪裡生出來的」。
下一篇就接著拆這些很容易混在一起的名稱:
@model、ViewModel、Entity 和 DTO 到底是什麼?為什麼看起來全部都只是在裝資料?