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

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

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

    Entity Framework在.NET Core中被命名为Entity Framework Core.虽然一般会用于对SQL Server数据库进行数据操作,但其实它还支持其它数据库,这里就以Po ...

  2. .NET Core开发日志——RequestDelegate

    本文主要是对.NET Core开发日志--Middleware的补遗,但是会从看起来平平无奇的RequestDelegate开始叙述,所以以其作为标题,也是合情合理. RequestDelegate是 ...

  3. C#实现多级子目录Zip压缩解压实例 NET4.6下的UTC时间转换 [译]ASP.NET Core Web API 中使用Oracle数据库和Dapper看这篇就够了 asp.Net Core免费开源分布式异常日志收集框架Exceptionless安装配置以及简单使用图文教程 asp.net core异步进行新增操作并且需要判断某些字段是否重复的三种解决方案 .NET Core开发日志

    C#实现多级子目录Zip压缩解压实例 参考 https://blog.csdn.net/lki_suidongdong/article/details/20942977 重点: 实现多级子目录的压缩, ...

  4. .NET Core开发日志——Filter

    ASP.NET Core MVC中的Filter作用是在请求处理管道的某些阶段之前或之后可以运行特定的代码. Filter特性在之前的ASP.NET MVC中已经出现,但过去只有Authorizati ...

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

    .NET Core自2016年推出1.0版本开始,到目前已是2.1版本,在其roadmap计划里明年更会推出3.0版本,发展不可不谓之迅捷.不少公司在经过一个谨慎的观望期后,也逐步开始将系统升级至最新 ...

  6. .NET Core开发日志——OData

    简述 OData,即Open Data Protocol,是由微软在2007年推出的一款开放协议,旨在通过简单.标准的方式创建和使用查询式及交互式RESTful API. 类库 在.NET Core中 ...

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

    在.NET生态圈中,最早被广泛使用的日志库可能是派生自Java世界里的Apache log4net.而其后来者,莫过于NLog.Nlog与log4net相比,有一项较显著的优势,它支持结构化日志. 结 ...

  8. .NET Core开发日志——Edge.js

    最近在项目中遇到这样的需求:要将旧有系统的一部分业务逻辑集成到新的自动化流程工具中.这套正在开发的自动化工具使用的是C#语言,而旧有系统的业务逻辑则是使用AngularJS在前端构建而成.所以最初的考 ...

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

    SQL Server 2017版本已经可以在Linux系统上安装,但我在尝试.NET Core跨平台开发的时候使用的是Mac系统,所以这里记录了在Mac上安装SQL Server的过程. 最新的SQL ...

随机推荐

  1. 第三方文本框 在div中显示预览,让指定节点不受外部css影响

    例如,富文本框中 ol  li 但是我们往往全局样式时候会 让前面的数字不显示,但是富文本框时候,录入,我们需要显示,但是div中就不显示了 我们在预览页面中加上一个指定样式   然后后面 加上!im ...

  2. C#-MVC开发微信应用(8)--菜单管理的实现

    之前讲解了微信后台管理页面的操作来管理菜单,下面我们在简单的来看一下,代码是如何实现的. 我们要实现获取微信的菜单.创建菜单.删除菜单等操作. 01.首先定义菜单操作的接口: /// <summ ...

  3. 译:3.RabbitMQ Java Client 之 Publish/Subscribe(发布和订阅)

    在上篇 RabbitMQ 之Work Queues (工作队列)教程中,我们创建了一个工作队列,工作队列背后的假设是每个任务都交付给一个工作者. 在这一部分,我们将做一些完全不同的事情 - 我们将向多 ...

  4. [svc]linux iptables实战

    参考: http://blog.51yip.com/linux/1404.html 链和表 参考: https://aliang.org/Linux/iptables.html 配置 作为服务器 用途 ...

  5. python 函数的参数的几种类型

    定义函数的时候,我们把参数的名字和位置确定下来,函数的接口定义就完成了.对于函数的调用者来说,只需要知道如何传递正确的参数,以及函数将返回什么样的值就够了,函数内部的复杂逻辑被封装起来,调用者无需了解 ...

  6. Node入门教程(12)第十章:Node的HTTP模块

    Ryan Dahl开发node的初衷就是:把Nginx非阻塞IO功能和一个高度封装的WEB服务器结合在一起的东东.所以Node初衷就是为了高性能的Web服务器去的,所以:Node的HTTP模块也是核心 ...

  7. HTML5超酷秒表动画 可暂停和重置秒表

    关于HTML5和CSS3的时钟应用在之前我们已经分享过不少了,还有一些HTML5的日期选择应用.今天我们要分享一款基于HTML5和CSS3的圆盘秒表动画,秒表可以精确到0.001秒,并且可以在计时过程 ...

  8. 前端开发神级IDE-sublime text

    汉化并自动带常用插件的版本下载地址:http://www.cr173.com/soft/55484.html 1.修改auto_complete快捷键:首选项>设置-默认>ctrl+f搜索 ...

  9. mysqldump导出数据时,某些表不导出,排除某些表,不导出某些表

    需求说明: 今天一同事问,在通过mysqldump导出数据库的时候,能不能把某些表不导出,或者叫做排除在外呢, 记得应该是可以实现,就搜索了下,通过mysqldump的--ignore-table参数 ...

  10. Java基础复习笔记基本排序算法

    Java基础复习笔记基本排序算法 1. 排序 排序是一个历来都是很多算法家热衷的领域,到现在还有很多数学家兼计算机专家还在研究.而排序是计算机程序开发中常用的一种操作.为何需要排序呢.我们在所有的系统 ...