緊接著上篇asp.net mvc源碼分析-Action篇 ParameterDescriptor 文章 在ReflectedParameterBindingInfo有這個public override IModelBinder Binder屬性,同時在ControllerActionInvoker中也有一個類似的 protected internal ModelBinderDictionary Binders 看見這兩個屬性名稱我們就應該知道ModelBinderDictionary是IModelBinder的一個集合類,public class ModelBinderDictionary : IDictionary<Type, IModelBinder> 這裡是一個字典集合。
我們首先還是看看ReflectedParameterBindingInfo的Binder屬性吧:
public override IModelBinder Binder {
get {
IModelBinder binder = ModelBinders.GetBinderFromAttributes(_parameterInfo,
() => String.Format(CultureInfo.CurrentCulture, MvcResources.ReflectedParameterBindingInfo_MultipleConverterAttributes,
_parameterInfo.Name, _parameterInfo.Member));
return binder;
}
}
在ModelBinders中有一個屬性public static ModelBinderDictionary Binders,這個binders內容如下
ModelBinderDictionary binders = new ModelBinderDictionary() {
{ typeof(HttpPostedFileBase), new HttpPostedFileBaseModelBinder() },
{ typeof(byte[]), new ByteArrayModelBinder() },
{ typeof(Binary), new LinqBinaryModelBinder() }
};
說明預設的情況下就提供者3個Binder,但是這個屬性是共有靜態,所以我們可以往裡面添加自己的binder類,和前面文章講的Filiter以及後面要講的ValueProvider一樣,
如在 Application_Start()方法中 ModelBinders.Binders.Add(xxx,xxxx) 很不是很方便擴充了。
ModelBinders的GetBinderFromAttributes這個方法一看我們就能猜到它的邏輯了,
CustomModelBinderAttribute[] attrs = (CustomModelBinderAttribute[])element.GetCustomAttributes(typeof(CustomModelBinderAttribute), true /* inherit */);
擷取 當前參數的CustomModelBinderAttribute特性,如果有該特性就調用第一個特性的GetBinder()方法並返回其值,沒有特性則返回null,如果有多個特性則拋出異常,說明一個參數上是不可以有多個CustomModelBinderAttribute特性的,正樣 ReflectedParameterBindingInfo的binder屬性就設定好了。
下面 該輪到ControllerActionInvoker的Binders屬性,
protected internal ModelBinderDictionary Binders {
get {
if (_binders == null) {
_binders = ModelBinders.Binders;
}
return _binders;
}
set {
_binders = value;
}
}
可以 看到預設他返回的是ModelBinders.Binders。
接下來看看 IModelBinder binder = GetModelBinder(parameterDescriptor)這句究竟怎麼返回的binder,
return parameterDescriptor.BindingInfo.Binder ?? Binders.GetBinder(parameterDescriptor.ParameterType);
太簡單了 ,首先看看參數是否有binder特性,如果有就返回相應的binder,否者根據參數類型擷取對應的binder。
其 方法如下:
public IModelBinder GetBinder(Type modelType) { return GetBinder(modelType, true /* fallbackToDefault */); } public virtual IModelBinder GetBinder(Type modelType, bool fallbackToDefault) { if (modelType == null) { throw new ArgumentNullException("modelType"); } return GetBinder(modelType, (fallbackToDefault) ? DefaultBinder : null); } private IModelBinder GetBinder(Type modelType, IModelBinder fallbackBinder) { // Try to look up a binder for this type. We use this order of precedence: // 1. Binder returned from provider // 2. Binder registered in the global table // 3. Binder attribute defined on the type // 4. Supplied fallback binder IModelBinder binder = _modelBinderProviders.GetBinder(modelType); if (binder != null) { return binder; } if (_innerDictionary.TryGetValue(modelType, out binder)) { return binder; } binder = ModelBinders.GetBinderFromAttributes(modelType, () => String.Format(CultureInfo.CurrentCulture, MvcResources.ModelBinderDictionary_MultipleAttributes, modelType.FullName)); return binder ?? fallbackBinder; }
這裡需要注意binder選著的優先順序,(1)從_modelBinderProviders裡面找相應的binder
private ModelBinderProviderCollection _modelBinderProviders;
public ModelBinderDictionary()
: this(ModelBinderProviders.BinderProviders) {
}
internal ModelBinderDictionary(ModelBinderProviderCollection modelBinderProviders) {
_modelBinderProviders = modelBinderProviders;
}
public static class ModelBinderProviders {
private readonly static ModelBinderProviderCollection _binderProviders = new ModelBinderProviderCollection {
};
public static ModelBinderProviderCollection BinderProviders {
get {
return _binderProviders;
}
}
}
從這些代碼我們可以得知 預設情況下_modelBinderProviders裡面是沒有資料 ,那麼什麼時候這個集合有資料了,當我們在Application_Start()中調用ModelBinderProviders.BinderProviders.Add(xxx)就有資料了,
(2)從_innerDictionary中取資料,這個資料時什麼時候添加上去的了,看了ModelBinderDictionary的add放就明白了
public void Add(Type key, IModelBinder value) {
_innerDictionary.Add(key, value);
}
其實 ModelBinderDictionary內部很多方法都是圍繞著_innerDictionary集合操作的。
(3)從參數資料類型上擷取binder
(4)返貨預設的DefaultBinder,該屬性預設= new DefaultModelBinder()
現在 我們可以總結一下binder的優先順序(1)參數上的CustomModelBinderAttribute特性;(2)ModelBinderProviders.BinderProviders.Add(xxx)註冊的IModelBinderProvider;(3)ModelBinders的Binders;(4)參數類型上的CustomModelBinderAttribute特性;(5)返回預設的DefaultModelBinder
IValueProvider valueProvider = controllerContext.Controller.ValueProvider; 這句的講解我們放到後面的文章中吧,
string parameterName = parameterDescriptor.BindingInfo.Prefix ?? parameterDescriptor.ParameterName;這句擷取參數名稱,
Predicate<string> propertyFilter = GetPropertyFilter(parameterDescriptor);這個就只控制該參數時候需要綁定對應的值。
private static Predicate<string> GetPropertyFilter(ParameterDescriptor parameterDescriptor) {
ParameterBindingInfo bindingInfo = parameterDescriptor.BindingInfo;
return propertyName => BindAttribute.IsPropertyAllowed(propertyName, bindingInfo.Include.ToArray(), bindingInfo.Exclude.ToArray());
}
BindAttribute.IsPropertyAllowed如下:
internal static bool IsPropertyAllowed(string propertyName, string[] includeProperties, string[] excludeProperties) {
// We allow a property to be bound if its both in the include list AND not in the exclude list.
// An empty include list implies all properties are allowed.
// An empty exclude list implies no properties are disallowed.
bool includeProperty = (includeProperties == null) || (includeProperties.Length == 0) || includeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase);
bool excludeProperty = (excludeProperties != null) && excludeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase);
return includeProperty && !excludeProperty;
}
現在 終於看到了BindAttribute在申明地方用到了。
現在 我們來做一個自定的ModelBinder類怎麼做。
public class UserInfo { public string Name { set; get; } public string Age { set; get; } } public class UserInfoModelBinder : IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { object obj = Activator.CreateInstance(bindingContext.ModelType); foreach (PropertyInfo p in bindingContext.ModelType.GetProperties()) { ValueProviderResult vpResult= bindingContext.ValueProvider.GetValue(p.Name); if (vpResult != null) { object value = vpResult.ConvertTo(p.PropertyType); p.SetValue(obj, value, null); } } return obj; } } public class HomeController : Controller { public ActionResult Index([ModelBinder(typeof(UserInfoModelBinder))]UserInfo userInfo) { return Content("Name:" + userInfo.Name + " Age:" + userInfo.Age); // return View(); } }
運行結果