zoukankan      html  css  js  c++  java
  • .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的地方是前两篇文章(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的顺序,就比较容易了解其中道理了。

  • 相关阅读:
    JavaScript 用new创建对象的过程
    从输入URL到浏览器显示页面发生了什么
    JS中的this对象详解
    JS事件
    vue如何正确销毁当前组件的scroll事件?
    pg创建存储过程批量提交
    mysql去掉明文密码不安全提示
    解决npm安装node-sass太慢及编译错误问题
    解决vs code编写python输出中文乱码问题
    EditPlus配置Java编译器
  • 原文地址:https://www.cnblogs.com/kenwoo/p/9514817.html
Copyright © 2011-2022 走看看