🚫 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(二) (第17天)

Agenda

前言

上篇揭開MVC常用的過濾器如何被獲取呼叫跟基本介紹.

前幾篇有介紹ControllerDescriptor,ActionDescriptor兩個物件,今天會來細部探討他們裡面有哪些重要成員.

本篇會繼續分析呼叫Action方法邏輯和在過程中有用到重要物件跟動作.

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

ControllerActionInvoker方法 重要InvokeAction方法

前面有說ControllerActionInvoker類別最重要的就是InvokeAction方法,因為主要透過他去呼叫ActionResult抽象類別ExecuteResult方法.

InvokeAction有兩個參數

  • ControllerContext:對於RequestContext,RouteData,使用Controller資訊封裝.
  • actionName:此次呼叫方法(從RouteData取得action值)
1
public virtual bool InvokeAction(ControllerContext controllerContext, string actionName)

InvokeAction方法除了呼叫ExecuteResult方法外還做了其他事情,對於藉由ControllerContext封裝兩個物件.

  • ControllerDescriptor
  • ActionDescriptor

這兩個物件在此方法中很重要.

1
2
ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);
ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);

取得ControllerDescriptor(ReflectedControllerDescriptor)

GetControllerDescriptor會返回一個ReflectedControllerDescriptor物件.

1
2
3
4
5
6
7
8
9
protected virtual ControllerDescriptor GetControllerDescriptor(ControllerContext controllerContext)
{
Type controllerType = controllerContext.Controller.GetType();
ControllerDescriptor controllerDescriptor = DescriptorCache.GetDescriptor(
controllerType: controllerType,
creator: (Type innerType) => new ReflectedControllerDescriptor(innerType),
state: controllerType);
return controllerDescriptor;
}

ReflectedControllerDescriptor裡面有許多重要資訊,我會列出其重要成員和代表含意.

  1. ControllerType此次執行Controller類型
  2. (重要)FindAction透過此方法取得ActionDescriptor物件.
  3. GetFilterAttributes方法會透過此物件取得AcitonFilter(掛載在Controller上)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public abstract class ControllerDescriptor : ICustomAttributeProvider, IUniquelyIdentifiable
{
public virtual string ControllerName
{
get
{
string typeName = ControllerType.Name;
if (typeName.EndsWith("Controller", StringComparison.OrdinalIgnoreCase))
{
return typeName.Substring(0, typeName.Length - "Controller".Length);
}

return typeName;
}
}

public abstract Type ControllerType { get; }

public abstract ActionDescriptor FindAction(ControllerContext controllerContext, string actionName);

public abstract ActionDescriptor[] GetCanonicalActions();

public virtual IEnumerable<FilterAttribute> GetFilterAttributes(bool useCache)
{
return GetCustomAttributes(typeof(FilterAttribute), inherit: true).Cast<FilterAttribute>();
}

public virtual bool IsDefined(Type attributeType, bool inherit)
{
if (attributeType == null)
{
throw new ArgumentNullException("attributeType");
}

return false;
}
}

ReflectedControllerDescriptor實現FindAction抽象方法.

主要透過反射取得此Controller物件中相對應Action名稱的方法,並把他封裝到ReflectedActionDescriptor類別中返回.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public override ActionDescriptor FindAction(ControllerContext controllerContext, string actionName)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
if (String.IsNullOrEmpty(actionName))
{
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
}

MethodInfo matched = _selector.FindActionMethod(controllerContext, actionName);
if (matched == null)
{
return null;
}

return new ReflectedActionDescriptor(matched, actionName, this);
}

ActionDescriptor(ReflectedActionDescriptor)

執行每一個Action方法會通過ActionDescriptor物件,所以ActionDescriptor是另一個對於InovkeAction方法來說很重要物件

ActionDescriptor抽象類別中有許多重要的成員

  • Execute:Action執行呼叫方法,其中裡面的parameters參數就是調用ControllerAction方法鎖使用的參數.
  • GetFilterAttributes:回傳在Action方法上的所有Filter標籤
  • GetFilters:返回一個FilterInfo物件,這個物件可以得到應用在該Action方法上所有filter
  • ActionName:Action方法名稱
1
2
3
4
5
6
7
8
9
10
11
12
public abstract class ActionDescriptor : ICustomAttributeProvider, IUniquelyIdentifiable
{
//....
public virtual bool IsDefined(Type attributeType, bool inherit);
public virtual IEnumerable<FilterAttribute> GetFilterAttributes(bool useCache);
public abstract ParameterDescriptor[] GetParameters();
public abstract object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters);
public virtual FilterInfo GetFilters();
public abstract string ActionName { get; }
public abstract ControllerDescriptor ControllerDescriptor { get; }
public virtual string UniqueId { get; }
}

繼承這個抽象類的子類就會擁有一種特性描述此次執行Action方法特徵和如何去使用Execute方法.

ReflectedActionDescriptor 取得ActionMethod參數資訊

上面提到ReflectedControllerDescriptorActionDescriptor FindAction(ControllerContext controllerContext, string actionName)預設使用ReflectedActionDescriptor.

ReflectedActionDescriptor類別顧名思義就是依靠反射來取得Action的資訊

切入重點我們來看看ReflectedActionDescriptor如何實現Execute方法的吧

  1. MethodInfo是從ReflectedControllerDescriptor利用反射取得執行Action方法資訊.
  2. 利用ExtractParameterFromDictionary方法將IDictionary<string, object> parameters傳入參數轉成可傳入方法物件.
  3. 透過ActionMethodDispatcher物件Execute方法執行Action方法(ActionMethodDispatcher透過Expression表達式動態建立方法並呼叫)

