【导读】一年多没更新博客,上一次写此系列还是四年前,虽迟但到,没有承诺,主打随性,所以不存在断更,催更,哈哈,上一篇我们细究从请求到绑定详细原理,本篇则是探讨模型绑定细节,当一个问题产生到最终解决时,回过头我们整体分析其产生背景以及设计思路才能有所获。好了,废话不多说,我们开始模型绑定细节之旅。
问题产生
public class UserAddress
{
public string Code { get; set; }
}
[ ]
[ ]
public class UserAddressController : ControllerBase
{
private readonly ILogger<UserAddressController> _logger;
public UserAddressController(ILogger<UserAddressController> logger)
{
_logger = logger;
}
[ ]
public IActionResult Get([FromQuery] UserAddress address)
{
return Ok(address);
}
}
没任何毛病,接下来我们在定义用户地址类上增加一个属性,如下所示
public class UserAddress
{
public string Code { get; set; }
public string Address { get; set; }
}
值绑定不上,这是神马情况,这难道是官方的Bug吗,我们用6.0和7.0都是如此,毫无疑问,利用.NET 8.0依然是此等结果,问题来了,请稍加思考大概是什么原因,让我们继续往下分析
根因源码分析
通过前后对比我们可以初步分析到原因可能是二方面之一或者二者结合,其一,对象属性address和接收对象参数变量address不能相同(不区分大小写),其二,接受对象参数变量address和URL上的键名称address不能相同(不区分大小写)。
我们暂且只能分析到这个地方,当然,我们一试便知,至于根因是什么,接下来我们只能去分析模型绑定源码,说到分析源码,可能有些童鞋不知从何开始,这里给出我们从0开始分析其根因的整个过程,以供需要的童鞋做参考哈。
仅我个人看法,除非精通,否则必会经历一个过程,这是必然,所以不用怀疑任谁都不能立马找到大概源码在哪里,我们注意关注点分析,别看着看着跑偏了,既然是模型绑定而且是查询绑定,这是在了解基本原理或学习官网文档有所印象的前提下,先看这里
然后我们怎么开始呢,我们直接自定义实现一个查询字符串值绑定即将上述代码拷贝一份出来,比如有些是有依赖的等等,将其修改去掉等等处理,还是我们强调的关注点,最后我们还要添加自定义实现
builder.Services.AddControllers(options =>
{
options.ValueProviderFactories.Insert(0, new QueryStringValueProviderFactory());
});
我们看到实际上值都已正确获取到,但实际上传过来的键应该是属性Code或者Address才对,同时我们发现在此实现中包含了一个是否包含前缀的方法
这好像貌似就是针对我们绑定的属性加上方法上的参数变量即address,所以我们断点一步步调试进入该方法具体实现
继续往下调试实际调用的绑定者是哪一个,我们看到实际使用的复杂对象绑定,框架内置实现了十几个绑定,ValueProvider只是其中后台接收最简单的参数类型或者直接接收请求上下文相关的预处理,大多都由ModelBinder来接收处理绑定到控制器方法上,调试源码并不是那么明朗
我们再自定义实现一个ComplexObjectModelBinderProvider,其具体ComplexObjectModelBinder有个方法BindPropertiesAsync,这是实际做相关处理的地方
/// <summary>
/// Create a property model name with a prefix.
/// </summary>
/// <param name="prefix">The prefix to use.</param>
/// <param name="propertyName">The property name.</param>
/// <returns>The property model name.</returns>
public static string CreatePropertyModelName(string? prefix, string? propertyName)
{
if (string.IsOrEmpty(prefix))
{
return propertyName ?? string.Empty;
}
if (string.IsOrEmpty(propertyName))
{
return prefix ?? string.Empty;
}
if (propertyName.StartsWith('['))
{
// The propertyName might represent an indexer access, in which case combining
// with a 'dot' would be invalid. This case occurs only when called from ValidationVisitor.
return prefix + propertyName;
}
return prefix + "." + propertyName;
}
举一反三