我们知道针对客户端的请求,最终都会转换为对 Controller 中的一个 Action 方法的调用,指定的 Action 方法会返回一个 ActionResult 类型的实例来响应该请求,但 ActionResult 类型的实例是如何转换为请求终端最终呈现的页面的呢?这就是我们这里要介绍的。

ActionResult

  每个标准的 Action 方法总会返回一个 ActionResult 类型的对象,该类型是一个抽象类,该类的定义如下:

public abstract class ActionResult
{
public abstract void ExecuteResult(ControllerContext context);
}

  可以看出,该类中仅定义了一个 void ExecuteResult(ControllerContext context) 方法,该方法的作用就是将当前的 ActionResult 处理为能够直接响应给请求终端的内容。

ActionResult 类型的子类

ContentResult

  ContentResult 用于返回一些文本类型的内容给请求终端。该类型的定义如下示:

public class ContentResult : ActionResult
{
//要返回的响应的内容
public string Content { get; set; } //返回内容所使用的的编码规则,如utf-8等
public Encoding ContentEncoding { get; set; } //返回内容的 MIME 类型,如 application/json、text/plain 等
public string ContentType { get; set; } public override void ExecuteResult(ControllerContext context);
}

  下面看看它的的 ExecuteResult 方法是如何实现的。

public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
} HttpResponseBase response = context.HttpContext.Response; //设置 contentType
if (!String.IsNullOrEmpty(ContentType))
{
response.ContentType = ContentType;
}
//设置 Encoding
if (ContentEncoding != null)
{
response.ContentEncoding = ContentEncoding;
}
if (Content != null)
{
//向客户端输出
response.Write(Content);
}
}
EmptyResult

  该类型用于向客户端返回一个空的响应,该类型的 ExecuteResult 方法为一个空方法,即没有任何的实现,在该类型中使用单例的方式定义了一个该类型的静态的实例 Instance,但该属性的访问权限为 internal,即只能在 System.Web.Mvc 程序集下使用,该类型并没有采用完全的单例模式,亦对外公开了 public 的构造函数。

JavaScriptResult

  该类型用于向客户端返回一串 JavaScript 脚本,该类型仅定义的一个 string 类型的属性 Script,用于表示返回的脚本的内容,其 ExecuteResult 方法的实现基本上与 ContentResult 相同,不同在于其响应的 Content-Type 被固定的设置为了 application/x-javascript

JsonResult

  该类型用于向客户端返回一个 Json 类型的响应。该类型的定义如下:

public class JsonResult : ActionResult
{
//属性部分
public Encoding ContentEncoding { get; set; }//返回内容的编码格式 //返回内容的 contentType
public string ContentType { get; set; } //用于序列化返回的对象
public object Data { get; set; } //枚举,设置是否允许 Get 请求
public JsonRequestBehavior JsonRequestBehavior; //序列化生成的字符串的最大长度
public int? MaxJsonLength { get; set; } //序列化允许递归的最大层数
public int? RecursionLimit { get; set; }
}

  其 ExecuteResult 方法的实现如下示:

public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
} //当 JsonRequestBehavior 设置为 DenyGet 时,若当前请求方法为 Get,则抛出异常
if (JsonRequestBehavior == JsonRequestBehavior.DenyGet &&
String.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(MvcResources.JsonRequest_GetNotAllowed);
} HttpResponseBase response = context.HttpContext.Response; //设置 ContentType
if (!String.IsNullOrEmpty(ContentType))
{
response.ContentType = ContentType;
}
else
{
response.ContentType = "application/json";
}
//设置编码
if (ContentEncoding != null)
{
response.ContentEncoding = ContentEncoding;
} //对对象进行序列化处理
if (Data != null)
{
JavaScriptSerializer serializer = new JavaScriptSerializer();
if (MaxJsonLength.HasValue)
{
serializer.MaxJsonLength = MaxJsonLength.Value;
}
if (RecursionLimit.HasValue)
{
serializer.RecursionLimit = RecursionLimit.Value;
}
//写入到输出
response.Write(serializer.Serialize(Data));
}}

  在 Controller 下定义有一个 Json 方法,其内部便会创建该类型的实例并返回。

  从上面的代码可以看出,该类型内部是通过 JavaScriptSerializer 类型对类型进行序列化的,且并没有提供对序列化进行进一步设置的选项,如对 DateTime 的格式问题等,如果有特殊的序列化需求,那么,此时,可以使用自定义的序列化工具进行序列化,然后使用 ContentResult 返回序列化后的字符串,并将其 ContentType 设置为 application/json 即可。

