ASP.NET MVC Tips #2 - 令人混乱的Get、Post、Return View和Return Redirect

今天说说一个让我,这个很久没有接触Web开发的人混乱了很久的问题,就是什么时候应该使用Http.Get、Http.Post和返回视图的时候什么时候用return View,什么时候要return Redirect。首先声明一下,本人对于Web编程并不精通,下面的一些解释和看法可能有错误,希望各位看到了的话回复一下,纠正本人的错误同时也别误导了其他人。

好了先说一下这个Http.Get和Http.Post。在一个Controller Action方法上面我们可以通过Attribute声明这个Action的一些属性,比如Authorize属性表示这个方法需要登录(权限)验证,ActionName属性可以重定义这个Action的名字(默认是方法名)。还有一个Attribute标识了这个Action可以接受哪种Http Verb的请求,包括Http.Get、Http.Post、Http.Put、Http.Delete和Http.Head。其中最经常使用到的就是Http.Get和Http.Post两个值。

Http.Get一般来说(基本可以把这个定语去掉)是在请求数据的时候使用的,也就是说这个Action操作只会读取数据并显示到页面上,而不会对数据进行任何修改。他的最大特点就是,用户可以通过指向这个Action的URL就可以直接访问到数据,有点RESTful的味道。对应到我们的ASP.NET MVC 1(以下简称MVC)的项目中,Http.Get用于显示内容的Action上面。比如网站的首页一般都是只需要读取数据并显示的,所以Home/Index的Action都会是Http.Get的。

Http.Post则不同,它是通过页面的Form(表单)Post之后所调用的Action。(注:当然可以通过Form的Method参数指定使用Get或者Post Verb提交。)一般来说,Post之后的页面将会对后台数据进行修改,不论是添加、删除或是修改。相对于Get,Post方法是无法直接通过浏览器的URL进行访问的,只有通过特定的网页进行提交才能执行,所以相对于Get,Post的安全性也稍微高些。(注:仍旧可以通过Mock表单来进行伪造的提交,但是MVC为我们提供了一个防范措施。)对应到MVC的项目,对于数据的修改操作则都会通过Post Action进行操作。

