緊接著上篇asp.net mvc源碼分析-Action篇 Filter 中提到了 IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor);首先這個方法的目的很明白擷取當前Action參數名稱和值得一個字典。
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;
}
這個代碼邏輯很簡單吧,有前面的文章我們知道這裡的actionDescriptor 是一個ReflectedActionDescriptor執行個體,我們猜測ParameterDescriptor是Paramete的一個封裝類,具體返回應該是它的子類。 的GetParameters方法如下
public override ParameterDescriptor[] GetParameters() {
ParameterDescriptor[] parameters = LazilyFetchParametersCollection();
// need to clone array so that user modifications aren't accidentally stored
return (ParameterDescriptor[])parameters.Clone();
}
private ParameterDescriptor[] LazilyFetchParametersCollection() {
return DescriptorUtil.LazilyFetchOrCreateDescriptors<ParameterInfo, ParameterDescriptor>(
ref _parametersCache /* cacheLocation */,
MethodInfo.GetParameters /* initializer */,
parameterInfo => new ReflectedParameterDescriptor(parameterInfo, this) /* converter */);
}
一看到LazilyFetchOrCreateDescriptors這個名稱我們就知道 如果有就直接擷取,沒有就建立。
public static TDescriptor[] LazilyFetchOrCreateDescriptors<TReflection, TDescriptor>(ref TDescriptor[] cacheLocation, Func<TReflection[]> initializer, Func<TReflection, TDescriptor> converter) {
// did we already calculate this once?
TDescriptor[] existingCache = Interlocked.CompareExchange(ref cacheLocation, null, null);
if (existingCache != null) {
return existingCache;
}
TReflection[] memberInfos = initializer();
TDescriptor[] descriptors = memberInfos.Select(converter).Where(descriptor => descriptor != null).ToArray();
TDescriptor[] updatedCache = Interlocked.CompareExchange(ref cacheLocation, descriptors, null);
return updatedCache ?? descriptors;
}
這裡的memberInfos=MethodInfo.GetParameters()擷取Actin的所有參數。而converter=new ReflectedParameterDescriptor(parameterInfo, this),ReflectedParameterDescriptor建構函式如下:
public ReflectedParameterDescriptor(ParameterInfo parameterInfo, ActionDescriptor actionDescriptor) {
ParameterInfo = parameterInfo;
_actionDescriptor = actionDescriptor;
_bindingInfo = new ReflectedParameterBindingInfo(parameterInfo);
}
在這個ReflectedParameterDescriptor有個屬性需要我們注意一下,那就是DefaultValue
public override object DefaultValue {
get {
object value;
if (ParameterInfoUtil.TryGetDefaultValue(ParameterInfo, out value)) {
return value;
}
else {
return base.DefaultValue;
}
}
}
internal static class ParameterInfoUtil { public static bool TryGetDefaultValue(ParameterInfo parameterInfo, out object value) { // this will get the default value as seen by the VB / C# compilers // if no value was baked in, RawDefaultValue returns DBNull.Value object defaultValue = parameterInfo.DefaultValue; if (defaultValue != DBNull.Value) { value = defaultValue; return true; } // if the compiler did not bake in a default value, check the [DefaultValue] attribute DefaultValueAttribute[] attrs = (DefaultValueAttribute[])parameterInfo.GetCustomAttributes(typeof(DefaultValueAttribute), false); if (attrs == null || attrs.Length == 0) { value = default(object); return false; } else { value = attrs[0].Value; return true; } } }
這段代碼主要意思是先找到對象的parameterInfo.DefaultValue值,如果不是null這設定value=parameterInfo.DefaultValue並返回true,如果沒有找到我們就找參數是否有DefaultValueAttribute特性,如果有就返回設定value=attrs[0].Value並返回true,否則value=default(object) 並返回false。一旦返回false,ReflectedParameterDescriptor的DefaultValue就會返回null。從這段帶代碼我們需要注意在申明預設參數盡量寫成 public ActionResult Index(string name="majiang") 而不是 public ActionResult Index([DefaultValue("majiang")]string name)
現在我們再來看看建構函式中的那個ReflectedParameterBindingInfo,在參數綁定過程中並不是所有的參數都需要綁定資料的,有寫參數是不需要綁定資料。
ReflectedParameterBindingInfo的主要代碼如下:
public ReflectedParameterBindingInfo(ParameterInfo parameterInfo) {
_parameterInfo = parameterInfo;
ReadSettingsFromBindAttribute();
}
private void ReadSettingsFromBindAttribute() {
BindAttribute attr = (BindAttribute)Attribute.GetCustomAttribute(_parameterInfo, typeof(BindAttribute));
if (attr == null) {
return;
}
_exclude = new ReadOnlyCollection<string>(AuthorizeAttribute.SplitString(attr.Exclude));
_include = new ReadOnlyCollection<string>(AuthorizeAttribute.SplitString(attr.Include));
_prefix = attr.Prefix;
}
ReadSettingsFromBindAttribute方法主要是擷取參數的BindAttribute來初始化exclude排除參數,include包含參數。這個類還有一個比較特殊的屬性
public override IModelBinder Binder 這個將放到後面來說說。
預設或則一般情況(簡單類型)下我們不考慮什麼排除參數的情況 擷取到的BindAttribute為null。
現在我們已經得到Action的Parameters的一個封裝對象集合ParameterDescriptor[]。同一個Action看你多次調用為了彼此不影響所以這裡需要把這個ParameterDescriptor[]集合給複製一份。 (ParameterDescriptor[])parameters.Clone();
緊接下來就是根據parameterDescriptor來擷取真正值了,調用GetParameterValue方法。
protected virtual object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor) { // collect all of the necessary binding properties 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); // finally, call into the binder 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; }
這段代碼說白了就是通過 binder.BindModel方法來擷取值,如果沒有找到就返回parameterDescriptor.DefaultValue。有關binder.BindModel這個方法很是複雜,需要IModelBinder、IValueProvider、ModelMetadataProvider這幾個東西,所以我們將放到後面來統一講解。