生成输出(Producing Output)
在controller完成处理请求之后,通常需要生成一个响应。当我们通过直接实现IController接口创建一个简单的controller时,我们需要对处理请求的每一个方面负责,包括创建对客户端的响应。如果我们想发送一个HTML响应,那我们必须创建并且集合HTML数据,然后使用Response.Write方法将数据发送到客户端。类似地,如果我们想重定向用户到其他的URL,可以使用Response.Redirect方法。
下面是具体的实例:
using System.Web.Mvc;
using System.Web.Routing;
namespace ControllersAndActions.Controllers
{
public class BasicController : IController
{
public void Execute(RequestContext requestContext)
{
string controller = (string)requestContext.RouteData.Values["controller"];
string action = (string)requestContext.RouteData.Values["action"];
requestContext.HttpContext.Response.Write(string.Format("Controller:{0},Action:{1}", controller, action));
//..or..
requestContext.HttpContext.Response.Redirect("/Some/Other/Url");
}
}
}
当然在DerivedController里面也可以使用Response属性,但是这样使用会带来一些问题:
①controller类必须包含HTML或URL架构的详细信息,这会让维护和可读性非常差
②对于这种直接响应输出的controller进行单元测试也很困难
③处理好每一个这种方法响应的细节是非常乏味和易出错的
幸运的是,MVC框架有一个解决这些问题非常好的功能——Action Results。下面会介绍Action Result的概念并展示它用来生成controller的响应的不同方式:
理解Action Results
MVC框架使用Action Results将"说明我们意图(stating our intentions)"和"执行我们的意图(executing our intentions)"分开(我也不是很明白这两个概念,如果路过的朋友清楚,请留言指正,谢谢)
不直接使用Response对象,而是返回一个从ActionResult类派生的对象,用这个对象来描述我们想要controller响应什么,比如呈现一个视图或重定向到另外的URL,再或者action方法等等。ps:MVC里面的Action Result系统采用了设计模式里面的命令模式
MVC框架接收从action方法返回的ActionResult对象,并调用定义在ActionResult类里面ExecuteResult方法,接着,对ActionResult(这是一个抽象类)的实现为我们处理Response对象并生成符合我们意图的输出。下面是一段关于RedirectResult类的源码:
public class RedirectResult : ActionResult {
[SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#", Justification = "Response.Redirect() takes its URI as a string parameter.")]
public RedirectResult(string url)
: this(url, permanent: false) {
}
[SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#", Justification = "Response.Redirect() takes its URI as a string parameter.")]
public RedirectResult(string url, bool permanent) {
if (String.IsNullOrEmpty(url)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "url");
}
Permanent = permanent;
Url = url;
}
public bool Permanent {
get;
private set;
}
[SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Response.Redirect() takes its URI as a string parameter.")]
public string Url {
get;
private set;
}
public override void ExecuteResult(ControllerContext context) {
if (context == null) {
throw new ArgumentNullException("context");
}
if (context.IsChildAction) {
throw new InvalidOperationException(MvcResources.RedirectAction_CannotRedirectInChildAction);
}
string destinationUrl = UrlHelper.GenerateContentUrl(Url, context.HttpContext);
context.Controller.TempData.Keep();
if (Permanent) {
context.HttpContext.Response.RedirectPermanent(destinationUrl, endResponse: false);
}
else {
context.HttpContext.Response.Redirect(destinationUrl, endResponse: false);
}
}
}
当我们创建一个RedirectResult类的实例,我们传入了一个想要跳转的URL参数,还可以传入一个可选的参数:是否永久重定向(默认值是false)。ExecuteResult将会在Action方法执行完时被MVC调用,通过ControllerContext对象获取用于查询的Response对象,然后调用RedirectPermanent或Redirect方法。
我们在DerivedController类里面添加一个方法如下:
public class DerivedController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Hello from DerivedController Index Method";
return View("Index");
}
public ActionResult Redirect()
{
return new RedirectResult("/Derived/Index");
}
}
这时运行程序输入/Derived/Redirect会跳转到Index页面。当然可以简便的写成return Redirect("/Derived/Redirect")。
此外,MVC框架内置了许多action result类型,都是从ActionResult类派生的,这样能够方便我们在action方法里面选择具体的返回类型,比如我们要呈现到View,可以选择ViewResult作为action方法的返回值。更多关于从ActionResult派生的类型请参考这里。下面会展示怎样使用这些返回值类型以及怎样创建和使用自定义的ActionResult。
通过呈现View返回HTML
最常见的来自action方法的响应就是生成HTML并发送给浏览器。当使用了ActionResult这套体系,为了生成HTML,我们要创建一个指定了要呈现视图的ViewResult类的实例。如下所示:例子指定了Homepage的视图
public class ExampleController : Controller
{
public ViewResult Index()
{
return View("Homepage");
}
}
ps:我们可以显示的创建ViewResult对象(return new ViewResult { ViewName = "Homepage" };)这是一种完美的,可接受的方式。但是我们偏向于使用Controller类提供的的帮助方法来简化代码。
当MVC框架调用ViewResult对象的ExecuteResult时,就会开始对我们指定的View进行搜索,如果我们使用了Area,则搜索的顺序如下:
• /Areas/<AreaName>/Views/<ControllerName>/<ViewName>.aspx
• /Areas/<AreaName>/Views/<ControllerName>/<ViewName>.ascx
• /Areas/<AreaName>/Views/Shared/<ViewName>.aspx
• /Areas/<AreaName>/Views/Shared/<ViewName>.ascx
• /Areas/<AreaName>/Views/<ControllerName>/<ViewName>.cshtml
• /Areas/<AreaName>/Views/<ControllerName>/<ViewName>.vbhtml
• /Areas/<AreaName>/Views/Shared/<ViewName>.cshtml
• /Areas/<AreaName>/Views/Shared/<ViewName>.vbhtml
只要有一个View找到,整个搜索就会停止,并将找的View呈现给客户端。如果没有用Area,则没有前面的/Areas/<AreaName>.
ps:MVC按照这种顺序搜索Viiew也体现了"约定胜于配置(convention over configuration)"的思想
下面可以进行如下单元测试:
[TestMethod]
public void ViewSelectionTest()
{
// Arrange -创建一个Controller
ExampleController target = new ExampleController();
// Act - 调用Action方法
ActionResult result = target.Index();
// Assert -检查结果
Assert.AreEqual("Homepage", result.ViewName);
}
//如果测试一个Action方法选择的是默认的View,如下所示:
public ViewResult Index()
{
return View();
}
//这时可以做这样的判断
Assert.AreEqual("", result.ViewName);
当我们调用View方法不指定具体的视图的名字时,会呈现默认的View,这个默认的View名就是该Action方法名,如public ViewResult Index(){return View();},默认的视图就是Index。MVC就会按照约定的顺序搜寻Index视图,搜寻哪一个视图是由RouteData.Values["action"]的值决定的。View方法很多重载,大家可以自己看看智能提示。
通过路径来指定呈现的View
这种命名约定的方法非常方便和简捷的,但是它限制了我们所能呈现的一些View。如果我们要呈现一个具体的view,可以提供一个显示的路径来绕开搜索的阶段。下面是一个这样的例子:
using System.Web.Mvc;
namespace ControllersAndActions.Controllers
{
public class ExampleController : Controller
{
public ViewResult Index() {
return View("~/Views/Other/Index.cshtml");
}
}
}
当我们像这样指定一个view时,指定的路径必须以"/"或"~/"并且包含扩展名(如.cshtml)。当然我推荐这样来使用,因为我们可以有其他的方法来达到同样的效果。
从Action方法向View传递数据
我们常常需要从action方法向view传递数据,MVC框架提供了多种方式来实现。
1.提供一个View Model对象
我们能够将一个对象作为View方法的参数传递给view,例如:
//Controller部分
namespace ControllersAndActions.Controllers
{
public class ExampleController : Controller
{
public ViewResult Index()
{
DateTime date = DateTime.Now;
return View(date);
}
}
}
//View部分
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
The day is: @(((DateTime)Model).DayOfWeek)
上面的View是一个没有类型或者说若类型的view。这个view不知道关于view model对象的任何信息,并且将它作为了object对象的实例进行处理。为了得到DayofWeek属性的值,我们需要将object对象的实例强转为DateTime.这样做能够实现我们效果,但是却让view变得凌乱。
我们可以通过创建强类型的view来改进,即在view里面指定view model对象的类型,只需要添加一句代码:@model Datetime,如下所示:
@model DateTime
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
The day is: @Model.DayOfWeek
可以发现,指定view model的类型通过@model关键字(注意这里的"m"是小写),当我们读取view model对象属性值时,使用@Model关键字(这里的"M"是大写哦),使用强类型的视图不仅让我们的view变得整洁,而且方便我们编码,因为会对属性的智能提示。
下面进行单元测试:
[TestMethod]
public void ViewSelectionTest()
{
// Arrange - create the controller
ExampleController target = new ExampleController();
// Act - call the action method
ActionResult result = target.Index();
// Assert - check the result
Assert.AreEqual("Hello, World", result.ViewData.Model);
}
使用ViewBag传递数据
在前面就已经使用过ViewBag,这个功能让我们能够随意地定义一个动态对象的属性,并能够在view里面访问,如下所示:
//Controller部分
public ViewResult Index() {
ViewBag.Message = "Hello";
ViewBag.Date = DateTime.Now;
return View();
}
//View部分
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
The day is: @ViewBag.Date.DayOfWeek
<p />
The message is: @ViewBag.Message
相对于使用view model对象,ViewBag能够很容易地发送多个对象到view。如果我们被限制只能使用view models,那么为实现相同的效果,我们需要创建一个新的类型具有string和DateTime两个类型的成员。使用动态对象,我们可以输入在视图中调用的方法和属性序列。
像这样的:The day is: @ViewBag.Date.DayOfWeek.Blah.Blah。VS不会为动态对象提供任何智能提示包括ViewBag,所以如果有什么错误,只有等到view呈现才知道。当然我们完全可以同时使用view model对象和ViewBag,各尽其用。
使用ViewData传递数据
ViewData是在MVC3之前的版本中出现的,主要的功能类似于ViewBag,但ViewData是使用ViewDataDictionary类实现的而不是一个动态类型,ViewDataDictionary类像一个常规的键/值对的集合,并通过Controller类的ViewData属性访问。如下所示:
//Controller部分
public ViewResult Index()
{
ViewData["Message"] = "Hello";
ViewData["Date"] = DateTime.Now;
return View();
}
//View部分
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
The day is: @(((DateTime)ViewData["Date"]).DayOfWeek)
<p />
The message is: @ViewData["Message"]
ps:我们能看到需要对object对象进行类型转换,现在有了ViewBag以后,推荐使用ViewBag,但是尽量使用强类型view和view models是非常明智的。
执行重定向(Performing Redirections)
一个来自action方法常见的结果不是直接生成任何输出,而是重定向用户到其他的URL。大多数时候,这个重定向的URL是应用程序里面另外一个action方法,用来生成我们想给用户看到的输出。
POST/REDIRECT/GET模式:最频繁使用的重定向是在处理POST请求的action方法里面,正如我们前面提到的,POST请求是要改变应用程序状态的,如果我们处理一个请求后接着仅仅是返回HTML,我们就冒了用户第二次点击重载按钮和重提交按钮所导致的不可预期的异常的风险。为了避免这个问题,我们可以遵循POST/Redirect/GET模式。在这个模式中,接收一个请求,处理它并重定向到浏览器以至于浏览器为其他的URL制造一个GET请求。GET请求不应该修改我们应用程序的状态,所以任何无意的关于请求的重提交不会引起任何问题。
当我们执行一个重定向,我们就发送了两个HTTP编码中的一个到浏览器:
①发送HTTP 302状态编码,代表暂时重定向。这是最频繁使用的重定向类型,MVC3里面也是这样,当我们遵循Post/Redirect/Get模式时,这时发送的代码就是我们想要的。
②发生HTTP 301状态编码,表示永久重定向。这要慎重使用,因为它指示HTTP编码的接收者不会再次请求原始的URL并使用包含了重定向编码的新URL。如果你有疑虑,那么最好使用暂时重定向,发送302编码。
不早了,今天的笔记就到这里!笔记里面不对的地方还请路过的朋友指正,谢谢!
晚安