欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

.NET Core开发日志——Model Binding

程序员文章站 2022-06-30 18:59:25
ASP.NET Core MVC中所提供的Model Binding功能简单但实用,其主要目的是将请求中包含的数据映射到action的方法参数中。这样就避免了开发者像在Web Forms时代那样需要从Request类中手动获取数据的繁锁操作,直接提高了开发效率。此功能继承自ASP.NET MVC,所 ......

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的地方是前两篇文章(controlleraction)中已经提到过的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的顺序,就比较容易了解其中道理了。