RedirectResult

  该类型用于返回一个重定向的响应。该类型的定义如下示:

public class RedirectResult : ActionResult
{
public RedirectResult(string url): this(url, permanent: false) //url:重定向的url,permanent:是否为永久重定向
public RedirectResult(string url,bool permanent)
}

  其 ExecuteResult 方法实现如下示:

public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
} //子 Action 不能执行重定向
if (context.IsChildAction)
{
throw newInvalidOperationException(MvcResources.RedirectAction_CannotRedirectInChildActi;
} //如果Url中不是以 ~ 字符开头,则直接返回,如果是,则对其进行转换,例如:~/home/index,将会被转换为 /home/index
string destinationUrl = UrlHelper.GenerateContentUrl(Url, context.HttpContext); //持久化 TempDataDictionary
context.Controller.TempData.Keep(); //永久重定向
if (Permanent)
{
context.HttpContext.Response.RedirectPermanent(destinationUrl, endResponse:false);
}
else
{
//临时重定向
context.HttpContext.Response.Redirect(destinationUrl, endResponse: false);
}}
RedirectToRouteResult

  该类型根据传入的路由数据和路由名称生成目标 Url,然后重定向到该 Url,该类型的定义如下:

public class RedirectToRouteResult : ActionResult
{
//构造函数
public RedirectToRouteResult(RouteValueDictionary routeValues):this(null, routeValues); public RedirectToRouteResult(string routeName, RouteValueDictionary routeValues): this(routeName, routeValues, permanent: false); public RedirectToRouteResult(string routeName, RouteValueDictionary routeValues, bool permanent); //属性
public bool Permanent { get; private set; }//是否为永久重定向 //生成 Url 使用的路由的名称
public string RouteName { get; private set; } //生成 Url 使用的路由数据
public RouteValueDictionary RouteValues { get; private set; } }

   其 ExecuteResult 方法的实现基本上同 RedirectResult 的同名方法,唯一不同的就是生成重定向的目标 Url 处不同。后者后调用如下的方法生成目标 Url。

string destinationUrl = UrlHelper.GenerateUrl(
RouteName,
null /* actionName */,
null /* controllerName */,
RouteValues,
Routes, //全局注册的路由表
context.RequestContext,
false /* includeImplicitMvcValues */是否包含隐式的路由数据,即 RequestContext.RouteData.Values
);

  在该方法中首先对路由数据进行合并,首先根据 includeImplicitMvcValues(从上看出这里为false) 决定是否使用隐式的路由数据,然后根据方法是否指定了 controllerNameactionName(从上面可以看出这里全为null),如果指定了这两个参数,且在 RouteValues 中同时设置了 key 为 controlleraction 的项,则前者优先使用。然后调用 RouteCollection 类型的实例(即前面的全局的路由表)的 GetVirtualPath 方法返回一个 VirtualPathData,该类型是对当前使用的 Route 对象及虚拟路径的封装。最后将其虚拟路径采用与 RedirectResult 中同样的方式进行转换,将转换后的路径作为重定向的目标 Url

HttpStatusCodeResult

  该类型用于返回一些 Http 状态码信息。该类型的定义如下示:

public class HttpStatusCodeResult
{
//属性,均可以通过对应的构造函数重载进行设置
//除了指定具体的状态码,亦可以使用框架内预定义的 HttpStatusCode 类型的枚举,其中包括了绝大多数常用的状态码 public int StatusCode { get; private set; }//状态码 //状态短语,如 200 对应的 ok,404 对应的 Not Found 等
public string StatusDescription { get; private set; } }

  其 ExecuteResult 方法的实现如下示:

  

