且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

Asp.net MVC - 使用PRG模式(附源码)

更新时间:2022-09-14 14:30:01

阅读目录:

一、 传统的Asp.net页面问题

二、Asp.net MVC中也存在同样的问题

三、使用PRG模式

四、PRG模式在MVC上的实现

 

一. 传统的Asp.net页面问题

一个传统的Asp.net页面的请求会是这样的:
    HTTP GET 请求"Register.aspx"
    HTTP POST 请求 "Register.aspx"(点击按钮等触发服务器端事件)
    数据检验失败, 重新返回到"Register.aspx"
    在HTTP POST到"Register.aspx"
    数据创建成功, 重新返回到"Register.aspx",提示创建成功
 
看看好像没有什么问题呀, 但是如果在标记为红色的这步之后,你在浏览器上点击"刷新"按钮, 就会弹出下面的对话框。

Asp.net MVC - 使用PRG模式(附源码)
 
这个对话框的意思是, 为了显示你点击"刷新"按钮的页面, 浏览器需要发送你上次提交的数据到服务器端, 之所以会这样的原因是浏览器记录的是上次你的Post请求, 所以你点击"刷新"按钮, 也是重复执行一次Post请求, 而用户其实是想得到初始的页面,也就是GET请求"Register.aspx"页面. 对于大多数不清楚原理的普通用户来说,这样的对话框会让用户会非常困扰.
 
web系统应当是以URL为标记的资源, 一个URL***代表的一种资源. 当你收藏一个网页,分享一个网页给你朋友的时候, 你用的是网页的URL, 那是因为网页的URL就对应了你想分享的资源.
所以上面方式带来的另外一个问题就是, Get, POST, 以及POST成功后的页面实际上代表了3中不同的资源,但是这三种资源的URL是同一个URL.
 

二. Asp.net MVC中也存在同样的问题

假如我们在完成一个注册页面, Controller中的代码是这样的:
 

Asp.net MVC - 使用PRG模式(附源码)
 //
        // GET: /Home/
        [HttpGet]
        public ActionResult Register()
        {
            return View();
        }
        [HttpPost]
        public ActionResult Register(Models.RegisterModel registerModel)
        {
            return View();
        }
Asp.net MVC - 使用PRG模式(附源码)

View中的代码是:

Asp.net MVC - 使用PRG模式(附源码)
@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>
}
Asp.net MVC - 使用PRG模式(附源码)

运行以后,当你提交表单的时候,你会发现出现了同样的问题.
 Asp.net MVC - 使用PRG模式(附源码)

三. 使用PRG模式

PRG模式是Post/Redirect/Get的简称.
当一个Post请求过来的时候, 服务端会处理Post请求后,再发送Redirect(HTTP 303状态码)到浏览器,浏览器之后再发送Get请求到其它页面.
这样做, 浏览器的上一个操作就总是Http Get操作, 而不是Post操作, 也就解决了刷新弹出框的问题.

Asp.net MVC - 使用PRG模式(附源码)
 

四. PRG模式在MVC上的实现

针对上面的例子,我们的修改思路是:
创建3个不同的Action对应, Post请求到"RegisterProcess"之后,无论成功还是失败, 都会转换成Get请求, 成功转向"RegisterSuccess", 失败转向"Register"
 Asp.net MVC - 使用PRG模式(附源码)
 
修改之后的Controller代码如下:

Asp.net MVC - 使用PRG模式(附源码)
 //
        // 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();
        }
Asp.net MVC - 使用PRG模式(附源码)

上面的ImportModelStateFromTempData和ExportModelStateToTempData是ActionFilter, 是为了解决Redirect不能保存Model的验证错误的问题.
实现的基本原理是通过ExportModelStateToTempData把Model的验证错误存放到TempData中, 通过ImportModelStateFromTempData从TempData中把验证错误导入.


View代码是:

Asp.net MVC - 使用PRG模式(附源码)
@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>
}
Asp.net MVC - 使用PRG模式(附源码)


ImportModelStateFromTempData和ExportModelStateToTempData的实现代码如下:

Asp.net MVC - 使用PRG模式(附源码)
 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);
        }
    }
Asp.net MVC - 使用PRG模式(附源码)

 最后,附上本文相关源代码 MVCFormValiation.zip



本文转自JustRun博客园博客,原文链接:http://www.cnblogs.com/JustRun1983/archive/2013/04/18/3027861.html,如需转载请自行联系原作者