ASP.NET MVC 的請求處理核心在於 ControllerActionInvoker,其 InvokeAction 方法協調了授權、模型繫結、動作執行、結果執行等完整管線。本文聚焦於 ControllerDescriptor 與 ActionDescriptor 如何被建立、快取,以及動作選擇的核心邏輯。
public virtual bool InvokeAction(ControllerContext controllerContext, string actionName)
{
// 1. 取得 ControllerDescriptor(控制器描述器)
ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);
// 2. 尋找對應的 ActionDescriptor
ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);
// ... 後續管線略
}
protected virtual ControllerDescriptor GetControllerDescriptor(ControllerContext controllerContext)
{
Type controllerType = controllerContext.Controller.GetType();
// 從快取取得或建立 ReflectedControllerDescriptor
return DescriptorCache.GetDescriptor(controllerType,
() => new ReflectedControllerDescriptor(controllerType));
}
MVC 大量使用快取避免重複反射。ControllerDescriptorCache 繼承 ReaderWriterCache<TKey, TValue>,內部以 Dictionary<Type, ControllerDescriptor> 儲存。
internal sealed class ControllerDescriptorCache : ReaderWriterCache<Type, ControllerDescriptor>
{
public ControllerDescriptor GetDescriptor(Type controllerType, Func<ControllerDescriptor> creator)
{
return FetchOrCreateItem(controllerType, creator);
}
}
FetchOrCreateItem 邏輯:若快取命中直接回傳,否則呼叫 creator 建立實例並存入快取。
public ReflectedControllerDescriptor(Type controllerType)
{
_controllerType = controllerType;
_selector = new ActionMethodSelector(_controllerType); // 關鍵:動作方法選擇器
}
public ActionMethodSelector(Type controllerType)
{
ControllerType = controllerType;
PopulateLookupTables();
}
private void PopulateLookupTables()
{
// 取得所有 public instance 方法
MethodInfo[] allMethods = ControllerType.GetMethods(BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public);
// 過濾有效動作方法
MethodInfo[] actionMethods = Array.FindAll(allMethods, IsValidActionMethod);
// 分離具別名與無別名方法
AliasedMethods = Array.FindAll(actionMethods, IsMethodDecoratedWithAliasingAttribute);
NonAliasedMethods = actionMethods.Except(AliasedMethods)
.ToLookup(m => m.Name, StringComparer.OrdinalIgnoreCase);
}
ActionMethodSelector 在建構時一次性掃描控制器所有方法,建立查找表,極大提升後續 FindAction 效能。
protected virtual ActionDescriptor FindAction(ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName)
{
return controllerDescriptor.FindAction(controllerContext, actionName);
}
// ReflectedControllerDescriptor.FindAction
public override ActionDescriptor FindAction(ControllerContext controllerContext, string actionName)
{
MethodInfo matched = _selector.FindActionMethod(controllerContext, actionName);
return matched != null ? new ReflectedActionDescriptor(matched, actionName, this) : null;
}
public MethodInfo FindActionMethod(ControllerContext controllerContext, string actionName)
{
// 1. 收集具別名匹配的方法
List<MethodInfo> methods = GetMatchingAliasedMethods(controllerContext, actionName);
// 2. 加入無別名同名方法
methods.AddRange(NonAliasedMethods[actionName]);
// 3. 執行選擇過濾器
List<MethodInfo> finalMethods = RunSelectionFilters(controllerContext, methods);
// 4. 判斷結果
switch (finalMethods.Count)
{
case 0: return null;
case 1: return finalMethods[0];
default: throw CreateAmbiguousMatchException(finalMethods, actionName);
}
}
為避免重複 GetCustomAttributes,MVC 引入 ReflectedAttributeCache,以 ConcurrentDictionary 快取各類屬性:
ActionMethodSelectorAttribute(如 HttpGet, ActionName, NonAction)ActionNameSelectorAttributeFilterAttribute(動作、授權、結果、例外過濾器)private static ReadOnlyCollection<TAttribute> GetAttributes<TMemberInfo, TAttribute>(
ConcurrentDictionary<TMemberInfo, ReadOnlyCollection<TAttribute>> lookup,
TMemberInfo memberInfo)
where TAttribute : Attribute
where TMemberInfo : MemberInfo
{
return lookup.GetOrAdd(memberInfo,
mi => new ReadOnlyCollection<TAttribute>(
(TAttribute[])memberInfo.GetCustomAttributes(typeof(TAttribute), true)));
}
建立時會驗證方法是否可呼叫,拒絕條件:
ControllerBase 實例方法Index<T>()),但允許泛型集合參數(如 Index(List<string>))ref / outControllerType → ReflectedControllerDescriptor。ActionMethodSelector 查找表。MethodInfo 的各類屬性集合。這層層快取設計,確保 MVC 在高併發下仍能維持優異的路由解析效能。