public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
} //设置响应的状态码
context.HttpContext.Response.StatusCode = StatusCode;
if (StatusDescription != null)
{
//设置响应的状态描述短语
context.HttpContext.Response.StatusDescription = StatusDescription;
}
}

  该类型还具有两个子类:HttpNotFoundResultHttpUnauthorizedResult,其内部也仅仅时调用基类的构造函数分别转入状态码 404401,两者都有一个可以设置状态描述短语的重载。

FileResult

  该类型用于返回文件信息给客户端。该类型是一个抽象类,其定义如下示:

  

public class FileResult
{
//属性部分 //文件的 ContentType,MIME 字符串
public string ContentType { get; private set; } // 文件下载时的名称
public string FileDownloadName; //方法部分 //将文件数据写入的响应流
protected abstract void WriteFile(HttpResponseBase response);
}

  其 ExecuteResult 方法的实现如下示:

  

public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
} HttpResponseBase response = context.HttpContext.Response;
response.ContentType = ContentType;//设置文件的 ContentType if (!String.IsNullOrEmpty(FileDownloadName))
{ //这里会对文件的下载名称进行处理
string headerValue = ContentDispositionUtil.GetHeaderValue(FileDownloadName); //设置该头部会使浏览器将其识别为一个下载项
context.HttpContext.Response.AddHeader("Content-Disposition", headerValue);
} WriteFile(response);
}

  下载文件的名称中除去以下的字符外,其余的字符均为非法字符

  

a-z、A-Z、0-9、.、-、_、+、$、&、!、:、~、

  对于非法的字符,便会对其进行 Url 编码处理,详情请参考

  该类型具有三个子类,FileContentResultFilePathResultFileStreamResult,下面对这三个类一一进行说明。

  • FileContentResult

      该类型的定义如下示:
public class FileContentResult : FileResult
{
public FileContentResult(byte[] fileContents, string contentType)
: base(contentType); public byte[] FileContents { get; private set; } protected override void WriteFile(HttpResponseBase response)
{
response.OutputStream.Write(FileContents, 0, FileContents.Length);
}
}

  从上可以看出该类型接受一个字节数组,然后将其写入到响应的输出流中。

  • FilePathResult

      该类型接受一个要返回的文件的路径(相对根目录的路径),然后读取文件并返回,该类的定义如下示:
public class FilePathResult : FileResult
{
public FilePathResult(string fileName, string contentType)
: base(contentType) //文件的名称(含路径)
public string FileName { get; private set; } protected override void WriteFile(HttpResponseBase response)
{
response.TransmitFile(FileName);
}}
  • FileStreamResult

      该类型接受一个文件流,然后将该流写入到响应的输出流中,该类的定义如下示:  
 public class FileStreamResult : FileResult
{
// default buffer size as defined in BufferedStream type
private const int BufferSize = 0x1000; public FileStreamResult(Stream fileStream, string contentType)
: base(contentType) //输出文件流
public Stream FileStream { get; private set; } protected override void WriteFile(HttpResponseBase response)
{
// grab chunks of data and write to the output stream
Stream outputStream = response.OutputStream;
using (FileStream)
{
byte[] buffer = new byte[BufferSize]; while (true)
{
int bytesRead = FileStream.Read(buffer, 0, BufferSize);
if (bytesRead == 0)
{
// no more data
break;
}
outputStream.Write(buffer, 0, bytesRead);
}
}
}

  在 Controller 下定义的 File 方法具有多个不同的重载,根据重载参数的不同在方法内部分别会创建上述三种类型其一的实例。

  至此,**Asp.net mvc ** 中大多数 ActionResult 类型已介绍完毕,还有一个 ViewResult,由于其太重要了,因此,单独放在下一节进行说明。

04-13 20:32