处理输出
controller完成处理请求后,通常需要生产响应。当我们直接实现 IController 接口创建controller时,我们就需要对处理请求的各个方面负责,包括对客户端的响应。如果我们需要生成HTML响应。比如,我们需要创建组合HTML数据,并使用使用 Response.Write方法传给客户端。相似的,如果我们想给用户浏览器返回另一个URL,我们需要调用Response.Redirect方法,直接传递URL。这些方法都在下面列出:
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");
}
}
}
当你从Controller类中继承了一个controller,你就使用相同的方法。当你通过Controller.Response属性,在Execute方法鲜中读取requestContext.HttpContext.Response属性时,你得到的返回值是HttpResponseBase类。如下:
using System.Web.Mvc;
namespace ControllersAndActions.Controllers {
public class DerivedController : Controller {
public void Index() {
string controller = (string)RouteData.Values["controller"];
string action = (string)RouteData.Values["action"];
Response.Write(
string.Format("Controller: {0}, Action: {1}", controller, action));
// ... or ...
Response.Redirect("/Some/Other/Url");
}
}
}
This approach works, but it has a few problems:
这个方法也是可行的,但是有几个小问题:
controller类必须包含HTML或者URL结构的详细信息。这就让类的可读性和可维护性变差。
对controller单元测试变的困难,你需要创建模拟的Response信息,使之能从controller接受并处理输出,这意味着要转换HTML关键字,这都是很痛苦的过程。
对每个请求都用这种方法处理非常乏味,而且容易出错。一些程序员喜欢绝对控制,喜欢创建一个未处理过的controller,但是大多数程序员对这种方法很不习惯。
幸运的是,MVC Framework能很好的处理这些问题,这个功能叫做action results。下面会介绍action result的概念,并展示几种不同的从controller生成响应的方法。
理解Action Results
MVC并不直接和Response对象交互,而是返回一个ActionResult类的子类,以此描述我们想从controller中得到的响应,比如呈现视图或者跳转到URL或者另一个action方法。
注意,action result系统只是command模式的一个实例。更多信息,看http://en.wikipedia.org/wiki/Command pattern。
当MVC Framework从action方法中收到一个ActionResult对象,它会调用这个类所定义的ExecuteResult方法。这个action result就会处理response对象。生成符合你意图的输出。一个简单的关于RedirectResult 类的例子。如下。MVC Framework开源的一个好处是,你可以看到背后的工作方式。我们简化了这个类,使之容易读懂。
public class RedirectResult : ActionResult {
public RedirectResult(string url): this(url, permanent: false) {
}
public RedirectResult(string url, bool permanent) {
Permanent = permanent;
Url = url;
}
public bool Permanent {
get;
private set;
}
public string Url {
get;
private set;
}
public override void ExecuteResult(ControllerContext context) {
string destinationUrl = UrlHelper.GenerateContentUrl(Url, context.HttpContext);
if (Permanent) {
context.HttpContext.Response.RedirectPermanent(destinationUrl,
endResponse: false);
}
else {
context.HttpContext.Response.Redirect(destinationUrl, endResponse: false);
}
}
}
当我们创建一个RedirectResult类的实例时,我们传入要定位给用户的URL。 MVC Framework在action方法完成后,会执行ExecuteResult方法,通过framework提供方的 ControllerContext 对象得到query的response对象,同时调用RedirectPermanent方法或者Redirect方法。
我们可以通过创建一个新的RedirectResult实例,并且从action方法返回它。如下代码,展示了DerivedController类有2个action方法,其中之一使用了RedirectResult来重定向请求。
using System.Web.Mvc;
namespace ControllersAndActions.Controllers {
public class DerivedController : Controller {
public void Index() {
string controller = (string)RouteData.Values["controller"];
string action = (string)RouteData.Values["action"];
Response.Write(
string.Format("Controller: {0}, Action: {1}", controller, action));
}
public ActionResult Redirect() {
return new RedirectResult("/Derived/Index");
}
}
}
如果你导航到 /Derived/Redirect,浏览器会重定位到/Derived/Index。为了是代码更简洁,Controller类提供简单的方法来生成不同的ActionResults,比如,我们可以通过返回Redirect方法的结果,实现同样的效果。
public ActionResult Redirect() {
return Redirect("/Derived/Index");
}
这对复杂的action result来说没有什么复杂的,但是你却能写出简洁一致的代码。你也可以很方便的对你的action方法做单元测试。MVC Framework包含了很多内建的action result类型,在下面列出,这些类型都继承自ActionResult,而且大多使用起来都很简单。
通过呈现View来返回HTML
最常用的响应是要从action方法中生成一个HTML,发送给浏览器。当使用action result系统,你通过创建ViewResult实例来指定想要生成的HTML。如下演示代码
using System.Web.Mvc;
namespace ControllersAndActions.Controllers {
public class ExampleController : Controller {
public ViewResult Index() {
return View("Homepage");
}
}
}
这些代码,使用View方法创建了ViewResult类的实例,该实例作为action方法的结果返回。
注意,返回类型是ViewResult,如果把返回值设为ActionResult,那么此方法也会编译运行的很好。事实上,一些MVC程序员喜欢吧每一个action的返回值都定义成ActionResult,即使他们知道返回的是一个特定的类型。在此,我们偏向于只要知道该返回的是什么类型,我们就使用这个返回类型。在下面的例子中我们都会这样做,这会让你清楚的知道返回的是什么类型的值。
这个例子中,我们指定了想要呈现的view,在这个例子中,我们指定的是Homepage view。
注意,我们可以显式的创建ViewResult对象,(返回new ViewResult { ViewName ="Homepage" };)这是一个完美的可行的方法,但是我们哈市偏向使用Controller类中定义的简便方法。
当MVC调用ViewResult对象的ExecuteResult方法,在指定的view上开始一个搜索。如果你项目中使用了area,那么framework会查询下面几个地址:
/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
你可以看到,framework查询的序列中有ASPX视图引擎创建的view,即使我们指定了Razor。Framework有人会查询C#和Visual Basic.NET Razor模板。MVC Framework会依次检查这些文件是否存在,只要有一个匹配了,就会使用这个view作为action方法的结果来呈现。
如果你没有使用area,或者你使用了area但是要呈现的文件不在上述清单中,那么framework会使用下面的地址继续查询“
/Views/<ControllerName>/<ViewName>.aspx
/Views/<ControllerName>/<ViewName>.ascx
/Views/Shared/<ViewName>.aspx
/Views/Shared/<ViewName>.ascx
/Views/<ControllerName>/<ViewName>.cshtml
/Views/<ControllerName>/<ViewName>.vbhtml
/Views/Shared/<ViewName>.cshtml
/Views/Shared/<ViewName>.vbhtml
同样的,只要MVC找到一个匹配项,就停止搜寻,这个找到的view就作为对客户端的响应。
MVC Framework查找目录顺序也是一个约定的配置,你不需要注册view文件。你只需要把它们放在正确的地方,framework会找到它们的。防止view的这种约定也是可以配置的,在后续会讲到。
我们可以在更进一步,在调用view方法时,忽略掉想要呈现的view的名字。如下代码
using System.Web.Mvc;
namespace ControllersAndActions.Controllers {
public class ExampleController : Controller {
public ViewResult Index() {
return View();
}
}
}
当我我们这么做的时候,MVC Framework假设我们需要呈现的view和action方法的名字是一致的。意思就,上例中的View方法的调用会开始查询命名为Index的视图。注意,这样做的结果就是MVC Framework会寻找和action方法同名的view,但是view的名字事实上由RouteData.Values["action"]的值决定。
View 有多个重载方法,分别可以在创建的ViewResult对象属性上设置不同的值。比如,你可以显式的覆盖view的laout属性,如下:
public ViewResult Index() {
return View("Index", "_AlternateLayoutPage");
}
使用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 Framework提供很多方法来实现这种功能。这里我们会讨论一下,在后续我们还会深入讨论。
提供View Model对象
你可以通过View方法的参数传递对象给view,如下代码:
public ViewResult Index() {
DateTime date = DateTime.Now;
return View(date);
}
我们传递了一个DateTime对象作为view model。我们可以在view中,使用Razor Model关键字,访问这个对象,如下:
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
The day is: @(((DateTime)Model).DayOfWeek)
上述例子的view是无类型的或者弱类型的。view不知道任何关于view model对象的事情,它以object的实例来处理这model。(此处译者测试下来,并不是object类型,而是dynamic类型,不知是为什么)要得到DayOfWeek属性,必须转换object类型到DateTime。这虽然可行,但是有点麻烦。我们可以通过强类型view使代码简洁点,我们告诉view,它的view model的类型是什么。如下代码:
@model DateTime
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
The day is: @Model.DayOfWeek
我们使用了Razor的model关键字指定了view model的类型,注意当我们指定model类型的时候,使用了一个小写的m,而读取值的时候用的是大写的M。这不仅是我们的的代码证件,也使得Visual Studio在强类型view中支持智能提示。如下: