ASP.NET Core MVC中所提供的Model Binding功能簡單但實用,其主要目的是將請求中包含的數據映射到action的方法參數中。這樣就避免了開發者像在Web Forms時代那樣須要從Request類中手動獲取數據的繁鎖操做,直接提升了開發效率。此功能繼承自ASP.NET MVC,因此熟悉上一代框架開發的工程師,能夠毫無障礙地繼續享有它的便利。html
本文想要探索下Model Binding相關的內容,這裏先從源碼中找到其發生的時機與場合。緩存
在ControllerActionInvoker類的Next方法內部,能夠看到對BindArgumentsAsync方法的調用,這裏即會對方法的參數進行綁定數據的處理。框架
private Task Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted) { switch (next) { case State.ActionBegin: { var controllerContext = _controllerContext; _cursor.Reset(); _instance = _cacheEntry.ControllerFactory(controllerContext); _arguments = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); var task = BindArgumentsAsync(); if (task.Status != TaskStatus.RanToCompletion) { next = State.ActionNext; return task; } goto case State.ActionNext; } ... } }
此方法又調用了ControllerActionInvokerCacheEntry類中ControllerBinderDelegate屬性,該屬性是一個delegate方法,因此傳入參數後可直接執行處理。async
private Task BindArgumentsAsync() { ... return _cacheEntry.ControllerBinderDelegate(_controllerContext, _instance, _arguments); }
建立ControllerActionInvokerCacheEntry的地方是前兩篇文章(Controller,Action)中已經提到過的ControllerActionInvokerCache類。ide
public (ControllerActionInvokerCacheEntry cacheEntry, IFilterMetadata[] filters) GetCachedResult(ControllerContext controllerContext) { ... if (!cache.Entries.TryGetValue(actionDescriptor, out var cacheEntry)) { ... var propertyBinderFactory = ControllerBinderDelegateProvider.CreateBinderDelegate( _parameterBinder, _modelBinderFactory, _modelMetadataProvider, actionDescriptor); var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor); cacheEntry = new ControllerActionInvokerCacheEntry( filterFactoryResult.CacheableFilters, controllerFactory, controllerReleaser, propertyBinderFactory, objectMethodExecutor, actionMethodExecutor); cacheEntry = cache.Entries.GetOrAdd(actionDescriptor, cacheEntry); } ... return (cacheEntry, filters); }
因而跟蹤至ControllerBinderDelegateProvider類,找到CreateBinderDelegate方法。ui
public static ControllerBinderDelegate CreateBinderDelegate( ParameterBinder parameterBinder, IModelBinderFactory modelBinderFactory, IModelMetadataProvider modelMetadataProvider, ControllerActionDescriptor actionDescriptor) { ... var parameterBindingInfo = GetParameterBindingInfo(modelBinderFactory, modelMetadataProvider, actionDescriptor); ... return Bind; async Task Bind(ControllerContext controllerContext, object controller, Dictionary<string, object> arguments) { var valueProvider = await CompositeValueProvider.CreateAsync(controllerContext); var parameters = actionDescriptor.Parameters; for (var i = 0; i < parameters.Count; i++) { var parameter = parameters[i]; var bindingInfo = parameterBindingInfo[i]; var modelMetadata = bindingInfo.ModelMetadata; if (!modelMetadata.IsBindingAllowed) { continue; } var result = await parameterBinder.BindModelAsync( controllerContext, bindingInfo.ModelBinder, valueProvider, parameter, modelMetadata, value: null); if (result.IsModelSet) { arguments[parameter.Name] = result.Model; } } ... } }
這裏能夠看到建立綁定的delegate方法,與之對應的是以前那句_cacheEntry.ControllerBinderDelegate(_controllerContext, _instance, _arguments)
代碼。this
public virtual async Task<ModelBindingResult> BindModelAsync( ActionContext actionContext, IModelBinder modelBinder, IValueProvider valueProvider, ParameterDescriptor parameter, ModelMetadata metadata, object value) { ... var modelBindingContext = DefaultModelBindingContext.CreateBindingContext( actionContext, valueProvider, metadata, parameter.BindingInfo, parameter.Name); modelBindingContext.Model = value; ... await modelBinder.BindModelAsync(modelBindingContext); ... var modelBindingResult = modelBindingContext.Result; ... return modelBindingResult; }
到了此處,就是旅程的終點。ParameterBinder類的BindModelAsync中能夠找到對IModelBinder類型的BindModelAsync方法的調用。Model Binding這一操做即是在此時此地實現的。code
接下來的疑問有兩處,modelBinder是如何產生的,請求中的數據又是怎樣與modelBinder發生聯繫。orm
回到ControllerBinderDelegateProvider類的CreateBinderDelegate方法,能夠看到其中調用了GetParameterBindingInfo方法。htm
private static BinderItem[] GetParameterBindingInfo( IModelBinderFactory modelBinderFactory, IModelMetadataProvider modelMetadataProvider, ControllerActionDescriptor actionDescriptor) { var parameters = actionDescriptor.Parameters; ... var parameterBindingInfo = new BinderItem[parameters.Count]; for (var i = 0; i < parameters.Count; i++) { var parameter = parameters[i]; ... var binder = modelBinderFactory.CreateBinder(new ModelBinderFactoryContext { BindingInfo = parameter.BindingInfo, Metadata = metadata, CacheToken = parameter, }); parameterBindingInfo[i] = new BinderItem(binder, metadata); } return parameterBindingInfo; }
這裏的代碼很明顯地說明了modelBinder由ModelBinderFactory類的CreateBinder方法建立。
public IModelBinder CreateBinder(ModelBinderFactoryContext context) { ... IModelBinder binder; if (TryGetCachedBinder(context.Metadata, context.CacheToken, out binder)) { return binder; } var providerContext = new DefaultModelBinderProviderContext(this, context); binder = CreateBinderCoreUncached(providerContext, context.CacheToken); ... AddToCache(context.Metadata, context.CacheToken, binder); return binder; }
CreateBinder方法內部中若是緩存能夠取到值,則從緩存內取值並直接返回,不然經過CreateBinderCoreUncached方法取值。
private IModelBinder CreateBinderCoreUncached(DefaultModelBinderProviderContext providerContext, object token) { ... IModelBinder result = null; for (var i = 0; i < _providers.Length; i++) { var provider = _providers[i]; result = provider.GetBinder(providerContext); if (result != null) { break; } } ... return result; }
這裏的providers集合又包含哪些數據呢?能夠從MvcCoreMvcOptionsSetup類中找到答案。
public void Configure(MvcOptions options) { // Set up ModelBinding options.ModelBinderProviders.Add(new BinderTypeModelBinderProvider()); options.ModelBinderProviders.Add(new ServicesModelBinderProvider()); options.ModelBinderProviders.Add(new BodyModelBinderProvider(options.InputFormatters, _readerFactory, _loggerFactory, options)); options.ModelBinderProviders.Add(new HeaderModelBinderProvider()); options.ModelBinderProviders.Add(new FloatingPointTypeModelBinderProvider()); options.ModelBinderProviders.Add(new EnumTypeModelBinderProvider(options)); options.ModelBinderProviders.Add(new SimpleTypeModelBinderProvider()); options.ModelBinderProviders.Add(new CancellationTokenModelBinderProvider()); options.ModelBinderProviders.Add(new ByteArrayModelBinderProvider()); options.ModelBinderProviders.Add(new FormFileModelBinderProvider()); options.ModelBinderProviders.Add(new FormCollectionModelBinderProvider()); options.ModelBinderProviders.Add(new KeyValuePairModelBinderProvider()); options.ModelBinderProviders.Add(new DictionaryModelBinderProvider()); options.ModelBinderProviders.Add(new ArrayModelBinderProvider()); options.ModelBinderProviders.Add(new CollectionModelBinderProvider()); options.ModelBinderProviders.Add(new ComplexTypeModelBinderProvider()); ... }
以上即是.NET Core MVC中全部被框架支持的ModelBinderProvider。
以一個最典型的FormCollectionModelBinderProvider爲例。它以Metadata.ModelType的類型做爲判斷依據,若是是IFormCollection類型的話,則返回一個FormCollectionModelBinder對象。
public IModelBinder GetBinder(ModelBinderProviderContext context) { ... var modelType = context.Metadata.ModelType; ... if (modelType == typeof(IFormCollection)) { var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>(); return new FormCollectionModelBinder(loggerFactory); } return null; }
在CreateBinderCoreUncached方法的循環體內部會依次嘗試ModelBinderProvider們是否能建立合適的ModelBinder,一旦可以生成ModelBinder,則跳出當前循環,以這個對象做爲返回值。
有了ModelBinder,還須要有數據才能進行綁定。而爲ModelBinder提供數據的是一些ValueProvider。
MvcCoreMvcOptionsSetup類的Configure方法裏,再往下找,能夠看到ValueProvider們的蹤跡。更確切地是與之對應的工廠類們。
public void Configure(MvcOptions options) { ... // Set up ValueProviders options.ValueProviderFactories.Add(new FormValueProviderFactory()); options.ValueProviderFactories.Add(new RouteValueProviderFactory()); options.ValueProviderFactories.Add(new QueryStringValueProviderFactory()); options.ValueProviderFactories.Add(new JQueryFormValueProviderFactory()); ... }
以FormValueProviderFactory爲例,看一下其內部:
public Task CreateValueProviderAsync(ValueProviderFactoryContext context) { ... var request = context.ActionContext.HttpContext.Request; if (request.HasFormContentType) { // Allocating a Task only when the body is form data. return AddValueProviderAsync(context); } return Task.CompletedTask; } private static async Task AddValueProviderAsync(ValueProviderFactoryContext context) { var request = context.ActionContext.HttpContext.Request; var valueProvider = new FormValueProvider( BindingSource.Form, await request.ReadFormAsync(), CultureInfo.CurrentCulture); context.ValueProviders.Add(valueProvider); }
經過CreateValueProviderAsync方法能夠獲得一個FormValueProvider對象。
而這些ValueProviderFactory所建立的ValueProvider又統一被CompositeValueProvider類的CreateAsync方法聚合成CompositeValueProvider這個集合對象的內部元素。
public static async Task<CompositeValueProvider> CreateAsync( ActionContext actionContext, IList<IValueProviderFactory> factories) { var valueProviderFactoryContext = new ValueProviderFactoryContext(actionContext); for (var i = 0; i < factories.Count; i++) { var factory = factories[i]; await factory.CreateValueProviderAsync(valueProviderFactoryContext); } return new CompositeValueProvider(valueProviderFactoryContext.ValueProviders); }
再到ControllerBinderDelegateProvider類的CreateBinderDelegate方法中,找到valueProvider建立的起始點。
async Task Bind(ControllerContext controllerContext, object controller, Dictionary<string, object> arguments) { var valueProvider = await CompositeValueProvider.CreateAsync(controllerContext); var parameters = actionDescriptor.Parameters; for (var i = 0; i < parameters.Count; i++) { var parameter = parameters[i]; var bindingInfo = parameterBindingInfo[i]; var modelMetadata = bindingInfo.ModelMetadata; if (!modelMetadata.IsBindingAllowed) { continue; } var result = await parameterBinder.BindModelAsync( controllerContext, bindingInfo.ModelBinder, valueProvider, parameter, modelMetadata, value: null); if (result.IsModelSet) { arguments[parameter.Name] = result.Model; } } ... }
所獲得的valueProvider在ParameterBinder類的BindModelAsync方法裏還要再做進一步的處理。先做爲參數傳入建立DefaultModelBindingContext的方法:
var modelBindingContext = DefaultModelBindingContext.CreateBindingContext( actionContext, valueProvider, metadata, parameter.BindingInfo, parameter.Name);
再對ValueProvider做過濾處理:
return new DefaultModelBindingContext() { ActionContext = actionContext, BinderModelName = binderModelName, BindingSource = bindingSource, PropertyFilter = propertyFilterProvider?.PropertyFilter, // Because this is the top-level context, FieldName and ModelName should be the same. FieldName = binderModelName ?? modelName, ModelName = binderModelName ?? modelName, IsTopLevelObject = true, ModelMetadata = metadata, ModelState = actionContext.ModelState, OriginalValueProvider = valueProvider, ValueProvider = FilterValueProvider(valueProvider, bindingSource), ValidationState = new ValidationStateDictionary(), };
FilterValueProvider方法最終會調用CompositeValueProvider類的Filter方法,以獲得全部合適的valueProvider。
public IValueProvider Filter(BindingSource bindingSource) { ... var filteredValueProviders = new List<IValueProvider>(); foreach (var valueProvider in this.OfType<IBindingSourceValueProvider>()) { var result = valueProvider.Filter(bindingSource); if (result != null) { filteredValueProviders.Add(result); } } ... return new CompositeValueProvider(filteredValueProviders); }
那麼當在ModelBinder的BindModelAsync方法裏須要獲取數據時,以FloatModelBinder爲例:
public Task BindModelAsync(ModelBindingContext bindingContext) { ... var modelName = bindingContext.ModelName; var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName); ... }
會試圖從已過濾的ValueProvider中獲取值。這時仍是利用了CompositeValueProvider類中的方法。
public virtual ValueProviderResult GetValue(string key) { // Performance-sensitive // Caching the count is faster for IList<T> var itemCount = Items.Count; for (var i = 0; i < itemCount; i++) { var valueProvider = Items[i]; var result = valueProvider.GetValue(key); if (result != ValueProviderResult.None) { return result; } } return ValueProviderResult.None; }
這裏的邏輯是從valueProvider集合中逐一嘗試取值,有數據的則直接返回。
這也意味着數據綁定會以FormValueProvider到RouteValueProvider,再到QueryStringValueProvider,最後向JQueryFormValueProvider取值,這一流程執行,中間若是有任何一個能獲得數據的話,則再也不繼續訪問後面的ValueProvider。固然,前提是這些ValueProvider要不被先前的過濾處理排除在外。
如果還不明白這一順序關係的話,能夠回想下從ValueProviderFactories的添加順序,再至ValueProvider集合生成時各個ValueProvider的順序,就比較容易瞭解其中道理了。