上篇得知MVC預設透過DefaultControllerFactory
反射方式動態建立Controller
物件
本篇會分享我們常用到Controller
基礎類別和相關物件.
我有做一個可以針對於Asp.net MVC Debugger的專案,只要下中斷點就可輕易進入Asp.net MVC原始碼.
此篇同步發布在筆者Blog [Day12] 談談Controller幾個重要成員
ControllerBase
具有如下幾個重要的屬性
TempData
:將設置資料存於Session
中,生命週期除了當下請求, 導頁後仍可續存.ViewBag
:儲存Controller
向view
傳遞資料或變數 (型別dynamic
)ViewData
:儲存Controller
向view
傳遞資料或變數 (型別ViewDataDictionary
)雖說ViewBag
和ViewData
看起來使用不同的物件,但從程式碼了解到其實ViewBag
也是使用ViewData
引用.
public abstract class ControllerBase : IController
{
public ControllerContext ControllerContext { get; set; }
public TempDataDictionary TempData
{
get
{
if (ControllerContext != null && ControllerContext.IsChildAction)
{
return ControllerContext.ParentActionViewContext.TempData;
}
if (_tempDataDictionary == null)
{
_tempDataDictionary = new TempDataDictionary();
}
return _tempDataDictionary;
}
set { _tempDataDictionary = value; }
}
public dynamic ViewBag
{
get
{
if (_dynamicViewDataDictionary == null)
{
_dynamicViewDataDictionary = new DynamicViewDataDictionary(() => ViewData);
}
return _dynamicViewDataDictionary;
}
}
public ViewDataDictionary ViewData { get; set; }
}
上面說到TempData
字典集合生命週期除了當下請求, 導頁後仍可續存.原因是在SessionStateTempDataProvider
將資料存在Session
中
controllerContext.HttpContext.Session["__ControllerTempData"]
可以透過上面程式碼取得當前的TempData
字典集合物件.
ControllerBase
這個類別繼承IController
,前篇說到在HttpHandler ProcessRequest
方法會透過反射找到一個符合Http請求IController
介面物件.
並呼叫其Execute
方法
在Execute
做了幾件事情.
ControllerContext
物件,對於RequestContext
簡易封裝.ExecuteCore
呼叫Hock
方法(ExecuteCore
是一個抽象方法提供繼承他的物件實做,預設是Controller
類別)protected virtual void Execute(RequestContext requestContext)
{
if (requestContext == null)
{
throw new ArgumentNullException("requestContext");
}
if (requestContext.HttpContext == null)
{
throw new ArgumentException(MvcResources.ControllerBase_CannotExecuteWithNullHttpContext, "requestContext");
}
VerifyExecuteCalledOnce();
Initialize(requestContext);
using (ScopeStorage.CreateTransientScope())
{
ExecuteCore();
}
}
void IController.Execute(RequestContext requestContext)
{
Execute(requestContext);
}
在專案中Controller
是我們預設使用繼承控制器類別,此類別中定義了很多的輔助方法和屬性讓撰寫控制器變得簡單。Controller
類別除了直接繼承ControllerBase
之外,Controller
還顯式實現IController
和IAsyncController
介面,跟ASP.NET MVC
四大篩選器(IAuthorizationFilter,IActionFilter、IResultFilter,IExceptionFilter
)的4個介面。
public abstract class Controller :
ControllerBase,
IActionFilter,
IAuthenticationFilter,
IAuthorizationFilter,
IDisposable,
IExceptionFilter,
IResultFilter,
IAsyncController,
IAsyncManagerContainer
{
}
在Controller
重載實做ExecuteCore
方法.
主要透過GetActionName(RouteData)
取得執行的Action
名稱,並透過ActionInvoker
取得要Invoker的ActionInvoker
.
PossiblyLoadTempData
:建立載入TempData
PossiblySaveTempData
:儲存TempData
的資料protected override void ExecuteCore()
{
// If code in this method needs to be updated, please also check the BeginExecuteCore() and
// EndExecuteCore() methods of AsyncController to see if that code also must be updated.
PossiblyLoadTempData();
try
{
string actionName = GetActionName(RouteData);
if (!ActionInvoker.InvokeAction(ControllerContext, actionName))
{
HandleUnknownAction(actionName);
}
}
finally
{
PossiblySaveTempData();
}
}
第一次初始話ControllerContext
利用建構子
public ControllerContext(RequestContext requestContext, ControllerBase controller)
在ControllerBase.Initialize
方法對於ControllerContext
初始化,這個上下文資料封裝了許多此次請求的資料.
protected virtual void Initialize(RequestContext requestContext)
{
ControllerContext = new ControllerContext(requestContext, this);
}
後面對於繼承ControllerContext
的Context
傳入第一次初始化ControllerContext
物件,
在建構子函數把傳入ControllerContext
的RequestContext
資料填入繼承ControllerContext
物件中
下面是MVC有繼承ControllerContext
類別
在原始碼中可以看到ControllerContext(ControllerContext controllerContext)
很巧妙把自身類別當作建構子方法參數傳入.
Controller = controllerContext.Controller;
RequestContext = controllerContext.RequestContext;
主要是要把RequestContext
值給填充,之後就可以利用RequestContext
取得理面一些資料.
public class ControllerContext
{
internal const string ParentActionViewContextToken = "ParentActionViewContext";
private HttpContextBase _httpContext;
private RequestContext _requestContext;
private RouteData _routeData;
// parameterless constructor used for mocking
public ControllerContext()
{
}
protected ControllerContext(ControllerContext controllerContext)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
Controller = controllerContext.Controller;
RequestContext = controllerContext.RequestContext;
}
public ControllerContext(HttpContextBase httpContext, RouteData routeData, ControllerBase controller)
: this(new RequestContext(httpContext, routeData), controller)
{
}
public ControllerContext(RequestContext requestContext, ControllerBase controller)
{
if (requestContext == null)
{
throw new ArgumentNullException("requestContext");
}
if (controller == null)
{
throw new ArgumentNullException("controller");
}
RequestContext = requestContext;
Controller = controller;
}
//....
}
我們看一下Authorization
filter用到的參數AuthorizationContext
.
在InvokeAuthorizationFilters
方法將AuthorizationContext
初始化
AuthorizationContext context = new AuthorizationContext(controllerContext, actionDescriptor);
其中傳入參數controllerContext
是第一次透過ControllerBase.Initialize
初始化Context
.
public class AuthorizationContext : ControllerContext
{
// parameterless constructor used for mocking
public AuthorizationContext()
{
}
public AuthorizationContext(ControllerContext controllerContext)
: base(controllerContext)
{
}
public AuthorizationContext(ControllerContext controllerContext, ActionDescriptor actionDescriptor) : base(controllerContext)
{
if (actionDescriptor == null)
{
throw new ArgumentNullException("actionDescriptor");
}
ActionDescriptor = actionDescriptor;
}
public virtual ActionDescriptor ActionDescriptor { get; set; }
public ActionResult Result { get; set; }
}
之後再呼叫base(controllerContext)
利用ControllerContext
建構子把資料填充.
Asp.net MVC
對於為了方便我們使用控制器所以對於Controller
進行許多資料封裝,讓我們只要繼承Controller
就可以方便使用許多屬性.
下圖是Controller
核心類別關係圖.Controller
類別左右兩側有本次沒介紹到類別(之後會介紹到)
當我看到ControllerContext
的設計時讓我驚艷的,因為他把MVC用到Context
都關聯綁定到一個類別中.
因為在商業邏輯中會有許多Model
類別,且這些類別資料存在一定的相關性,我覺得這個設計可以使用可以大大改善資料傳遞上的麻煩,讓程式寫起來更安全,簡單
之後我會把上面的UML圖慢慢畫出來,一步一步揭開Asp.net MVC
面紗.