本文轉自:博客園-文超的技術博客
一、 ASP.NET MVC 1.0 Result 幾何?
Action的返回值類型到底有幾個?咱們來數數看。
ASP.NET MVC 1.0 目前一共提供了以下十幾種Action返回結果類型:
1. ActionResult(base)
2. ContentResult
3. EmptyResult
4. HttpUnauthorizedResult
5. JavaScriptResult
6. JsonResult
7. FileResult (base)
8. FileContentResult
9. FilePathResult
10. FileStreamResult
11. RedirectResult
12. RedirectToRouteResult
13. ViewResultBase(base)
14. ViewResult
15. 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為空的HMTL架構:
<!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) {
// The JavaScriptSerializer type was marked as obsolete prior to .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 is the HTTP status code for unauthorized access - setting this
// will cause the active authentication module to execute its default
// unauthorized handler
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。
ViewResult 及其相關內容將在下回討論。