.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的顺序,就比较容易了解其中道理了。

原文地址:https://www.cnblogs.com/kenwoo/p/9514817.html

时间: 2024-08-30 11:39:38

.NET Core开发日志——Model Binding的相关文章

.NET Core开发日志——RequestDelegate

本文主要是对.NET Core开发日志--Middleware的补遗,但是会从看起来平平无奇的RequestDelegate开始叙述,所以以其作为标题,也是合情合理. RequestDelegate是一种委托类型,其全貌为public delegate Task RequestDelegate(HttpContext context),MSDN上对它的解释,"A function that can process an HTTP request."--处理HTTP请求的函数.唯一参数,

.NET Core开发日志——Entity Framework与PostgreSQL

Entity Framework在.NET Core中被命名为Entity Framework Core.虽然一般会用于对SQL Server数据库进行数据操作,但其实它还支持其它数据库,这里就以PostgreSQL作为例子. PostgreSQL PostgreSQL可以选用原生系统与Docker两种安装方式. Official Docker Package 在应用程序工程中添加相关的引用. dotnet add package Npgsql.EntityFrameworkCore.Postg

.NET Core开发日志——Filter

ASP.NET Core MVC中的Filter作用是在请求处理管道的某些阶段之前或之后可以运行特定的代码. Filter特性在之前的ASP.NET MVC中已经出现,但过去只有Authorization,Exception,Action,Result四种类型,现在又增加了一种Resource类型.所以共计五种. Resource类型Filter在Authorization类型Filter之后执行,但又在其它类型的Filter之前.且执行顺序也在Model Binding之前,所以可以对Mode

.Net Core开发日志——从搭建开发环境开始

.Net Core自2016年推出1.0版本开始,到目前已是2.1版本,在其roadmap计划里明年更会推出3.0版本,发展不可不谓之迅捷.不少公司在经过一个谨慎的观望期后,也逐步开始将系统升级至最新的.Net Core平台,所以现在开始进行.Net Core开发可谓正当其时. 因为.Net Core支持Windows系统以外的Linux与Mac系统,在选择开发环境时,并不需要局限在原有的Windows平台,这里我选用了Mac平台. 开发硬件设备是一台14年款的Apple Macbook Air

.Net Core开发日志——Peachpie

.Net Core的生态圈随着开源社区的力量不断注入至其中,正在变得越来越强盛,并且不时得就出现些有意思的项目,比如Peachpie,它使得PHP的代码迁移到.Net Core项目变得可能. 从创建简单的入门程序开始可以更容易地体会其特性. 首先安装Peachpie的模板: dotnet new -i Peachpie.Templates::* 接着创建项目: dotnet new web -lang PHP -o helloPHP 然后切换目录至Server文件夹运行程序: cd Server

.NET Core开发日志——简述路由

有过ASP.NET或其它现代Web框架开发经历的开发者对路由这一名字应该不陌生.如果要用一句话解释什么是路由,可以这样形容:通过对URL的解析,指定相应的处理程序. 回忆下在Web Forms应用程序中使用路由的方式: public static void RegisterRoutes(RouteCollection routes) { routes.MapPageRoute("", "Category/{action}/{categoryName}", "

.NET Core开发日志——Linux版本的SQL Server

SQL Server 2017版本已经可以在Linux系统上安装,但我在尝试.NET Core跨平台开发的时候使用的是Mac系统,所以这里记录了在Mac上安装SQL Server的过程. 最新的SQL Server没有专门为Mac系统准备安装包,但由于Mac系统上支持Docker,所以可以用一种变通的方式--在Docker内部安装Linux版本的SQL Server. 系统要求 因为我的Macbook Air型号比较老,硬件条件很一般,所以首先确定下是否满足安装SQL Server的条件.官方给

.NET Core开发日志——结构化日志

在.NET生态圈中,最早被广泛使用的日志库可能是派生自Java世界里的Apache log4net.而其后来者,莫过于NLog.Nlog与log4net相比,有一项较显著的优势,它支持结构化日志. 结构化日志,也被称为语义化日志.其作用有二,利于查询与方便分析. 当系统上线被广泛使用或者时间久远之后,日志的大量出现不可避免.且日志本身作为一种数据,也有其重要的价值.因此,如何有效地对其进行查询以及最大价值化地分析处理便成了一个重要的问题. 非结构化日志 对于日志的处理,需要权衡对开发者的友好性与

.NET Core开发日志——OData

简述 OData,即Open Data Protocol,是由微软在2007年推出的一款开放协议,旨在通过简单.标准的方式创建和使用查询式及交互式RESTful API. 类库 在.NET Core中想要使用OData功能的话需要添加Microsoft.AspNetCore.OData包. dotnet add package Microsoft.AspNetCore.OData 准备模型类 public class Address { public string City { get; set