🚫 Ad Blocker Detected

Please disable your AD blocker to continue using this site. Ads help us keep the content free! please press keyboard F5 to refresh page after disabled AD blocker

請關閉廣告攔截器以繼續使用本網站。廣告有助於我們保證內容免費。謝謝! 關閉後請按 F5 刷新頁面

0%

Action方法如何被執行InvokeAction(一) (第15天)

Agenda

前言

前面介紹完 Asp.net MVC解析器和IOC容器之間關係

本篇要介紹Controller如何去呼叫使用的Action方法.

ExecuteCoreControllerBase類別提供給Controller來實作Hook方法.

我有做一個可以針對於Asp.net MVC Debugger的專案,只要下中斷點就可輕易進入Asp.net MVC原始碼.

InvokeAction方法

之前說到MVC呼叫ControllerBase.Execute方法,其中這個方法做了幾件事情

  1. VerifyExecuteCalledOnce方法對於同步請求做一個防呆機制(不允許同一時間處理相同請求)
  2. Initialize初始化資料
  3. 呼叫ExecuteCore抽象方法(由Controller實現)

ControllerBaseInvokeAction來執行並叫Action方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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來實現

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected override void ExecuteCore()
{
PossiblyLoadTempData();
try
{
string actionName = GetActionName(RouteData);
if (!ActionInvoker.InvokeAction(ControllerContext, actionName))
{
HandleUnknownAction(actionName);
}
}
finally
{
PossiblySaveTempData();
}
}

這個方法會呼叫GetActionName透過Route規則解析Action名稱,在呼叫ActionInvokerInvokeAction方法判斷呼叫Action方法是否呼叫成功.

取得執行的ActionInvoker(AsyncControllerActionInvoker)

ActionInvoker是一個在Controller屬性,一開始先判斷_actionInvoker是否為null如果是就會建立一個IActionInvoker物件.

1
2
3
4
5
6
7
8
9
10
11
12
public IActionInvoker ActionInvoker
{
get
{
if (_actionInvoker == null)
{
_actionInvoker = CreateActionInvoker();
}
return _actionInvoker;
}
set { _actionInvoker = value; }
}

讓我們來看看CreateActionInvoker方法如何建立IActionInvoker物件吧!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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,取得順序如下

  1. 透過解析器找尋是否有實現AsyncActionInvokerFactory物件
  2. 透過解析器找尋是否有實現IActionInvokerFactory物件
  3. 透過解析器IAsyncActionInvoker物件
  4. 透過解析器IActionInvoker物件
  5. 建立一個AsyncControllerActionInvoker物件

所以預設是使用AsyncControllerActionInvoker這個非同步ActionInvoker

ControllerActionInvoker呼叫InvokeAction方法

  • ControllerActionInvoker是同步版本
  • AsyncControllerActionInvoker是非同步版本

使用InvokeAction方法來調用我們使用的Action方法,並透過執行完回傳Bool辨別調用是否成功.

取得 ControllerDescriptor & ActionDescriptor

InvokeAction方法一開始會先取得ControllerDescriptorActionDescriptor兩個物件(把得到資訊進行封裝).

1
2
ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);
ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);
  1. GetControllerDescriptor取得Controller封裝後的資訊(同步使用ReflectedControllerDescriptor).
  2. 取得ActionDescriptor(ReflectedActionDescriptor)並在執行Execute方法要靠他來執行Action方法

FindAction返回一個ActionDescriptor.

這個物件對於日後呼叫Action方法有很重要地位.

1
2
3
4
5
6
7
8
9
10
11
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方法取得目前所有註冊過濾器.

1
2
3
4
if (actionDescriptor != null)
{
FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);
//....

呼叫GetFilters方法會取得Asp.net MVC註冊的所有Filter物件(提供一個織布點方便開發人員彈性做擴充).

1
2
3
4
5
6
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註冊最常用)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/// <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取得過濾器集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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);
}
}
}
}

從程式碼得知

  • GetControllerAttributesController取得,所有繼承FilterAttribute標籤.
  • GetActionAttributesAction取得,所有繼承FilterAttribute標籤.

我們最常把Filter寫在ControllerAction上就是透過FilterAttributeFilterProviderGetFilters方法取得標籤並封裝成Filter物件返回,使用.

FilterProviderCollection的GetFilters方法(額外註冊過濾器)

GetFilters方法利用CombinedItems取得所有IFilterProvider物件,再利用GetFilters方法逐一取得註冊Filter物件.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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方法中有兩個很重要的物件.

  1. ControllerDescriptor封裝Controller主要使用資訊
  2. ActionDescriptor封裝Action主要使用資訊,並利用裡面的Execute方法執行Action.

另外一點我們也了解Asp.net MVC如何實現AOP編寫方式,透過Attribute + Filter,讓系統更有擴展性.

目前Filter類別跟ControllerActionInvoker類別UML圖關係如下

UML_ActionInvoker.PNG

下篇會跟大家分享MVC Filter是在哪裡被呼叫且裡面Filter參數是如何被產生的.

__此文作者__:Daniel Shih(石頭)
__此文地址__: https://isdaniel.github.io/Ithelp-day15/
__版權聲明__:本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 3.0 TW 許可協議。轉載請註明出處!

如果本文對您幫助很大,可街口支付斗內鼓勵石頭^^