(轉)從零開始學習ASP.NET MVC(三) Controller/Action 深入解析和應用
一.摘要
一個Url請求經過了Routing處理後會呼叫Controller的Act
二.承上啟下
在上一篇文章中, 我已經學會了如何使用Routing獲取Controller和Act
每個Act
三.Controller與Action的作用
1.職責
Controller負責將獲取Model資料並將Model傳遞給View物件.通知View物件顯示.
2.ASP.NET MVC中的Controller和Action
在ASP.NET MVC中, 一個Controller可以包含多個Act
ActionResult類包括ExecuteResult方法, 當ActionResult物件返回後會執行此方法.
下面分層次的總結Controller 處理流程:
1. 頁面處理流程
傳送請求 –> UrlRoutingModule捕獲請求 –> MvcRouteHandler.GetHttpHandler() –> MvcHandler.ProcessRequest()
2.MvcHandler.ProcessRequest() 處理流程:
使用工廠方法獲取具體的Controller –> Controller.Execute() –> 釋放Controller物件
3.Controller.Execute() 處理流程
獲取Act
4.ActionResult.ExecuteResult() 處理流程
獲取IView物件-> 根據IView物件中的頁面路徑獲取Page類-> 呼叫IView.RenderView()
通過對MVC原始碼的分析,我們瞭解到Controller物件的職責是傳遞資料,獲取View物件(實現了IView介面的類),通知View物件顯示.
View物件的作用是顯示.雖然顯示的方法RenderView()是由Controller呼叫的,但是Controller僅僅是一個"指揮官"的作用, 具體的顯示邏輯仍然在View物件中.
需要注意IView介面與具體的ViewPage之間的聯絡.在Controller和View之間還存在著IView物件.對於ASP.NET程式提供了WebFormView物件實現了IView介面.WebFormView負責根據虛擬目錄獲取具體的Page類,然後呼叫Page.RenderView().
四.ActionResult解析
通過上面的流程,我們知道了ActionResult物件在整個流程中的作用.ActionResult是一個抽象類, 在Act
類名 | 抽象類 | 父類 | 功能 |
ContentResult | 根據內容的型別和編碼,資料內容. | ||
EmptyResult | 空方法. | ||
FileResult | abstract | 寫入檔案內容,具體的寫入方式在派生類中. | |
FileContentResult | FileResult | 通過 檔案byte[] 寫入檔案. | |
FilePathResult | FileResult | 通過 檔案路徑 寫入檔案. | |
FileStreamResult | FileResult | 通過 檔案Stream 寫入檔案. | |
HttpUnauthorizedResult | 丟擲401錯誤 | ||
JavaScriptResult | 返回javas | ||
JsonResult | 返回Json格式的資料 | ||
RedirectResult | 使用Response.Redirect重定向頁面 | ||
RedirectToRouteResult | 根據Route規則重定向頁面 | ||
ViewResultBase | abstract | 呼叫IView.Render() | |
PartialViewResult | ViewResultBase | 呼叫父類ViewResultBase 的ExecuteResult方法. 重寫了父類的FindView方法. 尋找使用者控制元件.ascx檔案 | |
ViewResult | ViewResultBase | 呼叫父類ViewResultBase 的ExecuteResult方法. 重寫了父類的FindView方法. 尋找頁面.aspx檔案 |
目前ASP.NET MVC還沒有提供官方的ActionResult列表.上面的列表是我在原始碼中分析得出的.有些解釋的可能不夠清楚,請諒解.
下面我將列舉各個ActionResult的例項.
五.例項應用
1.新增Controller
安裝了ASP.NET MVC後, 在專案上點選右鍵會找到新增Controller項:
2.新增Action
下面這個類提供了返回各種型別的ActionResult的Act
public class DemoController : Controller
{
/// <summary>
/// http://localhost:1847/Demo/ContentResultDemo
/// </summary>
/// <returns></returns>
public ActionResult ContentResultDemo()
{
string contentString = "ContextResultDemo!";
return Content(contentString);
}
/// <summary>
/// http://localhost:1847/Demo/EmptyResultDemo
/// </summary>
/// <returns></returns>
public ActionResult EmptyResultDemo()
{
return new EmptyResult();
}
/// <summary>
/// http://localhost:1847/Demo/FileContentResultDemo
/// </summary>
/// <returns></returns>
public ActionResult FileContentResultDemo()
{
FileStream fs = new FileStream(Server.MapPath(@"/resource/Images/1.gif"), FileMode.Open, FileAccess.Read);
byte[] buffer = new byte[Convert.ToInt32(fs.Length)];
fs.Read(buffer, 0, Convert.ToInt32(fs.Length));
return File(buffer, @"image/gif");
}
/// <summary>
/// http://localhost:1847/Demo/FilePathResultDemo
/// </summary>
/// <returns></returns>
public ActionResult FilePathResultDemo()
{
//可以將一個jpg格式的影象輸出為gif格式
return File(Server.MapPath(@"/resource/Images/2.jpg"), @"image/gif");
}
/// <summary>
/// http://localhost:1847/Demo/FileStreamResultDemo
/// </summary>
/// <returns></returns>
public ActionResult FileStreamResultDemo()
{
FileStream fs = new FileStream(Server.MapPath(@"/resource/Images/1.gif"), FileMode.Open, FileAccess.Read);
return File(fs, @"image/gif");
}
/// <summary>
/// http://localhost:1847/Demo/HttpUnauthorizedResultDemo
/// </summary>
/// <returns></returns>
public ActionResult HttpUnauthorizedResultDemo()
{
return new HttpUnauthorizedResult();
}
/// <summary>
/// http://localhost:1847/Demo/JavaScriptResultDemo
/// </summary>
/// <returns></returns>
public ActionResult JavaScriptResultDemo()
{
return JavaS
}
/// <summary>
/// http://localhost:1847/Demo/JsonResultDemo
/// </summary>
/// <returns></returns>
public ActionResult JsonResultDemo()
{
var tempObj = new { Controller = "DemoController", Act
return Json(tempObj);
}
/// <summary>
/// http://localhost:1847/Demo/RedirectToRouteResultDemo
/// </summary>
/// <returns></returns>
public ActionResult RedirectToRouteResultDemo()
{
return RedirectToAction(@"FileStreamResultDemo");
}
/// <summary>
/// http://localhost:1847/Demo/PartialViewResultDemo
/// </summary>
/// <returns></returns>
public ActionResult PartialViewResultDemo()
{
return PartialView();
}
/// <summary>
/// http://localhost:1847/Demo/RedirectToRouteResultDemo
/// </summary>
/// <returns></returns>
public ActionResult ViewResultDemo()
{
//如果沒有傳入View名稱, 預設尋找與Act
return View();
}
}
在文章最後提供有完整例項程式碼下載.
六.Controller 深入分析
在研究Controller/Act
1.Routing元件與MVC框架的結合
Routing元件和ASP.NET MVC並不是一個專案, 在ASP.NET MVC中僅僅是使用了Routing元件, 在原始碼中是通過dll的方式引用的.Routing元件已經包含在.net framework 3.5 sp1中了.而ASP.NET MVC還未出正式版.
那麼ASP.NET MVC是如何應用Routing元件的呢?
Routing元件獲取了Url中的資料後, 會將資料儲存在一個 RouteData 物件中.並將請求傳遞給一個實現了IRouteHandler介面的物件. 在Asp.net MVC中提供的MvcRouteHandler類實現了此介面, Routing 將請求傳遞給MvcRouteHandler的GetHttpHandler方法.下面是原始碼:
IRouteHandler介面:
public interface IRouteHandler
{
IHttpHandler GetHttpHandler(RequestContext requestContext);
}
MvcRouteHandler類:
public class MvcRouteHandler : IRouteHandler {
protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext) {
return new MvcHandler(requestContext);
}
#region IRouteHandler Members
IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext) {
return GetHttpHandler(requestContext);
}
#endregion
}
曾經我認為IRouteHandler是多餘的, 用IHttpHandler就夠了. 現在知道了為何要定義這個介面. 主要是為了傳遞RouteData物件.GetHttpHandler方法需要一個RequestContext 物件.RequestContext 是 System.Web.Routing程式集中的類, 裡面除了處理請求需要的HttpContextBase物件,還包括了一個RouteData物件.
RequestContext類:
public class RequestContext
{
public RequestContext(HttpContextBase httpContext, RouteData routeData);
public HttpContextBase HttpContext { get; }
public RouteData RouteData { get; }
}
Routing元件在Web.Config中註冊了一個HttpModule: System.Web.Routing.UrlRoutingModule, 而不是HttpHandler:
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
可惜看不到這個類的原始碼. 所有請求最後都是要傳遞給IHttpHandler物件處理, 主要的工作是編譯頁面, 所以我猜測這個Module將請求截獲後通過IRouteHandler介面物件獲取一個HttpHandler, 然後將處理移交給獲取到的HttpHandler.
ASP.NET MVC 中實現了IHttpHandler介面的類是MvcHandler, MvcRouteHandler.GetHttpHandler方法就是返回一個MvcHandler物件. MvcHandler類的建構函式需要傳入一個RequestContext物件. 實現的IHttpHandler介面方法處理過程中都需要依賴這個物件.
但是微軟在這裡的處理有一些不足. MvcHandler雖然實現了IHttpHandler介面但是不能被當作IHttpHandler介面使用. 因為IHttpHandler中沒有定義RequestContext屬性, 如果一個MvcHandler物件此屬性沒有賦值則會出錯, 也沒有將預設的無引數建構函式設定為private, 所以理論上可以很隨意的例項化一個MvcHandler而不為其RequestContext屬性賦值.
IRouteHandler想實現的語意是: 返回一個具有RequestContext屬性的IHttpHandler物件.
但是最後的實現結果是: 提供"返回IHttpHandler物件"的方法, 此方法接收RequestContext物件引數.
還需要注意ControllerContext類. 在Controller的處理過程中使用此物件作為儲存上下文資料的容器.下面是這幾個類的包含關係:
可以看到在ControllerContext中包含了RequestContext物件,但是又將RequestContext物件中的兩個屬性提取到自己的類中.如果僅僅是為了使用方便而這麼做, 個人認為不是一個好的設計.資料物件的儲存職責也應該明確,使用ControllerContext.RequestContext.RouteData 的方式更容易被人理解.
PS:這種方式類似於方法內聯.對於屬性JIT為了效率會幫助我們做內聯.而僅僅是為了使用方便.
2.IView 與 View物件的關係
所以從系統的角度上看, 實現了IView介面的物件才是View.
但是從實現效果上看, 具體的aspx或者ascx頁面才是View.
當第一次看到IView介面時我認為它應該是"View角色"需要實現的介面. 但是結果並不是這樣.
在我們的系統中View物件應該是aspx或者ascx檔案. 而且並不是所有的ActionResult都需要找到aspx或者ascx檔案, 事實上只有PartialViewResult 和 ViewResult 才會去尋找View物件.其他的ActionResult要麼是返回檔案, 要麼是跳轉等等.
那麼兩者的關係到底是怎樣的? 其實其中的過程需要牽扯到這幾個介面和類:
IViewEngine, ViewEngineResult, ViewEngineCollection
ViewEngine是View引擎, ViewEngineCollection是一個引擎集合,裡面儲存了各種尋找View的引擎.但是在目前的原始碼中只有WebFormViewEngine : VirtualPathProviderViewEngine : IViewEngine
這一系列WebForm使用的引擎.引擎的作用有兩個:
1.尋找Page/使用者控制元件的路徑
2.根據路徑建立IView物件.也就是根據頁面的物理檔案建立IView介面物件.
而且目前實現了IView介面的物件也只有一個:
WebFormView
WebFormViewEngine 根據頁面路徑, 將一個頁面地址轉化為一個WebFormView物件,也就是一個IView介面物件.
至此IView介面和Page頁面類仍然沒有任何關係, IView物件只是儲存了頁面的物理路徑.
接著在IView的Render事件中,根據物理路徑建立了一個頁面的object例項,注意看這一段程式碼:
object viewInstance = BuildManager.CreateInstanceFromVirtualPath(ViewPath, typeof(object));
if (viewInstance == null) {
throw new InvalidOperationException(
String.Format(
CultureInfo.CurrentUICulture,
MvcResources.WebFormViewEngine_ViewCouldNotBeCreated,
ViewPath));
}
ViewPage viewPage = viewInstance as ViewPage;
if (viewPage != null) {
RenderViewPage(viewContext, viewPage);
return;
}
ViewUserControl viewUserControl = viewInstance as ViewUserControl;
if (viewUserControl != null) {
RenderViewUserControl(viewContext, viewUserControl);
return;
}
viewInstance 就是通過物理路徑建立的頁面物件.但是他的型別是object, 而且程式嘗試將其分別轉化為ViewPage物件和ViewUserControl物件.
我想很多人都看到了這裡的設計不足.現在我們只能"約定": 所有的MVC中的頁面物件都必須繼承自ViewPage或者ViewUserControl類, 否則程式就會出錯.產生這種不足的原因就是IView介面和ViewPage沒有任何的耦合性, 完全是硬編碼進去的.
為什麼不讓頁面直接實現IView介面? 然後嘗試將頁面轉化為IView介面物件, 而不是ViewPage, 這樣才是好的設計. 其實微軟知道什麼是好的設計, 我猜測他們遇到的困難是Page物件和IView介面的衝突. 因為兩者都需要Render. 如果在IView中定義自己的Render名稱, 那就意味著ASP.NET MVC開發小組要自己處理頁面的顯示邏輯, 而現在ASP.NET WebForm模式下面的頁面顯示引擎又不能複用, 重新開發自己的一套顯示引擎成本又太大, 才出此下策.
以上只是猜測.這種設計的缺陷雖然可以接受, 但是真的是讓我好幾天陷入了看不懂程式碼的痛苦之中.還好, 現在可以解脫了.
七.如何在MVC專案中使用MVC原始碼專案
另外在為了跟蹤實現過程, 我將ASP.NET MVC的原始碼專案新增到了例項專案中, 其中有一些需要注意的地方:
1. 將例項專案中的System.Web.Mvc引用刪除, 改成專案引用.
2. 需要在Web.Config中註釋掉程式集引用:
<compilation debug="true">
<assemblies>
<add assembly="System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
<add assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
<add assembly="System.Web.Abstractions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
<add assembly="System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
<!-- <add assembly="System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>-->
<add assembly="System.Da
<add assembly="System.Xml.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
<add assembly="System.Da
</assemblies>
註釋掉的程式集存在於GAC中, 但是我們現在不希望使用GAC中的程式集, 而是引用專案.
3. 將View目錄下的Web.Config中的所有System.Web.Mvc相關的 PublicKeyToken 都修改為 null:
<pages
validateRequest="false"
pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<controls>
<add assembly="System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" namespace="System.Web.Mvc" tagPrefix="mvc" />
</controls>
</pages>
八.總結
首先很抱歉在本系列文章開篇時承諾的每日一篇僅僅堅持了2天.具體原因就不解釋了.這篇文章的出爐歷時半個月, 並且經歷了ASP.NET MVC版本從RC到RC2的演變. 在檢視MVC原始碼上花費了大量的時間, 希望付出的努力能夠為大家研究學習ASP.NET MVC帶來幫助. 我也會把這一系列的文章寫完, 關於ASP.NET MVC還有太多的地方沒有學習.
例項原始碼下載地址:
http://files.cnblogs.com/zhangziqiu/Asp.net-MVC-3-Demo.rar
Tag標籤: ASP.NET MVC,MVC,Controller,Routing,Act