iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 15
2
Software Development

從Asp.net框架角度進入Asp.net MVC原始碼系列 第 15

[Day15] Action方法如何被執行InvokeAction(一)

前言

前面介紹完 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方法.

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名稱,在呼叫ActionInvokerInvokeAction方法判斷呼叫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,取得順序如下

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

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

ControllerActionInvoker呼叫InvokeAction方法

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

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

取得 ControllerDescriptor & ActionDescriptor

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

ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);
ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);
  1. GetControllerDescriptor取得Controller封裝後的資訊(同步使用ReflectedControllerDescriptor).
  2. 取得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);
            }
        }             
    }
}

從程式碼得知

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

我們最常把Filter寫在ControllerAction上就是透過FilterAttributeFilterProviderGetFilters方法取得標籤並封裝成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方法中有兩個很重要的物件.

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

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

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

UML_ActionInvoker.PNG

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


上一篇
[Day14] 反轉起來~透過IOC解析來執行依賴反轉
下一篇
[Day16] MVC Filter 機制解密
系列文
從Asp.net框架角度進入Asp.net MVC原始碼30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言