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
原文地址:https://www.cnblogs.com/kenwoo/p/9514817.html