在上一篇最后,我们进行到了Action调用的“门口”:
monospace !important; float: none !important; height: auto !important; font-size: 1em !important; vertical-align: baseline !important; top: auto !important; right: auto !important; font-weight: normal !important; padding-top: 0px !important; left: auto !important; background-origin: initial; background-clip: initial; border-width: 0px !important;">1
if
(!ActionInvoker.InvokeAction(ControllerContext, actionName))
在深入研究调用过程的细节前,先有一个总体的认识是很有帮助的。InvokeAction方法大致是按照这样的顺序进行的:
查找action:MVC内部查找action的方法似乎有点复杂,涉及到一个ActionDescriptor的东西,但是原理上是通过反射,在以后的文章中会有所涉及。
验证和过滤:众所周知的IActionFilter和IAuthorizationFilter在这部分生效,它们在真正执行action之前,事实上对于IResultFilter或IExceptionFilter这样的过滤器是在action执行之后执行的,图中对于这个没有画出。
执行action:真正进入用户代码执行,通过反射调用,调用之前还涉及到复杂的参数提供和绑定,在以后的文章中会涉及。
执行结果:ActionResult在这部起到了关键的作用,ActionResult有多个派生,其中最为常见的就是ViewResult。ActionResult是前面步骤执行的最终“果实”,通过执行ActionResult的ExecuteResult抽象方法,一个HttpRespose被正确的构造好,准备传回客户端。
就像上一篇讲到的,我们可以在Controller的Execute方法中直接对HttpContext.Response操作,绕过action;即便我们走了action这一路,仍然可以在action中像下面这样直接操作Response:
01
public
class
SimpleController : Controller
02
{
03
public
void
MyActionMethod()
04
{
05
Response.Write(
"I'll never stop using the <blink>blink</blink> tag"
);
06
// ... or ...
07
Response.Redirect(
"/Some/Other/Url"
);
08
// ... or ...
09
Response.TransmitFile(
@"c:\files\somefile.zip"
);
10
}
11
}
然而这种方式难以维护,而且难以单元测试,于是MVC框架建议action返回ActionResult,并由框架调用ActionResult的ExecuteResult方法,这类似于设计模式中的command模式。你会看到这种设计模式在这里的运用实在是十分精辟的。
ActionResult是一个十足的抽象类,抽象到不能再抽象了,它定义了唯一的ExecuteResult方法,参数为一个ControllerContext,其中封装了包括HttpContext在内的许多对象,也是重写这个方法唯一的上下文信息:
1
namespace
System.Web.Mvc {
2
3
public
abstract
class
ActionResult {
4
5
public
abstract
void
ExecuteResult(ControllerContext context);
6
7
}
8
9
}
MVC内置了很多实用的ActionResult,如下图:
我们可以看个简单的实现类RedirectResult是如何实现ExecuteResult的。在这里我发现了我曾经遇到过的一个异常的原因:Child actions are not allowed to perform redirect actions,意思是在子action不允许重定向。
01
public
override
void
ExecuteResult(ControllerContext context) {
02
if
(context ==
null
) {
03
throw
new
ArgumentNullException(
"context"
);
04
}
05
if
(context.IsChildAction) {
06
throw
new
InvalidOperationException(MvcResources.RedirectAction_CannotRedirectInChildAction);
07
}
08
09
string
destinationUrl = UrlHelper.GenerateContentUrl(Url, context.HttpContext);
10
context.Controller.TempData.Keep();
11
context.HttpContext.Response.Redirect(destinationUrl,
false
/* endResponse */
);
12
}
在这段代码中ExecuteResult的实现相对简单的多,事实上像ViewResult的实现就复杂很多,关于ViewResult将在视图中涉及到。
在Controller中有很多辅助方法便于我们在action中返回需要的ActionResult,下面列出:
大家都很少用到这个ActionResult,因为它的基本用法是返回文本,也许下面这段代码可以说服你
01
public
ContentResult RSSFeed()
02
{
03
Story[] stories = GetAllStories();
// Fetch them from the database or wherever
04
05
// Build the RSS feed document
06
string
encoding = Response.ContentEncoding.WebName;
07
XDocument rss =
new
XDocument(
new
XDeclaration(
"1.0"
, encoding,
"yes"
),
08
new
XElement(
"rss"
,
new
XAttribute(
"version"
,
"2.0"
),
09
new
XElement(
"channel"
,
new
XElement(
"title"
,
"Example RSS 2.0 feed"
),
10
from story
in
stories
11
select
new
XElement(
"item"
,
12
new
XElement(
"title"
, story.Title),
13
new
XElement(
"description"
, story.Description),
14
new
XElement(
"link"
, story.Url)
15
)
16
)
17
)
18
);
19
return
Content(rss.ToString(),
"application/rss+xml"
);
20
}
上面的代码返回了一个RSS。值得注意的是,Content的第二个参数是个contentType(MIME类型,参见www.iana.org/assignments/media-types),如果不指定这个参数将使用text/html的contentType。
事实上我们的action可以返回一个非ActionResult,MVC在执行action的返回结果前,会确保将返回值转换成一个ActionResult,其中一步,就是对非空和非ActionResult的结果转换成string,并包装成ContentResult:
1
protected
virtual
ActionResult CreateActionResult(ControllerContext controllerContext, ActionDescriptor actionDescriptor,
object
actionReturnValue) {
2
if
(actionReturnValue ==
null
) {
3
return
new
EmptyResult();
4
}
5
6
ActionResult actionResult = (actionReturnValue
as
ActionResult) ??
7
new
ContentResult { Content = Convert.ToString(actionReturnValue, CultureInfo.InvariantCulture) };
8
return
actionResult;
9
}
Controller的Json方法能返回一个JsonResult,出于安全性的考虑JsonResult只支持POST方式,设置response.ContentType = "application/json";并利用JavaScriptSerializer序列化对象,并返回给客户端。像下面这样使用JsonResult:
01
class
CityData {
public
string
city;
public
int
temperature; }
02
03
[HttpPost]
04
public
JsonResult WeatherData()
05
{
06
var citiesArray =
new
[] {
07
new
CityData { city =
"London"
, temperature = 68 },
08
new
CityData { city =
"Hong Kong"
, temperature = 84 }
09
};
10
11
return
Json(citiesArray);
12
}
JavaScript方法实例化一个JavaScriptResult,JavaScriptResult只是简单的设置response.ContentType = "application/x-javascript";
FileResult是个抽象类,File方法的多个重载返回不同的FileResult:
FilePathResult:直接将一个文件发送给客户端
1
public
FilePathResult DownloadReport()
2
{
3
string
filename =
@"c:\files\somefile.pdf"
;
4
return
File(filename,
"application/pdf"
,
"AnnualReport.pdf"
);
5
}
FileContentResult:返回byte字节给客户端比如图片
1
public
FileContentResult GetImage(
int
productId)
2
{
3
var product = productsRepository.Products.First(x => x.ProductID == productId);
4
return
File(product.ImageData, product.ImageMimeType);
5
}
1
<
img
src
=
"<%: Url.Action("
GetImage", "Products", new { Model.ProductID }) %>" />
FileStreamResult:返回流
1
public
FileStreamResult ProxyExampleDotCom()
2
{
3
WebClient wc =
new
WebClient();
4
Stream stream = wc.OpenRead(
"http://www.example.com/"
);
5
return
File(stream,
"text/html"
);
6
}
PartialViewResult和ViewResult十分复杂,涉及到视图,将在以后详细讨论。
产生重定向结果,上面已经展示了RedirectResult的实现了。