前言
前面介紹完 Asp.net MVC解析器和IOC容器之間關係
本篇要介紹Controller
如何去呼叫使用的Action
方法.
ExecuteCore
是ControllerBase
類別提供給Controller
來實作Hook方法.
我有做一個可以針對於Asp.net MVC Debugger的專案,只要下中斷點就可輕易進入Asp.net MVC原始碼.
InvokeAction方法
之前說到MVC呼叫ControllerBase.Execute
方法,其中這個方法做了幾件事情
VerifyExecuteCalledOnce
方法對於同步請求做一個防呆機制(不允許同一時間處理相同請求)Initialize
初始化資料- 呼叫
ExecuteCore
抽象方法(由Controller
實現)
在ControllerBase
會InvokeAction
來執行並叫Action
方法.
public abstract class ControllerBase : IController{
//....
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();
}
}
}
在Controller類別中 重要方法ExecuteCore()
在上面有說到ExecuteCore
抽象方法由Controller
來實現
protected override void ExecuteCore()
{
PossiblyLoadTempData();
try
{
string actionName = GetActionName(RouteData);
if (!ActionInvoker.InvokeAction(ControllerContext, actionName))
{
HandleUnknownAction(actionName);
}
}
finally
{
PossiblySaveTempData();
}
}
這個方法會呼叫GetActionName
透過Route
規則解析Action
名稱,在呼叫ActionInvoker
的InvokeAction
方法判斷呼叫Action
方法是否呼叫成功.
取得執行的ActionInvoker(AsyncControllerActionInvoker)
ActionInvoker
是一個在Controller
屬性,一開始先判斷_actionInvoker
是否為null
如果是就會建立一個IActionInvoker
物件.
public IActionInvoker ActionInvoker
{
get
{
if (_actionInvoker == null)
{
_actionInvoker = CreateActionInvoker();
}
return _actionInvoker;
}
set { _actionInvoker = value; }
}
讓我們來看看CreateActionInvoker
方法如何建立IActionInvoker
物件吧!
protected virtual IActionInvoker CreateActionInvoker()
{
IAsyncActionInvokerFactory asyncActionInvokerFactory = Resolver.GetService<IAsyncActionInvokerFactory>();
if (asyncActionInvokerFactory != null)
{
return asyncActionInvokerFactory.CreateInstance();
}
IActionInvokerFactory actionInvokerFactory = Resolver.GetService<IActionInvokerFactory>();
if (actionInvokerFactory != null)
{
return actionInvokerFactory.CreateInstance();
}
// Note that getting a service from the current cache will return the same instance for every request.
return Resolver.GetService<IAsyncActionInvoker>() ??
Resolver.GetService<IActionInvoker>() ??
new AsyncControllerActionInvoker();
}
透過CreateActionInvoker
方法來取得執行IActionInvoker
,取得順序如下
- 透過解析器找尋是否有實現
AsyncActionInvokerFactory
物件 - 透過解析器找尋是否有實現
IActionInvokerFactory
物件 - 透過解析器
IAsyncActionInvoker
物件 - 透過解析器
IActionInvoker
物件 - 建立一個
AsyncControllerActionInvoker
物件
所以預設是使用AsyncControllerActionInvoker
這個非同步ActionInvoker
ControllerActionInvoker呼叫InvokeAction方法
ControllerActionInvoker
是同步版本AsyncControllerActionInvoker
是非同步版本
使用InvokeAction
方法來調用我們使用的Action
方法,並透過執行完回傳Bool
辨別調用是否成功.
取得 ControllerDescriptor & ActionDescriptor
在InvokeAction
方法一開始會先取得ControllerDescriptor
和ActionDescriptor
兩個物件(把得到資訊進行封裝).
ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);
ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);
GetControllerDescriptor
取得Controller
封裝後的資訊(同步使用ReflectedControllerDescriptor
).- 取得
ActionDescriptor
(ReflectedActionDescriptor
)並在執行Execute
方法要靠他來執行Action
方法
FindAction
返回一個ActionDescriptor
.
這個物件對於日後呼叫Action
方法有很重要地位.
public override ActionDescriptor FindAction(ControllerContext controllerContext, string actionName)
{
//......
MethodInfo matched = _selector.FindActionMethod(controllerContext, actionName);
if (matched == null)
{
return null;
}
return new ReflectedActionDescriptor(matched, actionName, this);
}
ReflectedControllerDescriptor
利用反射取得要執行的Method
資料(MethodInfo),並封裝到ReflectedActionDescriptor
類別中.
Asp.net AOP機制揭密(Filter)
取得完ActionDescriptor
物件後,會先判斷actionDescriptor
是否建立成功,如果建立成功就會呼叫GetFilters
方法取得目前所有註冊過濾器.
if (actionDescriptor != null)
{
FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);
//....
呼叫GetFilters
方法會取得Asp.net MVC註冊的所有Filter
物件(提供一個織布點方便開發人員彈性做擴充).
private Func<ControllerContext, ActionDescriptor, IEnumerable<Filter>> _getFiltersThunk = FilterProviders.Providers.GetFilters;
protected virtual FilterInfo GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
return new FilterInfo(_getFiltersThunk(controllerContext, actionDescriptor));
}
_getFiltersThunk
是一個委派物件,預設使用FilterProviders.Providers.GetFilters
FilterProviderCollection
這個集合對象在前一篇Asp.net MVC DI
有介紹到,對於DI
做一個擴充點(CombinedItems
)屬性從容器中取得有對於IFilterProvider
註冊Filter
物件.
預設使用FilterProviders(FilterProviderCollection)
MVC
會透過FilterProviders.Providers
取得預設使用FilterProvider
,透過以下三個地方取得
GlobalFilterCollection
(在Global擴充)ControllerInstanceFilterProvider
(Controller
自行Override
)FilterAttributeFilterProvider
(提供Attribute
註冊最常用)
/// <summary>
/// 提供Filter AOP讀取的位置
/// </summary>
public static class FilterProviders
{
static FilterProviders()
{
Providers = new FilterProviderCollection();
Providers.Add(GlobalFilters.Filters);
Providers.Add(new FilterAttributeFilterProvider());
Providers.Add(new ControllerInstanceFilterProvider());
}
public static FilterProviderCollection Providers { get; private set; }
}
FilterAttributeFilterProvider(取得標籤的Filter)
IFilterProvider
介面提供一個方法GetFilters
取得過濾器集合
public interface IFilterProvider
{
IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor);
}
public class FilterAttributeFilterProvider : IFilterProvider
{
//....
public virtual IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
if (controllerContext.Controller != null)
{
foreach (FilterAttribute attr in GetControllerAttributes(controllerContext, actionDescriptor))
{
yield return new Filter(attr, FilterScope.Controller, order: null);
}
foreach (FilterAttribute attr in GetActionAttributes(controllerContext, actionDescriptor))
{
yield return new Filter(attr, FilterScope.Action, order: null);
}
}
}
}
從程式碼得知
GetControllerAttributes
從Controller
取得,所有繼承FilterAttribute
標籤.GetActionAttributes
從Action
取得,所有繼承FilterAttribute
標籤.
我們最常把
Filter
寫在Controller
或Action
上就是透過FilterAttributeFilterProvider
的GetFilters
方法取得標籤並封裝成Filter
物件返回,使用.
FilterProviderCollection的GetFilters方法(額外註冊過濾器)
GetFilters
方法利用CombinedItems
取得所有IFilterProvider
物件,再利用GetFilters
方法逐一取得註冊Filter
物件.
public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
//.....
IFilterProvider[] providers = CombinedItems;
List<Filter> filters = new List<Filter>();
for (int i = 0; i < providers.Length; i++)
{
IFilterProvider provider = providers[i];
foreach (Filter filter in provider.GetFilters(controllerContext, actionDescriptor))
{
filters.Add(filter);
}
}
filters.Sort(_filterComparer);
if (filters.Count > 1)
{
RemoveDuplicates(filters);
}
return filters;
}
小結
今天就先分享執行Action
前執行動作,在InvokeAction
方法中有兩個很重要的物件.
ControllerDescriptor
封裝Controller
主要使用資訊ActionDescriptor
封裝Action
主要使用資訊,並利用裡面的Execute
方法執行Action
.
另外一點我們也了解Asp.net MVC如何實現AOP編寫方式,透過Attribute + Filter,讓系統更有擴展性.
目前Filter
類別跟ControllerActionInvoker
類別UML圖關係如下
下篇會跟大家分享MVC Filter
是在哪裡被呼叫且裡面Filter
參數是如何被產生的.
如果本文對您幫助很大,可街口支付斗內鼓勵石頭^^