.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; }
这里的代码很明显地说明了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的顺序,就比较容易了解其中道理了。
下一篇: ZUK手机宣布6月回归 新国民旗舰马上来
推荐阅读
-
ASP.NET Core优雅的在开发环境保存机密(User Secrets)
-
.NET Core实战项目之CMS 第十章 设计篇-系统开发框架设计
-
Net Core平台灵活简单的日志记录框架NLog+SqlServer初体验
-
循序渐进学.Net Core Web Api开发系列【4】:前端访问WebApi
-
循序渐进学.Net Core Web Api开发系列【5】:文件上传
-
使用VS2019在WSL中开发调试.NET Core
-
.net core 开发 Windows Forms 程序
-
.Net Core+Angular Cli/Angular4开发环境搭建教程
-
Mbp,一个用于学习.net core的开发框架
-
.NET Core开发的iNeuOS工业互联网平台,发布 iNeuDA 数据分析展示组件,快捷开发图形报表和数据大屏