ViewModel
ViewModel 是一個用來渲染 ASP.NET MVC 視圖的強類型類,可用來傳遞來自一個或多個視圖模型(即類)或數據表的數據。可將其看做一座連接着模型、數據和視圖的橋梁。其生命期為當前視圖。視圖模型屬於強類型,所以在VS中便有智能提示並且可以進行靜態檢測。

在ASP.NET Core 中使用ViewModel:
先創建一個用於呈現視圖的視圖模型類:
1 public class Student 2 { 3 public int ID { get; set; } 4 public string Name { get; set; } 5 public DateTime Birth { get; set; } 6 }
在控制器中定義該類:
1 public IActionResult Index() 2 { 3 Student student=new Student() 4 { 5 ID = 1, 6 Birth = new DateTime(1997,1,1),
7 Name = "Nanase"
8 };
9 return View(student);
10 }
在視圖中使用
在Razor視圖開頭使用@model 指定強類型,使用了@model 的視圖稱為強類型視圖,強類型視圖可獲得智能提示和靜態檢查。不使用@mdoel 則后面的 @Model 為動態類型,不會獲得智能提示和靜態檢查。
同時強類型 Model 可使用輔助器方法,而弱類型 Model 不能使用輔助器方法,因為 c# 表達式樹不能包含動態操作。
Index視圖:
@model Student <div> @Html.DisplayNameFor(m=>m.ID) @Html.DisplayFor(m=>m.ID) </div> <div> Birth @Model.Birth </div> <div> Name @Model.Name </div>
強類型視圖:

弱類型視圖缺少智能提示:


最終結果:

ViewData
ViewData是一個Dictionary<string,object>的字典,數據以鍵值對的形式存儲在 ViewData 中。ViewData 用來在控制器和視圖之間傳遞數據,也可以在視圖和分部視圖之間傳遞數據。其生存期為當前視圖渲染結束。由於在使用 ViewData 時返回的是一個object對象,所以應用實際類型的屬性或值時需要使用強制類型轉換。當傳遞的數據作為字符串在視圖中使用時不需要進行強制轉換,因為 c# 每個對象存在 ToString 方法,而在 c# 視圖中會自動調用該方法。

除直接定義 ViewData[""] 外,ASP.NET Core 還支持對控制器中的屬性使用 [ViewData] 特性定義ViewData。
但是 ViewData 不能用於跨請求的情形,即無法在跳轉后的頁面使用跳轉前頁面定義的 ViewData ,而后續提到的 TempData 可以用於跨請求的數據傳遞。
在控制器和視圖間使用ViewData傳遞數據:
1 public IActionResult ViewDataTest() 2 { 3 ViewData["student"]=new Student() 4 { 5 ID = 1, 6 Birth = DateTime.Now, 7 Name = "Nanase" 8 }; 9 ViewData["Greeting"] = "Hello"; 10 return View(); 11 }
在 ViewDataTest 視圖中使用ViewData的數據:
@{ Student student = ViewData["student"] as Student; ViewData["test"] = "test"; } @ViewData["greeting"] <div>ID: @student.ID</div> <div>Name: @student.Name</div> <div>Birth: @student.Birth</div> <br/> @Html.ActionLink("Test","Test")
用來測試跨請求的 Test 方法和視圖:
1 public IActionResult Test() 2 { 3 return View(); 4 }
@{ ViewData["Title"] = "Test"; } <h2>Test</h2> @ViewData["test"]
結果:

Test視圖:

直接使用 ViewData 中數據的屬性:

在視圖和部分視圖間使用 ViewData 時,部分視圖中的 ViewData 的定義不會覆蓋原視圖的 ViewData 。在視圖和部分視圖之間傳遞 ViewData 時,主要使用的是 PartialAsync 輔助器方法以及 <partial> 標記輔助器方法。
在視圖和部分視圖間使用ViewData:
public static Task<IHtmlContent> PartialAsync(this IHtmlHelper htmlHelper, string partialViewName, ViewDataDictionary viewData)
第一個參數傳入部分視圖名稱,第二個參數傳入一個 ViewDataDictionary 對象。若不指定第二個參數,則會將當前視圖的 ViewDate 傳入部分視圖。
ViewDataTest2 視圖:
<h2>ViewDataTest2</h2> @{ ViewData["Student"] = new Student() { ID = 1, Birth = DateTime.Now, Name = "Nanase" }; ViewData["Greeting"] = "Good morning"; } View: @ViewData["Greeting"] <br/> @await Html.PartialAsync("PartialView",new ViewDataDictionary(ViewData)) View: @ViewData["Greeting"]
PartialView 視圖:
@{ Student student = ViewData["Student"] as Student; } Partial: @ViewData["Greeting"] <hr/> <div>ID: @student.ID</div> <div>Name: @student.Name</div> <div>Birth: @student.Birth</div> <hr/> @{ ViewData["Greeting"] = "Good afternoon"; } <div>Partial: @ViewData["Greeting"]</div>
結果:

