.NET Core开发日志——Model Binding

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; }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyjjzf.html