asp.net mvc源码分析-Action篇 DefaultModelBinder
接着上篇 asp.net mvc源码分析-Controller篇 ValueProvider 现在我们来看看ModelBindingContext这个对象。
ModelBindingContext bindingContext = new ModelBindingContext() {
FallbackToEmptyPrefix = (parameterDescriptor.BindingInfo.Prefix == null), // only fall back if prefix not specified
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, parameterType),
ModelName = parameterName,
ModelState = controllerContext.Controller.ViewData.ModelState,
PropertyFilter = propertyFilter,
ValueProvider = valueProvider
};
一般情况下FallbackToEmptyPrefix 应该是true,默认parameterDescriptor.BindingInfo.Prefix为空。里面的ModelMetadata属性是ModelMetadata的一个实例。
看看它的构造函数的定义,
public ModelMetadata(ModelMetadataProvider provider, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName) 这个类涉及到的东西很多,有些我现在也不是很明白,只知道他们是干什么的,所以这个类在这里只是简单的提一下而已。
它有一个属性
public virtual bool IsComplexType {
get {
return !(TypeDescriptor.GetConverter(ModelType).CanConvertFrom(typeof(string)));
}
}
看看当前参数对象能否转化为string对象,如果可以则是简单对象,否则则是复杂对象。
这里的ModelMetadataProviders.Current是一个DataAnnotationsModelMetadataProvider实例。DataAnnotationsModelMetadataProvider继承于AssociatedMetadataProvider继承于AssociatedMetadataProvider继承于ModelMetadataProvider,这里的调用GetMetadataForType来获取ModelMetadata,而真正创建ModelMetadata的是在DataAnnotationsModelMetadataProvider的CreateMetadata,该方法定义如下:
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
该方法真正创建了一个DataAnnotationsModelMetadata实例,该类是ModelMetadata的子类。
下面 我们来看看ModelState属性=controllerContext.Controller.ViewData.ModelState
其中 ModelState非常简单
[Serializable]
public class ModelState {
private ModelErrorCollection _errors = new ModelErrorCollection();
public ValueProviderResult Value { get; set;}
public ModelErrorCollection Errors {get { return _errors; }
}
而ViewDataDictionary的ModelState是一个ModelState的字典集合类ModelStateDictionary。该属性默认就只是一个实例里面没有ModelState。
下面这句binder.BindModel(controllerContext, bindingContext)是真正绑定参数的地方,我们知道默认的binder是DefaultModelBinder,所以现在我们来看看你它的BindModel方法:
public virtual object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { if (bindingContext == null ) { throw new ArgumentNullException( "bindingContext" ); } bool performedFallback = false ; if (!String.IsNullOrEmpty(bindingContext.ModelName) && !bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName)) { // We couldn't find any entry that began with the prefix. If this is the top-level element, fall back // to the empty prefix. if (bindingContext.FallbackToEmptyPrefix) { bindingContext = new ModelBindingContext() { ModelMetadata = bindingContext.ModelMetadata, ModelState = bindingContext.ModelState, PropertyFilter = bindingContext.PropertyFilter, ValueProvider = bindingContext.ValueProvider }; performedFallback = true ; } else { return null ; } } // Simple model = int, string, etc.; determined by calling TypeConverter.CanConvertFrom(typeof(string)) // or by seeing if a value in the request exactly matches the name of the model we're binding. // Complex type = everything else. if (!performedFallback) { bool performRequestValidation = ShouldPerformRequestValidation(controllerContext, bindingContext); ValueProviderResult vpResult = bindingContext.UnvalidatedValueProvider.GetValue(bindingContext.ModelName, skipValidation: !performRequestValidation); if (vpResult != null ) { return BindSimpleModel(controllerContext, bindingContext, vpResult); } } if (!bindingContext.ModelMetadata.IsComplexType) { return null ; } return BindComplexModel(controllerContext, bindingContext); } |
这里面有一个判断 if (!String.IsNullOrEmpty(bindingContext.ModelName) && !bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName)) 当然正常情况下
bindingContext.ModelName是不为空的,bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName)则是检查所有的ValueProvider中的所有keys是否有一个包含Action中的参数名,一般我们用的最多的是ChildActionValueProviderFactory、FormValueProviderFactory、QueryStringValueProviderFactory,用ChildActionValueProviderFactory一般是因为我们经常会有这样的代码Html.RenderAction,那么正常情况下ChildActionValueProviderFactory中就应该含有这里的bindingContext.ModelName;当我们实际参数值在FormValueProviderFactory、QueryStringValueProviderFactory中,如果我们的Action参数是简单数据类型,那么ValueProviderFactory也含有该bindingContext.ModelName,例如我们的Action定义为 public ActionResult Index(string name,string age) 访问url为http://localhost:7503/home/index?name=majiang&age=27,跑的流程主要是和Html.RenderAction调用一样,bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName)为true。
好让我们仔细看看
bool performRequestValidation = ShouldPerformRequestValidation(controllerContext, bindingContext);
ValueProviderResult vpResult = bindingContext.UnvalidatedValueProvider.GetValue(bindingContext.ModelName, skipValidation: !performRequestValidation);
if (vpResult != null) {
return BindSimpleModel(controllerContext, bindingContext, vpResult);
}
这几句 默认情况下performRequestValidation 为true,表示验证结果数据,而ModelBindingContext的UnvalidatedValueProvider
internal IUnvalidatedValueProvider UnvalidatedValueProvider {
get {
return (ValueProvider as IUnvalidatedValueProvider) ?? new UnvalidatedValueProviderWrapper(ValueProvider);
}
}
这里的bindingContext.UnvalidatedValueProvider.GetValue方法我想就很好明白了,不多说了。正常情况下vpResult 也不为null
internal object BindSimpleModel(ControllerContext controllerContext, ModelBindingContext bindingContext, ValueProviderResult valueProviderResult) { bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult); // if the value provider returns an instance of the requested data type, we can just short-circuit // the evaluation and return that instance if (bindingContext.ModelType.IsInstanceOfType(valueProviderResult.RawValue)) { return valueProviderResult.RawValue; } // since a string is an IEnumerable<char>, we want it to skip the two checks immediately following if (bindingContext.ModelType != typeof ( string )) { // conversion results in 3 cases, as below if (bindingContext.ModelType.IsArray) { // case 1: user asked for an array // ValueProviderResult.ConvertTo() understands array types, so pass in the array type directly object modelArray = ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, bindingContext.ModelType); return modelArray; } Type enumerableType = TypeHelpers.ExtractGenericInterface(bindingContext.ModelType, typeof (IEnumerable<>)); if (enumerableType != null ) { // case 2: user asked for a collection rather than an array // need to call ConvertTo() on the array type, then copy the array to the collection object modelCollection = CreateModel(controllerContext, bindingContext, bindingContext.ModelType); Type elementType = enumerableType.GetGenericArguments()[0]; Type arrayType = elementType.MakeArrayType(); object modelArray = ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, arrayType); Type collectionType = typeof (ICollection<>).MakeGenericType(elementType); if (collectionType.IsInstanceOfType(modelCollection)) { CollectionHelpers.ReplaceCollection(elementType, modelCollection, modelArray); } return modelCollection; } } // case 3: user asked for an individual element object model = ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, bindingContext.ModelType); return model; } |
BindSimpleModel方法相对简单,但是也还是比较复杂,我这里先看第一句
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
public void SetModelValue(string key, ValueProviderResult value) {
GetModelStateForKey(key).Value = value;
}
private ModelState GetModelStateForKey(string key) {
if (key == null) {
throw new ArgumentNullException("key");
}
ModelState modelState;
if (!TryGetValue(key, out modelState)) {
modelState = new ModelState();
this[key] = modelState;
}
return modelState;
}
从这里我们可以看到一个key对应一个ModelState 对应一个ValueProviderResult ;
这个 方法最后一句
object model = ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, bindingContext.ModelType);方法ConvertProviderResult实际就一句
object convertedValue = valueProviderResult.ConvertTo(destinationType);意思就是把数据转换成我们需要的数据类型。
如果这里的convertedValue 为null,且bindingContext.ModelType为负责类型那么我们就要调用一次BindComplexModel,有前面的分析我们也知道bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName)返回false也会调用BindComplexModel方法。
BindComplexModel方法非常复杂,这里有一句object model = bindingContext.Model; 而bindingContext.Model实际上是 return ModelMetadata.Model;具体的实现如下:
public object Model {
get {
if (_modelAccessor != null) {
_model = _modelAccessor();
_modelAccessor = null;
}
return _model;
}
set {
_model = value;
_modelAccessor = null;
_properties = null;
_realModelType = null;
}
}
默认 情况下这个_modelAccessor==null的,在ControllerActionInvoker.GetParameterValue方法中bindingContext的
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, parameterType),参数null造成的,GetMetadataForType的具体实现:
private IEnumerable<ModelMetadata> GetMetadataForPropertiesImpl(object container, Type containerType) {
foreach (PropertyDescriptor property in GetTypeDescriptor(containerType).GetProperties()) {
Func<object> modelAccessor = container == null ? null : GetPropertyValueAccessor(container, property);
yield return GetMetadataForProperty(modelAccessor, containerType, property);
}
}
现在我们又回到DefaultModelBinder的BindComplexModel中来,这里面有一句
if (model == null) {
model = CreateModel(controllerContext, bindingContext, modelType);
}
所以一般情况下 BindComplexModel不会返回null值,大家要切记啊。
BindComplexModel会把当前数据类型依次转化为typeof(IDictionary<,>)类型如果成功就按照字典来处理,调用UpdateDictionary方法,如果转化为typeof(IDictionary<,>失败就转化为 typeof(IEnumerable<>)按照集合来处理,调用UpdateCollection方法,如果这种转化也不行的话就按照普通的强类型来处理调用BindComplexElementalModel方法,这种绑定是我们在强类型情况下用的最多的情况。BindComplexElementalModel里面的核心代码是调用
BindProperties(controllerContext, newBindingContext);方法,
private void BindProperties(ControllerContext controllerContext, ModelBindingContext bindingContext) {
IEnumerable<PropertyDescriptor> properties = GetFilteredModelProperties(controllerContext, bindingContext);
foreach (PropertyDescriptor property in properties) {
BindProperty(controllerContext, bindingContext, property);
}
}
对 DefaultModelBinder的具体实现很复杂,我们在写Action时应该知道BindModel的时候里面究竟走的是BindSimpleModel还是BindComplexModel,还有参数具体是由哪个ValueProviderDictionary提供的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构