標籤:dna break obj content mat float dex nsset tps
ASP.NET Core MVC中所提供的Model Binding功能簡單但實用,其主要目的是將請求中包含的資料對應到action的方法參數中。這樣就避免了開發人員像在Web Forms時代那樣需要從Request類中手動擷取資料的繁鎖操作,直接提高了開發效率。此功能繼承自ASP.NET MVC,所以熟悉上一代架構開發的工程師,可以毫無障礙地繼續享有它的便利。
本文想要探索下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方法,所以傳入參數後可直接執行處理。
private Task BindArgumentsAsync(){ ... return _cacheEntry.ControllerBinderDelegate(_controllerContext, _instance, _arguments);}
建立ControllerActionInvokerCacheEntry的地方是前兩篇文章(Controller,Action)中已經提到過的ControllerActionInvokerCache類。
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方法。
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)
代碼。
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這一操作便是在此時此地實現的。
接下來的疑問有兩處,modelBinder是如何產生的,請求中的資料又是怎樣與modelBinder發生聯絡。
ModelBinder
回到ControllerBinderDelegateProvider類的CreateBinderDelegate方法,可以看到其中調用了GetParameterBindingInfo方法。
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,則跳出當前迴圈,以這個對象作為傳回值。
ValueProvider
有了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的順序,就比較容易瞭解其中道理了。
.NET Core開發日誌——Model Binding