﻿# ASP.NET MVC 開發心得分享 (28)：深入了解 ModelState 內部細節

在 ASP.NET MVC 的 **模型繫結** (Model Binding) 完成之後，我們可以在 Controller / Action 中取得 ModelState 物件，一般來說我們都會用 **ModelState.IsValid** 來檢查在「模型繫結」的過程中所做的 **輸入驗證** (Input Validation) 與 **模型驗證** (Model Validation) 是否成功。不過，這個 ModelState 物件的用途很廣，裡面存有非常多模型繫結過程的狀態資訊，不但在 Controller 中能用，在 View 裡面也能使用，用的好的話，可以讓你的 Controller 更輕、View 也更乾淨，本篇文章將分享幾個 ModelState 的使用技巧。

本篇文章的範例程式我已經發佈到 GitHub，請從以下網址取得程式原始碼：  
[**https://github.com/doggy8088/UnderstandingModelState**](https://github.com/doggy8088/UnderstandingModelState "https://github.com/doggy8088/UnderstandingModelState")

我的這個範例程式非常簡單，在預設的 ASP.NET MVC 5 專案範本建立好之後，我只建立了一組 **/Home/Login** 頁面，其 Action 程式碼如下圖示：

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/675f373572168491f09a-image_2873.png)

至於 View 的部分，我也是只是透過 Add View (新增檢視) 建立起一個預設的檢視頁面而已：

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/c7cca49b4664abee01cc-image_2874.png)

這個頁面的輸出畫面如下圖示：

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/efb93e490e806383386d-image_2875.png)

首先，我們就先來看看 ModelState 最常見的用法：**ModelState.IsValid**

以下這段程式碼範例，是一段真實可行的程式碼，先透過 **ModelState.IsValid** 檢查是否必要的欄位都有輸入，然後透過 **LoginCheck** 檢查帳號密碼是否正確。

只要登入驗證成功，就會寫入一個 FormsAuthentication 的 Cookie，並透過 HTTP 302 轉向到首頁；如果登入失敗，就會繼續顯示這個 Login 表單頁面，並且傳入本次模型繫結傳入的 login 物件 ( LoginViewModel 型別 )：

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/cac8e71881a5b30ea267-image_2876.png)

接著我們嘗試在表單執行登入，並且輸入一個錯誤的帳號、密碼，帳號我輸入 **test@example.com** 密碼則是 **test** (錯誤密碼)。

這時你會發現頁面上會再次出現上一頁所輸入的 Email 與 Password 欄位資料，原因是我們在 Login Action 有在傳入一次 login 物件到 View 裡面，所以自動將資料繫結到 View 裡面進去了：

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/58fa8852d04da96b0295-image_2877.png)

但目前我們的程式有點問題，就是登入失敗的時候並沒有顯示錯誤訊息，這樣對使用者會有點困擾。這時我們可以透過 ModelState.AddModelError 來新增一個自訂錯誤訊息：

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/7425241905a5f1fd5051-image_2878.png)

這時我們重新送出表單資料，你會發現錯誤訊息會正好顯示在 "密碼" 欄位下方，也就是 View 裡面 **@Html.ValidationMessageFor(model => model.Password, "", new { @class = "text-danger" })** 的地方。

換句話說，這個 **@Html.ValidationMessageFor** 會從 **ModelState** 中取得錯誤訊息內容，並顯示出來。如果你在 Model 中有實作 **輸入驗證** (Input Validation) 與 **模型驗證** (Model Validation) 的話，錯誤訊息最終也是會寫到 ModelState 物件裡面，所以我們可以透過控制 ModelState 的內容來控制 View 的顯示邏輯。

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/95c5a662d71c2b960331-image_2879.png)

接下來我們把第 52 行準備傳給 View 的 Model 也給拿掉 ( 將 login 物件從參數列中移除 )，並對專案重新建置。

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/2307ad8c7cdd269e59cc-image_2880.png)

我們再重新 POST 登入表單，這時你會發現 "密碼" 欄位的內容消失了，這點應該很合理，因為我們根本沒從 Action 傳資料給 View，沒顯示是正常的吧！

不正常的地方應該在 "電子郵件" ( Email ) 這個欄位吧，明明沒傳入資料，為什麼還會顯示上一頁輸入的內容呢？聰明的你，應該已經想到了，這個欄位資料就是從 **ModelState** 取得的，所以你應該會慢慢了解到 **ModelState** 的重要性！

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/0b1c1166ba698c978f81-image_2881.png)

