iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 17
2

前言

上篇揭開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值)
public virtual bool InvokeAction(ControllerContext controllerContext, string actionName)

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

  • ControllerDescriptor
  • ActionDescriptor

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

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

取得ControllerDescriptor(ReflectedControllerDescriptor)

GetControllerDescriptor會返回一個ReflectedControllerDescriptor物件.

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上)
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類別中返回.

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方法名稱
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表達式詳解會在後面做介紹

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解析完傳入字串轉換成一個存放參數字典.

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

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物件中.

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得並綁訂傳入參數資料.
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綁訂相關使用類別


上一篇
[Day16] MVC Filter 機制解密
下一篇
[Day18] 提供ModelBing幾個重要功臣(Model)
系列文
從Asp.net框架角度進入Asp.net MVC原始碼30

尚未有邦友留言

立即登入留言