一、MVC Result 几何?
ASP.NET MVC 目前一共提供了以下十几种Action返回结果类型:
ActionResult(base)
ContentResult
EmptyResult
HttpUnauthorizedResult
JavaScriptResult
JsonResult
FileResult (base)
FileContentResult
FilePathResult
FileStreamResult
RedirectResult
RedirectToRouteResult
ViewResultBase(base)
ViewResult
PartialViewResult
一个列表下来看得人眼花缭乱,因为可用的Result
很多,接着再瞧瞧类关系图以佐辨析:
<图>
如图中可见,ActionResult
可谓人丁兴旺,目前膝下有儿9子(如图中红色所圈的类),ViewResultBase
与FileResult
又各有子两三口,这些儿孙们各司所长。那么各个 Result
都会干点啥事儿呢?这个问题说来话长,不过根据诸如“虎父无犬子”、“种瓜得瓜,种豆得豆”、“龙生龙,凤生凤,老鼠的孩子打地洞”的俗语,孩子们多少从他爹那儿遗传了点什么,所以要说明它们的才干之前,得先唠叨唠叨一下ActionResult
这个爹,这个爷,因此这事情还是得先从ActionResult
说起。
二、朴实的 ActionResult
所有的 Result
都派生自 ActionResult
抽象类,因此 ActionResult
作为基类提供了最基础的功能,ActionResult
是一个抽象类,其声明如下:
public abstract class ActionResult {
public abstract void ExecuteResult(ControllerContext context);
}
看看普通人民、相貌平平的ActionResult
,ActionResult
是个朴素老百姓,没啥特长,就一个 ExecuteResult()
抽象方法,这个ExecuteResult()
抽象方法还啥都不干,遗传给儿女孙子们让它们去发挥,那么它的责任其实就很明确了,它就是为继承作准备的。因为ActionResult
是所有Result
的基类,因此你可以在所有的Action
上使用它作为返回值类型,而无需动脑筋来明确与返回值相同的类型。
三、EmptyResult
EmptyResult
是ActionResult
最没用的儿子,虽然生儿都想生孙仲谋,希望儿子们都是八斗之才,国家栋梁,可惜第一胎 EmptyResult
就严重破坏了它的梦想,看来也只能痛恨自己种子不够好。咱来瞧瞧这个没用的阿斗:
//表示一个啥都不干的结果,就像 controller action 返回 null
public class EmptyResult : ActionResult {
private static readonly EmptyResult _singleton = new EmptyResult();
internal static EmptyResult Instance {
get {
return _singleton;
}
}
public override void ExecuteResult(ControllerContext context) {
}
}
EmptyResult
遗传并实现了ActionResult
的ExecuteResult()
方法,同时也遗传了ActionResult
的天真朴实的想法,也想“还是等下一代吧”,它有点老子的“无为”味道,所以它的ExecuteResult()
方法像足了它的老爹,啥也不干。
EmptyResult
类使用了简单的单例模式,看来这样不思进取的儿子,整个家族里头生一个就够糟糕了,用广东人的话说,生它还不如生块叉烧肉。
在Action
中,若要返回一个空的页面(不常用),则可如下:
public ActionResult Index()
{
return new EmptyResult();
}
执行后页面将缺省返回一个body
为空的HTML
架构:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html><head>
<meta content="text/html; charset=gb2312" http-equiv=content-type></head>
<body></body></html>
四、RedirectResult
EmptyResult
的“无为”给ActionResult
的打击着实不小,只好将期待落在其他孩子身上,RedirectResult
虽然不是什么大才,起码有一技之长,我们看看它的 ExecuteResult()
方法:
public override void ExecuteResult(ControllerContext context) {
if (context == null) {
throw new ArgumentNullException("context");
}
string destinationUrl = UrlHelper.Content(Url, context.HttpContext);
context.HttpContext.Response.Redirect(destinationUrl, false /* endResponse */);
}
RedirectResult
用于执行转移。事实上 RedirectResult
最终调用了 Response.Redirect()
进行转移,所以您可以使用RedirectResult
跳转到任意的包括当前项目或网络上的Url
,例如:http://www.cnblogs.com
,对于当前项目的路径,因为使用了UrlHelper.Content()
方法获取目标路径,所以RedirectResult
传递的Url
同时支持当前项目目录标识符 ~ (即应用程序目录)。
五、RedirectToRouteResult
RedirectToRouteResult
对于RedirectResult
而言,其作用有所局限,仅能转移到路由(路由匹配的结果最终是一条相对当前项目的Url
,例如: /Home/Index
),总的来说与RedirectResult
的最终作用是一样的,都是执行转移。RedirectResult
较为直接地转移到任意指定的Url,而RedirectToRouteResult
则转移到指定的路由(路由匹配所得结果最终也是一个的Url):
public override void ExecuteResult(ControllerContext context) {
if (context == null) {
throw new ArgumentNullException("context");
}
string destinationUrl = UrlHelper.GenerateUrl(RouteName, null /* actionName */, null /* controllerName */, RouteValues, Routes, context.RequestContext, false /* includeImplicitMvcValues */);
if (String.IsNullOrEmpty(destinationUrl)) {
throw new InvalidOperationException(MvcResources.ActionRedirectResult_NoRouteMatched);
}
context.HttpContext.Response.Redirect(destinationUrl, false /* endResponse */);
}
RedirectToRouteResult
先通过调用UrlHelper.GenerateUrl()
来获得路由匹配所得的最终Url
,接着的执行转移过程与RedirectResult
相同。
路由配置的过程在Global.asax
文件中进行,在以MVC
模板方式创建的MVC
项目中都带有此文件,可在文件中的MvcApplication
类的 RegisterRoutes()
方法中进行配置路由,该方法缺省的内容如下:
public static void RegisterRoutes( RouteCollection routes )
{
routes.IgnoreRoute( "{resource}.axd/{*pathInfo}" );
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
}
RedirectToRouteResult
可跳转至任何一条匹配的路由规则。是以利用路由转移可以跳转到其他控制器的 Action
。
五、ContentResult
ContentResult
用于将字符串直接向客户端输出。ContentResult
的ExecuteResult
方法实际上是调用了 Response.Write( string… )
,输入并无特别之处,但是在 ASP 时代,这个Response.Write()
却是可以纵横页面。从输出一个简单的字符串到整个页面,Response.Write()
都能胜任,所以ContentResult
显得特别强大:
public override void ExecuteResult(ControllerContext context) {
if (context == null) {
throw new ArgumentNullException("context");
}
HttpResponseBase response = context.HttpContext.Response;
if (!String.IsNullOrEmpty(ContentType)) {
response.ContentType = ContentType;
}
if (ContentEncoding != null) {
response.ContentEncoding = ContentEncoding;
}
if (Content != null) {
response.Write(Content);
}
}
若没有提供任何输出的内容,ContentResult
呈现的结果与EmptyResult
是一样的,都是输出最基本的<body>
标记内容为空的HTML
,若内容不为空,则直接输出这些内容(不再输出其他任何 HTML
代码),例如:
public ActionResult Index()
{
return Content( "a" );
}
其页面的HTML
代码也将只有一个字符 a
,要补全所有基本标记需要在字符串中编写,例如:
public ActionResult Index()
{
return Content( "<!DOCTYPE HTML PUBLIC ""-//W3C//DTD HTML 4.0 Transitional//EN"">" +
"<html>" +
"<head><meta content=""text/html; charset=gb2312"" http-equiv=content-type></head>" +
"<body>" +
"abc" +
"</body>" +
"</html>"
);
}
当然不建议使用此方法来输出页面标记,ContentResult
用在Ajax中颇为合适,因为只要内容不为空,输出的字符串与传送到客户端的内容一致,没有额外的附加内容。
事实上从ContentResult
我们可以看到一个ActionResult
其实并无特别,从前面几个Result
来看,其实不过是Response.Redirect
或Response.Write
,此外还可以利用二进制流Response.OutputStream.Write
向客户端上载文件……据此我们所以拓展编写更多针对实际意义的Result
。例如 XmlResult
(文件)、RssResult
(跟XmlResult
其实是一样的)等等。
六、JsonResult
JsonResult
首先将指定的对象序列化为Json
字符串,然后将字符串写入到HTTP
输出流。撇开对象序列化为Json
字符串这一过程,实际上与ContentResult
其实是一样的,因为JsonResult
与ContentResult
都是调用Response.Write()
向HTTP
输出流写入一些内容。所以对此不再赘述:
public override void ExecuteResult(ControllerContext context) {
if (context == null) {
throw new ArgumentNullException("context");
}
HttpResponseBase response = context.HttpContext.Response;
if (!String.IsNullOrEmpty(ContentType)) {
response.ContentType = ContentType;
}
else {
response.ContentType = "application/json";
}
if (ContentEncoding != null) {
response.ContentEncoding = ContentEncoding;
}
if (Data != null) {
//JavaScriptSerializer类型在.NET Framework 3.5 SP1之前被标记为已过时
#pragma warning disable 0618
JavaScriptSerializer serializer = new JavaScriptSerializer();
response.Write(serializer.Serialize(Data));
#pragma warning restore 0618
}
}
有个地方想唠叨两句,在代码中的:
response.ContentType = "application/json";
若要直接向页面输出的话需要更改为文本类型,例如 text/html
,否则你要以文件形式下载JsonResult
的结果内容。不过这对于将Json
用于Ajax
而言不会有什么影响。
七、JavaScriptResult
与 JsonResult
、ContentResult
相同。所以也不赘述,徒费唇舌:
public override void ExecuteResult(ControllerContext context) {
if (context == null) {
throw new ArgumentNullException("context");
}
HttpResponseBase response = context.HttpContext.Response;
response.ContentType = "application/x-javascript";
if (Script != null) {
response.Write(Script);
}
}
八、HttpUnauthorizedResult
HttpUnauthorizeResult
设置客户端错误代号为 401
,即未经授权浏览状态,若设置了Form
验证并且客户端没有任何身份票据,那么将转跳到指定的页面(例如登陆页):
public override void ExecuteResult(ControllerContext context) {
if (context == null) {
throw new ArgumentNullException("context");
}
//401是未授权访问的HTTP状态代码 - 设置这将导致主动认证模块执行其默认的未授权处理程序
context.HttpContext.Response.StatusCode = 401;
}
可以学习HttpUnauthorizeResult
来编写更多同类的返回结果,例如设置 Response.StatusCode = 404
,这个是常见的“页面未找到”错误,403
禁止访问等等。
九、FileResult
FileResult
是一个抽象类,主要属性包括声明内容类型信息ContentType
及文件名称FileDownloadName
,客户端下载工具中将显示此名称(如果有指定,ContentType
可指定任意非空字符串),如果不指定文件名,ContentType
需要正确指定,否则无法识别待下载的文件类型。
FileResult
用作其他向客户端上载文件的类的基类。
十、FilePathResult
FilePathResult
继承自 FileResult
,使用 FilePathResult
类向客户端上载文件只需要给出文件的路径即可。FilePathResult
将调用 Response.TransmitFile()
传输该文件:
protected override void WriteFile(HttpResponseBase response)
{
response.TransmitFile(FileName);
}
十一、FileContentResult
FileContentResult
继承自 FileResult
。
FileContentResult
将指定的字节内容写入二进制流(客户端将以文件形式下载),对比 FilePathResult
所不同的是 FilePathResult
是给出文件路径,然后将整份文件上载给客户,而 FileContentResult
则可以传输某一个字节数组,例如:
public ActionResult Index()
{
return File( System.Text.Encoding.UTF8.GetBytes( "你好吗" ), "unknown", "t.txt" );
}
FileContentResult
使用 Response.OutputStream.Write
输出内容:
protected override void WriteFile(HttpResponseBase response) {
response.OutputStream.Write(FileContents, 0, FileContents.Length);
}
十二、FileStreamResult
FileStreamResult
继承自 FileResult
。
FileStreamResult
向指定文件流读取数据,其他的内容与FileContentResult
道同。请参考FileContentResult
。