处理输出

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,而且大多使用起来都很简单。

1efa5ba1b30a4472b32cd58ef0267ac836cfbdda852a4918853ebb22bd6df7cb

通过呈现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中支持智能提示。如下:

clipboard