ActionMethodDispatcherExpression表達式詳解會在後面做介紹

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public MethodInfo MethodInfo { get; private set; }

public override object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters)
{
//....
ParameterInfo[] parameterInfos = MethodInfo.GetParameters();
object[] parametersArray = new object[parameterInfos.Length];
for (int i = 0; i < parameterInfos.Length; i++)
{
ParameterInfo parameterInfo = parameterInfos[i];
object parameter = ExtractParameterFromDictionary(parameterInfo, parameters, MethodInfo);
parametersArray[i] = parameter;
}

ActionMethodDispatcher dispatcher = DispatcherCache.GetDispatcher(MethodInfo);
object actionReturnValue = dispatcher.Execute(controllerContext.Controller, parametersArray);
return actionReturnValue;
}

actionReturnValueAction方法的回傳值.

取得Action方法執行參數

上面提到Action使用參數會轉換到一個IDictionary<string, object>裡面.

  • key:參數名稱
  • value:參數值

ActionFitlerAttribute.OnActionExcuting重載方法,參數ActionExecutingContext物件中有一個屬性public virtual IDictionary<string, object> ActionParameters { get; set; }
他透過IValueProvider解析完傳入字串轉換成一個存放參數字典.

讓我們了解一下這部分是如何完成.

1
2
3
4
5
6
7
8
9
10
11
protected virtual IDictionary<string, object> GetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
Dictionary<string, object> parametersDict = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
ParameterDescriptor[] parameterDescriptors = actionDescriptor.GetParameters();

foreach (ParameterDescriptor parameterDescriptor in parameterDescriptors)
{
parametersDict[parameterDescriptor.ParameterName] = GetParameterValue(controllerContext, parameterDescriptor);
}
return parametersDict;
}

在呼叫GetParameters方法返回一個ParameterDescriptor[]陣列(ParameterDescriptor存放參數相關資訊),主要呼叫ActionDescriptorHelper.GetParameters,利用反射取得MethodInfo.GetParameters在將裡面資訊封裝到ReflectedParameterDescriptor物件中.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public override ParameterDescriptor[] GetParameters()
{
return ActionDescriptorHelper.GetParameters(this, MethodInfo, ref _parametersCache);
}

public static ParameterDescriptor[] GetParameters(ActionDescriptor actionDescriptor, MethodInfo methodInfo, ref ParameterDescriptor[] parametersCache)
{
ParameterDescriptor[] parameters = LazilyFetchParametersCollection(actionDescriptor, methodInfo, ref parametersCache);

return (ParameterDescriptor[])parameters.Clone();
}

private static ParameterDescriptor[] LazilyFetchParametersCollection(ActionDescriptor actionDescriptor, MethodInfo methodInfo, ref ParameterDescriptor[] parametersCache)
{
return DescriptorUtil.LazilyFetchOrCreateDescriptors(
cacheLocation: ref parametersCache,
initializer: (CreateDescriptorState state) => state.MethodInfo.GetParameters(),
converter: (ParameterInfo parameterInfo, CreateDescriptorState state) => new ReflectedParameterDescriptor(parameterInfo, state.ActionDescriptor),
state: new CreateDescriptorState() { ActionDescriptor = actionDescriptor, MethodInfo = methodInfo });
}

ReflectedParameterDescriptor包含幾個重要屬性

  1. ParameterType:參數類型
  2. ParameterName:參數名稱
  3. DefaultValue:參數預設值

上面幾個為Action參數元數據資料.

利用ReflectedParameterDescriptor之前封裝方法參數資訊對於GetParameterValue方法執行物件建立.

GetParameterValue方法中有幾個重要的Field

  • IModelBinder使用DefaultModelBinder來綁定使用參數
  • IValueProvider依靠ValueProviderFactories來取使用哪個Provider得並綁訂傳入參數資料.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protected virtual object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor)
{
Type parameterType = parameterDescriptor.ParameterType;
IModelBinder binder = GetModelBinder(parameterDescriptor);
IValueProvider valueProvider = controllerContext.Controller.ValueProvider;
string parameterName = parameterDescriptor.BindingInfo.Prefix ?? parameterDescriptor.ParameterName;
Predicate<string> propertyFilter = GetPropertyFilter(parameterDescriptor);

ModelBindingContext bindingContext = new ModelBindingContext()
{
FallbackToEmptyPrefix = (parameterDescriptor.BindingInfo.Prefix == null), // only fall back if prefix not specified
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, parameterType),
ModelName = parameterName,
ModelState = controllerContext.Controller.ViewData.ModelState,
PropertyFilter = propertyFilter,
ValueProvider = valueProvider
};

object result = binder.BindModel(controllerContext, bindingContext);
return result ?? parameterDescriptor.DefaultValue;
}

小結:

介紹Action參數綁定使用點和前置動作(這邊會發現很多Interfaceabstract class,因為MVC提供許多可以替換點給開發人員擴充)

InvokeAction方法很重要,他的職責是執行使用者請求的Action方法,在此方法中有兩個核心物件.

  • ControllerDescriptor
  • ActionDescriptor

這兩個物件封裝後續呼叫Action需要的資訊,特別是ActionDescriptor裡面有一個Execute方法(靠他來呼叫Action方法)

另外也簡單介紹IDictionary<string, object>這個字典封裝了傳入Action方法的參數,

最後帶了點Model綁訂相關使用類別

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

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