所以你現在應該已經知道，原來在 **ModelState** 裡面，不單單只有「**驗證失敗的錯誤訊息**」，其實還有「**模型繫結過程中得到的資料**」啊！

假設我們想要清除所有已經儲存在 **ModelState** 裡面的內容，你可以用 **ModelState.Clear();** 來清除 (包含**錯誤訊息**與**模型繫結的資料** 都會被清空)，如下圖示：

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/c04d8d5e1d7237f3eddb-image_2882.png)

由於我們在執行**模型繫結**的過程中會有許多欄位被繫結進來，如果你只想清除特定一個欄位的內容 (包含**錯誤訊息**與**模型繫結的資料**)，可以使用 **ModelState.Remove("Email");** 來清除。

※ 請注意：這個方法會同時清除 **ModelState\["Email"\]** 這筆模型狀態的**錯誤訊息**與**模型繫結的資料**喔！

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/90c281dcff4c1cf724d2-image_2883.png)

如果你只想刪除**模型繫結的資料**並且想保留**錯誤訊息** (該欄位的驗證結果) 的部分，你還可以使用以下程式碼片段來重新指定新的**模型繫結**資料：

> if (!ModelState.IsValidField("Password"))  
> {  
>     var emptyValue = new ValueProviderResult(  
>         string.Empty,  
>         string.Empty,  
>         System.Globalization.CultureInfo.CurrentCulture);
> 
>     ModelState.SetModelValue(  
>         "Password",  
>         emptyValue);  
> }

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/89bc5e3fe9c5adb29ca3-image_2884.png)

最後，如果你想要更細部的了解 ModelState 的內部結構，可以透過下圖了解到存取每個欄位的 ModelState 內容的方法：

由下圖的 foreach 迴圈可以知道，ModelState 可以透過枚舉的方式取得每一個欄位的 ModelState 內容，然後可以用 ModelState.IsValidField 查出該欄位是否有驗證錯誤的狀態，然後可以再透過一個 foreach 迴圈取得所有該欄位 ModelState 的所有錯誤 (因為可能不只一個錯誤)，最後在取得 err.ErrorMessage 得知錯誤訊息的確切內容或 err.Exception 取得該模型的例外狀況物件 (如果有的話)。

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/6514f971309a03b4d89b-image_2885.png)

如果你也想透過這種 "弱型別" 的方式取得每個欄位的值 (透過 Model Binding 得到的值)，也可以透過以下徒的方式來取得 **RawValue** (透過 Model Binder 轉型後的值) 或 **AttemptedValue** (將值轉換成可顯示的字串值) 屬性內容。

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/e11a2db98f9f3b297f89-image_2886.png)

講到這裡，你應該對 ModelState 遊刃有餘了，本篇文章的最後一個技巧，則是如何從 View 中取得 ModelState 的內容。

如下圖示，你只要從 View 中透過 **ViewData.ModelState** 就可以取得所有 ModelState 的相關資訊。

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/cb445ca997ab3816641f-image_2887.png)

各位不要小看這個技巧，因為有時候你會想從 View 取得特定 Model Binding 的資料 (例如分頁資訊)，但這個 Model Binding 的資料並沒有從 Controller 中透過強型別的 Model 傳給 View 使用 ( ViewData.Model )，以往我們都要特別在用一個 ViewBag 來將這些參數傳給 View 使用，但這些 Code 在 Controller 中就會覺得有點多餘。知道了這個技巧，就可以在 Controller 少幾行 Code，而直接從 View 裡面取得 ModelState 的內容即可 (也就是取得本次 Model Binding 過程中的值)。

本文章所有操作步驟的原始碼，都已經做好 Git 版控，每個版本之間的差異都可以從 GitHub 上的 **[UnderstandingModelState](https://github.com/doggy8088/UnderstandingModelState)** 專案查看每個版本的[**修改歷史**](https://github.com/doggy8088/UnderstandingModelState/commits/master)。

**相關連結**

-   [ModelState 類別 (System.Web.Mvc)](https://msdn.microsoft.com/zh-tw/library/system.web.mvc.modelstate\(v=vs.118\).aspx)
-   [What is the ModelState? - ASP.NET MVC Demystified](http://www.exceptionnotfound.net/asp-net-mvc-demystified-modelstate/)
-   [How to get all Errors from ASP.Net MVC modelState? - Stack Overflow](http://stackoverflow.com/questions/1352948/how-to-get-all-errors-from-asp-net-mvc-modelstate) (第二個答案還不錯，用 LINQ 取得錯誤訊息)