在新建一个MVC项目之后,AccountController中就有相似的操作。

        public ActionResult Register()
        {

            ViewData["PasswordLength"] = MembershipService.MinPasswordLength;

            return View();
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Register(string userName, string email, string password, string confirmPassword)
        {

            ViewData["PasswordLength"] = MembershipService.MinPasswordLength;

            if (ValidateRegistration(userName, email, password, confirmPassword))
            {
                // Attempt to register the user
                MembershipCreateStatus createStatus = MembershipService.CreateUser(userName, password, email);

                if (createStatus == MembershipCreateStatus.Success)
                {
                    FormsAuth.SignIn(userName, false /* createPersistentCookie */);
                    return RedirectToAction("Index", "Home");
                }
                else
                {
                    ModelState.AddModelError("_FORM", ErrorCodeToString(createStatus));
                }
            }

            // If we got this far, something failed, redisplay form
            return View();
        }

上面的代码显示了“注册”逻辑。第一个Action没有显式声明AcceptVerb则表示它接受Http.Get的请求,只是简单的显示一个页面。而第二个Action则声明了Http.Post属性,它会在用户点击了注册页面的Submit按钮提交表单后运行,检查数据正确后将新的用户插入数据库(如果后台使用数据库保存数据的话)然后返回首页(Homt/Index)。

所以对于Http.Get和Http.Post,可以简单的认为,如果这个Action有修改数据的操作则要使用Http.Post,否则就可以使用Http.Get。

在第二个Register函数中,可以看到使用了两种不用的方法来返回视图(View)。一个是return RedirectToAction,注册成功之后返回首页的。另一个是末尾的return View,在注册失败(可能是检查未通过,也可能是添加数据的时候出错)的时候返回当前的注册页面并显示错误信息的。那么问题是,这两种返回试图的方法究竟应该在什么时候调用呢?

对于return View,MVC框架不会发起一个新的Http请求(这一点有待考证),所以在Http.Get Action中使用。而且,在Post方法中当数据修改失败需要返回当前页面并显示错误信息的时候也会使用,这样视图表单中数据的数据将会保留,而且View中的ModelState信息等也都会保留。而对于return Redirect,无论是直接Redirect方法还是RedirectToAction方法,都会重新发起一个Http请求并且清楚所有的表单信息和ModelState信息,一般都是在Post方法中修改数据成功后返回列表页面(或者结果页面)的时候使用。比如上面这个Action,注册成功之后将会通过一个新的Http请求执行Home Contrller的Index Action来显示首页。

而且由于return View方法没有发起新的Http请求,如果在Post Action中返回了别的视图,则有可能造成URL和View不匹配的情况。比如我们将上面的return RedirectToAction方法修改为return View("LogOn")来直接返回登录页面,则会看到如下的情形。URL显示的是Account/Register但是现实的页面却是LogOn,这一点和MVC中“一个URL对应唯一的页面”也是不相符合的。在这种情况下,如果用户复制了URL并且发送给别的人,那么他在打开这个URL的时候所看到的页面并不是你现在所看到的。这也损失了MVC的一大优点,就是可以通过URL将你所见到的页面分发给别人。Capture1

而且,如果在这种情形下按了浏览器的刷新按钮(F5),你还会发现浏览器给你提示了一个消息。首先说明一下当按了刷新按钮之后发生了什么。由于我们在Post Action中直接使用return View返回试图,浏览器不会认为这是一个新的Http请求,所提交的表单信息都存在于Http请求里面,所以当刷新本页面的时候,浏览器将会把表单信息再次提交给服务器,实质上是出现了重复提交。而这个消息框是浏览器发现相同的表单被重复请求了两次而特别加上的确认信息,用来防止重复提交的。这个技术不是TCP/IP协议或者HTTP提供的,而是现代浏览器提供的。这种现象也是我们在做MVC的时候应该避免的,而根源就在于使用了错误视图返回方式。正确的做法是通过return Redirect重定向到结果页面,或者显示结果的Action上,然后交由这个页面或Action进行视图展示。这种方法,一些人称之为Web开发的PRG模式(Post – Redirect - Get)。

image

总结一下,Http.Get Verb用于只读取数据并显示的Action上;Http.Post Verb则用于需要对数据库进行修改的Action上。return View方法不会发起新的Http请求,所以只是用户Get Action中用来返回结果,或者Post Action中返回当前页面并显示错误信息。而return Redirect方法将会重定向到一个URL或者Action上面并且会发起新的Http请求,所以只会用于Post Action中数据修改完毕返回结果页面时。

在实际开发中,可能会遇到Get Action显示的页面有一些逻辑,需要从数据库取,然后通过ViewModel传递给视图显示的情况。不像上面例子里面那么简单的LogOn Action。在对应Post方法失败返回当前页的时候,简单使用return View就不行了,因为这个View是需要一些ViewModel支持的。一个方法是在Post Action里面,return View之前进行和Get方法一样的数据读取操作,然后通过return View(model)来显示视图。或者,可以直接调用Get Action这个函数(注意是调用函数而不是调用Action),这样执行Action函数的数据读取操作然后return View(model)。

        [AcceptVerbs(HttpVerbs.Get)]
        public ActionResult MyAction()
        {
            object viewModel = new object();
            return View(viewModel);
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult MyAction(object args)
        {
            var validationResult = false;
            // some validation code
            // ...
            if (validationResult)
            {
                // update the data
                return RedirectToAction("Index");
            }
            else
            {
                return MyAction();
            }
        }
posted @ 2009-09-04 11:46 妖居 阅读(...) 评论(...) 编辑 收藏