使用 partial 標記輔助器方法:
關於 partial 標記輔助器方法:https://docs.microsoft.com/zh-cn/aspnet/core/mvc/views/partial?view=aspnetcore-2.1
使用時,為 name 屬性傳遞部分視圖的名稱,為 view-data 屬性傳遞 ViewDataDictionary 對象的名稱:
<h2>ViewDataTest2</h2> @{ ViewData["Student"] = new Student() { ID = 1, Birth = DateTime.Now, Name = "Nanase" }; ViewData["Greeting"] = "Good morning"; } View: @ViewData["Greeting"] <br/> @*@await Html.PartialAsync("PartialView",new ViewDataDictionary(ViewData))*@ <partial name="PartialView" view-data="ViewData" /> View: @ViewData["Greeting"]
結果和使用 PartialAsync 輔助器方法一致。
ViewBag
ViewBag 是一個包裝了 ViewData 的動態類型,從 ControllerBase 繼承而來,因此在視圖頁中也無法使用 ViewBag。由於 ViewBag 是一個動態對象,所以可以為其添加任意屬性。也由於 ViewBag 屬於動態類型,所以可以直接調用其中的屬性進行操作。ViewBag 和 ViewData 的區別在於:不能在視圖頁中傳遞特定的 ViewBag。

在視圖頁中使用 ViewBag 失敗。

1 public IActionResult ViewBagTest() 2 { 3 ViewBag.Student = new Student() 4 { 5 ID = 1, 6 Birth = new DateTime(1997,1,1),
7 Name = "Nanase"
8 };
9 return View();
10 }
ViewBagTest 視圖:
<h2>ViewBagTest</h2> <div>View: @(ViewBag.Student.ID+1)</div> @await Html.PartialAsync("_ViewBagPartial")
_ViewBagPartial 視圖:
<div>ID: @ViewBag.Student.ID</div> <div>Name: @ViewBag.Student.Name</div> <div>Birth: @ViewBag.Student.Birth</div>
結果:

TempData
TempData 是一個繼承自 System.Web.Mvc.TempDataDictionary 的類型,是一個 Dictionary<string,object> 的字典。生命期為控制器中生命開始到該 TempData 被讀取結束,即使重定向到另一個控制器的方法,只要一個 TempData 未被讀取,它就仍然存在。在一個訪問過 TempData 的頁面進行刷新會再次訪問該 TempData ,未被延長生命期的 TempData 將被刪除。TempData 通常用於在動作方法之間傳遞數據(如錯誤信息)。
如果希望在一次訪問后保留 TempData 對象,則需要在控制器的方法中調用 TempData.Keep 方法,或者在視圖中使用 TempData.Peek 方法訪問 TempData 對象。但是需要注意的是 TempData.Keep 方法不會延長重定向的視圖中被訪問的 TempData 對象的生命期。重定向的視圖即指使用 RedirectXXX 抵達的視圖。
由於 TempData 將它本身保存在 ASP.NET 的 Session 中,因此需要謹慎使用,當將應用程序托管到多個服務器時 TempData 會出現問題。為解決該問題此時可選擇在 session 的生命期內將用戶的請求全部分配給同一個服務器,或者在服務器之間轉發 session 信息。
除了使用 TempData[""] 對 TempData 進行定義外,還可以使用對屬性使用 [TempData] 修飾對 TempData 進行定義。

