閱讀目錄:html
1、 傳統的Asp.net頁面問題web
2、Asp.net MVC中也存在一樣的問題瀏覽器
3、使用PRG模式服務器
4、PRG模式在MVC上的實現ide
一個傳統的Asp.net頁面的請求會是這樣的:
HTTP GET 請求"Register.aspx"
HTTP POST 請求 "Register.aspx"(點擊按鈕等觸發服務器端事件)
數據檢驗失敗, 從新返回到"Register.aspx"
在HTTP POST到"Register.aspx"
數據建立成功, 從新返回到"Register.aspx",提示建立成功
看看好像沒有什麼問題呀, 可是若是在標記爲紅色的這步以後,你在瀏覽器上點擊"刷新"按鈕, 就會彈出下面的對話框。post
這個對話框的意思是, 爲了顯示你點擊"刷新"按鈕的頁面, 瀏覽器須要發送你上次提交的數據到服務器端, 之因此會這樣的緣由是瀏覽器記錄的是上次你的Post請求, 因此你點擊"刷新"按鈕, 也是重複執行一次Post請求, 而用戶實際上是想獲得初始的頁面,也就是GET請求"Register.aspx"頁面. 對於大多數不清楚原理的普通用戶來講,這樣的對話框會讓用戶會很是困擾.
web系統應當是以URL爲標記的資源, 一個URL最好表明的一種資源. 當你收藏一個網頁,分享一個網頁給你朋友的時候, 你用的是網頁的URL, 那是由於網頁的URL就對應了你想分享的資源.
因此上面方式帶來的另一個問題就是, Get, POST, 以及POST成功後的頁面實際上表明瞭3中不一樣的資源,可是這三種資源的URL是同一個URL.
url
假如咱們在完成一個註冊頁面, Controller中的代碼是這樣的:
spa
// // GET: /Home/ [HttpGet] public ActionResult Register() { return View(); } [HttpPost] public ActionResult Register(Models.RegisterModel registerModel) { return View(); }
View中的代碼是:.net
@using(Html.BeginForm()){ <fieldset> <legend>Register</legend> @Html.ValidationSummary(true) <ol> <li> @Html.LabelFor(m => m.NickName) @Html.TextBoxFor(m => m.NickName) @Html.ValidationMessageFor(m => m.NickName) </li> <li> @Html.LabelFor(m => m.Email) @Html.TextBoxFor(m => m.Email) @Html.ValidationMessageFor(m => m.Email) </li> </ol> <input type="submit" value="Sumbit" /> </fieldset> }
運行之後,當你提交表單的時候,你會發現出現了一樣的問題.
code
PRG模式是Post/Redirect/Get的簡稱.
當一個Post請求過來的時候, 服務端會處理Post請求後,再發送Redirect(HTTP 303狀態碼)到瀏覽器,瀏覽器以後再發送Get請求到其它頁面.
這樣作, 瀏覽器的上一個操做就老是Http Get操做, 而不是Post操做, 也就解決了刷新彈出框的問題.
針對上面的例子,咱們的修改思路是:
建立3個不一樣的Action對應, Post請求到"RegisterProcess"以後,不管成功仍是失敗, 都會轉換成Get請求, 成功轉向"RegisterSuccess", 失敗轉向"Register"
修改以後的Controller代碼以下:
// // GET: /Home/ [HttpGet, ImportModelStateFromTempData] public ActionResult Register() { return View(); } [HttpPost, ExportModelStateToTempData] public ActionResult RegisterProcess(Models.RegisterModel registerModel) { if (ModelState.IsValid) { return RedirectToAction("RegisterSuccess"); } return RedirectToAction("Register"); } [HttpGet] public ActionResult RegisterSuccess() { return View(); }
上面的ImportModelStateFromTempData和ExportModelStateToTempData是ActionFilter, 是爲了解決Redirect不能保存Model的驗證錯誤的問題.
實現的基本原理是經過ExportModelStateToTempData把Model的驗證錯誤存放到TempData中, 經過ImportModelStateFromTempData從TempData中把驗證錯誤導入.
View代碼是:
@using (Html.BeginForm("RegisterProcess", "Home")) { <fieldset> <legend>Register</legend> @Html.ValidationSummary(true) <ol> <li> @Html.LabelFor(m => m.NickName) @Html.TextBoxFor(m => m.NickName) @Html.ValidationMessageFor(m => m.NickName) </li> <li> @Html.LabelFor(m => m.Email) @Html.TextBoxFor(m => m.Email) @Html.ValidationMessageFor(m => m.Email) </li> </ol> <input type="submit" value="Sumbit" /> </fieldset> }
ImportModelStateFromTempData和ExportModelStateToTempData的實現代碼以下:
public abstract class ModelStateTempDataTransfer : ActionFilterAttribute { protected static readonly string Key = typeof(ModelStateTempDataTransfer).FullName; } public class ExportModelStateToTempData : ModelStateTempDataTransfer { public override void OnActionExecuted(ActionExecutedContext filterContext) { //Only export when ModelState is not valid if (!filterContext.Controller.ViewData.ModelState.IsValid) { //Export if we are redirecting if ((filterContext.Result is RedirectResult) || (filterContext.Result is RedirectToRouteResult)) { filterContext.Controller.TempData[Key] = filterContext.Controller.ViewData.ModelState; } } base.OnActionExecuted(filterContext); } } public class ImportModelStateFromTempData : ModelStateTempDataTransfer { public override void OnActionExecuted(ActionExecutedContext filterContext) { var modelState = filterContext.Controller.TempData[Key] as ModelStateDictionary; if (modelState != null) { //Only Import if we are viewing if (filterContext.Result is ViewResult) { filterContext.Controller.ViewData.ModelState.Merge(modelState); } else { //Otherwise remove it. filterContext.Controller.TempData.Remove(Key); } } base.OnActionExecuted(filterContext); } }
最後,附上本文相關源代碼 MVCFormValiation.zip