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裡面有許多重要資訊,我會列出其重要成員和代表含意.
ControllerType此次執行Controller類型
(重要) FindAction透過此方法取得ActionDescriptor物件.
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參數就是調用Controller上Action方法鎖使用的參數.
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參數資訊 上面提到ReflectedControllerDescriptor的ActionDescriptor FindAction(ControllerContext controllerContext, string actionName)預設使用ReflectedActionDescriptor.
ReflectedActionDescriptor類別顧名思義就是依靠反射來取得Action的資訊
切入重點我們來看看ReflectedActionDescriptor如何實現Execute方法的吧
MethodInfo是從ReflectedControllerDescriptor利用反射取得執行Action方法資訊.
利用ExtractParameterFromDictionary方法將IDictionary<string, object> parameters傳入參數轉成可傳入方法物件.
透過ActionMethodDispatcher物件Execute方法執行Action方法(ActionMethodDispatcher透過Expression表達式動態建立方法並呼叫)
ActionMethodDispatcher的Expression表達式詳解會在後面做介紹
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; }
actionReturnValue 是Action方法的回傳值.
取得Action方法執行參數 上面提到Action使用參數會轉換到一個IDictionary<string, object>裡面.
在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包含幾個重要屬性
ParameterType:參數類型
ParameterName:參數名稱
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 ), 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參數綁定使用點和前置動作(這邊會發現很多Interface和abstract 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 許可協議。轉載請註明出處!