1 public IActionResult TempDataTest() 2 { 3 TempData["error"] = "An error"; 4 TempData["greeting"] = "Hello"; 5 6 //將 TempData["error"] 生存期延長一次 7 TempData.Keep("error"); 8 9 return View(); 10 } 11 12 public IActionResult ReceiveTempData() 13 { 14 return View(); 15 } 16 17 public IActionResult ReceiveTempData2() 18 { 19 return View(); 20 }
TempDataTest 視圖:
@{ ViewData["Title"] = "TempDataTest"; } <h2>TempDataTest</h2> <div>@TempData["error"]</div> @*訪問 TempData["greeting"] 並將 TempData["greetng"] 延長一次*@ <div>@TempData.Peek("greeting")</div> <br/> <div>@Html.ActionLink("Another", "Index", "TempData")</div> @Html.ActionLink("ReceiveTempData","ReceiveTempData")
用來測試跨請求的 ReceiveTempData 視圖:
@{ ViewData["Title"] = "ReceiveTempData"; } <h2>ReceiveTempData</h2> <div>@TempData["error"]</div> @*將 TempData["greetng"] 生命期延長一次*@ <div>@TempData.Peek("greeting")</div> <br/>@Html.ActionLink("ReceiveTempData2", "ReceiveTempData2")
ReceiveTempData2 視圖:
@{ ViewData["Title"] = "ReceiveTempData2"; } <h2>ReceiveTempData2</h2> <div>@TempData["error"]</div> <div>@TempData["greeting"]</div>
另一個控制器 TempDataController:
1 public class TempDataController : Controller 2 { 3 public IActionResult Index() 4 { 5 return View(); 6 } 7 }
該控制器的 Index 視圖:
@{ ViewData["Title"] = "Index"; } <h2>Index</h2> <div>@TempData["error"]</div>
結果:

使用重定向:
1 public IActionResult TempDataTest() 2 { 3 TempData["error"] = "An error"; 4 TempData["greeting"] = "Hello"; 5 TempData.Keep("error"); 6 //return View(); 7 return RedirectToAction("ReceiveTempData"); 8 }
結果:


本應被延長的 TempData["error"] 沒有被延長。
Session
session 是從Web.SessionState 繼承的鍵值對對象。在控制器之間傳遞數據,可用於跨請求狀態下控制器和視圖之間的數據傳遞。一般Session用於保留對特定用戶的數據,但最好不要在其中放置敏感數據。生存期一直持續到 timeout 參數所限制的事件、調用Clear、RemoveAll、Abandon 方法或者關閉瀏覽器來明確地銷毀它。最好減少 Session 的使用因為它在服務器集群環境下不可靠,並且在使用時將一直占用服務器資源。
在使用 Session 時需要在 Startup.cs 中對其進行配置(配置詳情)。
使用方法:
在此我們將 Session 的過期時間設置為 5 秒,該過期時間在每次操作之后進行計算。
在 ConfigureServices 方法中:
1 services.AddDistributedMemoryCache(); 2 3 services.AddSession(options => 4 { 5 options.IdleTimeout = TimeSpan.FromSeconds(5);
6 options.Cookie.HttpOnly = true;
7 });
在 Configure 方法中:
1 app.UseSession(); 2 app.UseMvc();
中間件的順序很重要。 在 UseMvc 之后調用 UseSession 時會發生 InvalidOperationException 異常。
在默認的 ASP.NET Core 實現中,在 Session 中提供了字節流、int 以及 string 數據的獲取和設置方法,分別為:
- Get(ISession, String)
- GetInt32(ISession, String)
- GetString(ISession, String)
- SetInt32(ISession, String, Int32)
- SetString(ISession, String, String)
為了方便,分別實現一個 Set 和 Get 的 ISession 泛型擴展方法:
1 public static void Set<T>(this ISession session, string key, T value) 2 { 3 session.SetString(key, JsonConvert.SerializeObject(value)); 4 } 5 6 public static T Get<T>(this ISession session, string key) 7 { 8 var value = session.GetString(key); 9 10 return value == null ? default(T) : 11 JsonConvert.DeserializeObject<T>(value); 12 }
控制器中:
1 //用來獲取和設置值的鍵 2 public const string SessionKeyStudent = "_Student"; 3 4 public string AddSession() 5 { 6 if (HttpContext.Session.Get<Student>(SessionKeyStudent)==default(Student)) 7 { 8 Student student=new Student() 9 { 10 Birth = new DateTime(1996,1,1), 11 ID = 2, 12 Name = "Ruri" 13 }; 14 HttpContext.Session.Set<Student>(SessionKeyStudent,student); 15 } 16 return "Session has been set"; 17 } 18 19 public IActionResult SessionTest() 20 { 21 Student student = HttpContext.Session.Get<Student>(SessionKeyStudent) ?? new Student(); 22 return View(student); 23 }
SessionTest 視圖:
@model Student @{ ViewData["Title"] = "SessionTest"; } <h2>SessionTest2</h2> <div>ID: @Model.ID</div> <div>Name: @Model.Name</div> <div>Birth: @Model.Birth</div>
結果:
直接訪問 sessiontest :

設置 Session 然后再訪問 SessionTest2 :


未操作 5s 之后刷新頁面,Session 被移除:

總結表格:

示例代碼:https://github.com/NanaseRuri/Differences
參考網頁: https://www.mytecbits.com/microsoft/dot-net/viewmodel-viewdata-viewbag-tempdata-mvc
https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/app-state?view=aspnetcore-2.1
