【轉自】http://www.cnblogs.com/P_Chou/archive/2010/11/26/details-asp-net-mvc-06.html
在上一篇最后,我們進行到了Action調用的“門口”:
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被正確的構造好,准備傳回客戶端。
從ActionResult開始說起
就像上一篇講到的,我們可以在Controller的Execute方法中直接對HttpContext.Response操作,繞過action;即便我們走了action這一路,仍然可以在action中像下面這樣直接操作Response:
1
2
3
4
5
6
7
8
9
10
11
|
public
class
SimpleController : Controller
{
public
void
MyActionMethod()
{
Response.Write(
"I'll never stop using the <blink>blink</blink> tag"
);
// ... or ...
Response.Redirect(
"/Some/Other/Url"
);
// ... or ...
Response.TransmitFile(
@"c:\files\somefile.zip"
);
}
}
|
然而這種方式難以維護,而且難以單元測試,於是MVC框架建議action返回ActionResult,並由框架調用ActionResult的ExecuteResult方法,這類似於設計模式中的command模式。你會看到這種設計模式在這里的運用實在是十分精辟的。
ActionResult是一個十足的抽象類,抽象到不能再抽象了,它定義了唯一的ExecuteResult方法,參數為一個ControllerContext,其中封裝了包括HttpContext在內的許多對象,也是重寫這個方法唯一的上下文信息:
1
2
3
4
5
6
7
8
9
|
namespace
System.Web.Mvc {
public
abstract
class
ActionResult {
public
abstract
void
ExecuteResult(ControllerContext context);
}
}
|
MVC內置了很多實用的ActionResult
Action Result | Helper Method | Description |
ViewResult | View | Returns a view as a webpage |
PartialViewResult | PartialView | Returns a partial view. A partial view has the same role as a user control. A partial view will be rendered within a main view |
RedirectResult | Redirect | Redirects to another action method based on its URL |
RedirectToRouteResult | RedirectToAction RedirectToRoute |
Redirects to another action method |
ContentResult | Content | Returns a user defined content type |
JsonResult | Json | Returns a serialized JSON object |
JavaScriptResult | JavaScript | Returns a script that can be executed on the client |
FileResult | File | Returns binary output to write to the response |
FileStreamResult | File | Returns a file stream |
FilePathResult | File | Returns a file path |
FileContentResult | File | Returns the content of a file |
EmptyResult | (None) | Used with void action methods which doesn’t return a value |
HttpStatusCodeResult | Returns an HTTP Status code | |
HttpNotFoundResult | Returns HTTP Not Found | |
HttpUnauthorizedResult | Return HTTP Not Authorized |
Thanks to: http://msdn.microsoft.com/en-us/library/dd410269.aspx
我們可以看個簡單的實現類RedirectResult是如何實現ExecuteResult的。在這里我發現了我曾經遇到過的一個異常的原因:Child actions are not allowed to perform redirect actions,意思是在子action不允許重定向。
1
2
3
4
5
6
7
8
9
10
11
12
|
public
override
void
ExecuteResult(ControllerContext context) {
if
(context ==
null
) {
throw
new
ArgumentNullException(
"context"
);
}
if
(context.IsChildAction) {
throw
new
InvalidOperationException(MvcResources.RedirectAction_CannotRedirectInChildAction);
}
string
destinationUrl = UrlHelper.GenerateContentUrl(Url, context.HttpContext);
context.Controller.TempData.Keep();
context.HttpContext.Response.Redirect(destinationUrl,
false
/* endResponse */
);
}
|
在這段代碼中ExecuteResult的實現相對簡單的多,事實上像ViewResult的實現就復雜很多,關於ViewResult將在視圖中涉及到。
在Controller中有很多輔助方法便於我們在action中返回需要的ActionResult,下面列出:
Content():返回ContentResult
大家都很少用到這個ActionResult,因為它的基本用法是返回文本,也許下面這段代碼可以說服你
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public
ContentResult RSSFeed()
{
Story[] stories = GetAllStories();
// Fetch them from the database or wherever
// Build the RSS feed document
string
encoding = Response.ContentEncoding.WebName;
XDocument rss =
new
XDocument(
new
XDeclaration(
"1.0"
, encoding,
"yes"
),
new
XElement(
"rss"
,
new
XAttribute(
"version"
,
"2.0"
),
new
XElement(
"channel"
,
new
XElement(
"title"
,
"Example RSS 2.0 feed"
),
from story
in
stories
select
new
XElement(
"item"
,
new
XElement(
"title"
, story.Title),
new
XElement(
"description"
, story.Description),
new
XElement(
"link"
, story.Url)
)
)
)
);
return
Content(rss.ToString(),
"application/rss+xml"
);
}
|
上面的代碼返回了一個RSS。值得注意的是,Content的第二個參數是個contentType(MIME類型,參見www.iana.org/assignments/media-types),如果不指定這個參數將使用text/html的contentType。
事實上我們的action可以返回一個非ActionResult,MVC在執行action的返回結果前,會確保將返回值轉換成一個ActionResult,其中一步,就是對非空和非ActionResult的結果轉換成string,並包裝成ContentResult:
1
2
3
4
5
6
7
8
9
|
protected
virtual
ActionResult CreateActionResult(ControllerContext controllerContext, ActionDescriptor actionDescriptor,
object
actionReturnValue) {
if
(actionReturnValue ==
null
) {
return
new
EmptyResult();
}
ActionResult actionResult = (actionReturnValue
as
ActionResult) ??
new
ContentResult { Content = Convert.ToString(actionReturnValue, CultureInfo.InvariantCulture) };
return
actionResult;
}
|
Json():返回JsonResult
Controller的Json方法能返回一個JsonResult,出於安全性的考慮JsonResult只支持POST方式,設置response.ContentType = "application/json";並利用JavaScriptSerializer序列化對象,並返回給客戶端。像下面這樣使用JsonResult:
1
2
3
4
5
6
7
8
9
10
11
12
|
class
CityData {
public
string
city;
public
int
temperature; }
[HttpPost]
public
JsonResult WeatherData()
{
var citiesArray =
new
[] {
new
CityData { city =
"London"
, temperature = 68 },
new
CityData { city =
"Hong Kong"
, temperature = 84 }
};
return
Json(citiesArray);
}
|
JavaScript():返回JavaScriptResult
JavaScript方法實例化一個JavaScriptResult,JavaScriptResult只是簡單的設置response.ContentType = "application/x-javascript";
File():返回二進制數據或文件
FileResult是個抽象類,File方法的多個重載返回不同的FileResult:
FilePathResult:直接將一個文件發送給客戶端
1
2
3
4
5
|
public
FilePathResult DownloadReport()
{
string
filename =
@"c:\files\somefile.pdf"
;
return
File(filename,
"application/pdf"
,
"AnnualReport.pdf"
);
}
|
FileContentResult:返回byte字節給客戶端比如圖片
1
2
3
4
5
|
public
FileContentResult GetImage(
int
productId)
{
var product = productsRepository.Products.First(x => x.ProductID == productId);
return
File(product.ImageData, product.ImageMimeType);
}
|
1
|
<
img
src
=
"<%: Url.Action("
GetImage", "Products", new { Model.ProductID }) %>" />
|
FileStreamResult:返回流
1
2
3
4
5
6
|
public
FileStreamResult ProxyExampleDotCom()
{
WebClient wc =
new
WebClient();
return
File(stream,
"text/html"
);
}
|
PartialView()和View():分別返回PartialViewResult和ViewResult
PartialViewResult和ViewResult十分復雜,涉及到視圖,將在以后詳細討論。
Redirect():返回RedirectResult
產生重定向結果,上面已經展示了RedirectResult的實現了。
RedirectToAction(),RedirectToRoute():返回RedirectToRouteResult