今天看见一同事写了一段代码很是奇怪,大致结构如下:
public ActionResult Demo(string name, dynamic obj)
{ if (obj != null) { return Content("obj is not null"); } return Content("obj is null"); }在调用action时obj参数不传,如@{Html.RenderAction("Demo",new {name="majiang"});}。
可是在实际运行中obj永远不等于null。这是为什么了? 其实一切答案都在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); }
很显然object是复杂类型 应该关注方法BindComplexModel
internal object BindComplexModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { object model = bindingContext.Model; Type modelType = bindingContext.ModelType; // if we're being asked to create an array, create a list instead, then coerce to an array after the list is created if (model == null && modelType.IsArray) { Type elementType = modelType.GetElementType(); Type listType = typeof(List<>).MakeGenericType(elementType); object collection = CreateModel(controllerContext, bindingContext, listType); ModelBindingContext arrayBindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => collection, listType), ModelName = bindingContext.ModelName, ModelState = bindingContext.ModelState, PropertyFilter = bindingContext.PropertyFilter, ValueProvider = bindingContext.ValueProvider }; IList list = (IList)UpdateCollection(controllerContext, arrayBindingContext, elementType); if (list == null) { return null; } Array array = Array.CreateInstance(elementType, list.Count); list.CopyTo(array, 0); return array; } if (model == null) { model = CreateModel(controllerContext, bindingContext, modelType); } // special-case IDictionary<,> and ICollection<> Type dictionaryType = TypeHelpers.ExtractGenericInterface(modelType, typeof(IDictionary<,>)); if (dictionaryType != null) { Type[] genericArguments = dictionaryType.GetGenericArguments(); Type keyType = genericArguments[0]; Type valueType = genericArguments[1]; ModelBindingContext dictionaryBindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, modelType), ModelName = bindingContext.ModelName, ModelState = bindingContext.ModelState, PropertyFilter = bindingContext.PropertyFilter, ValueProvider = bindingContext.ValueProvider }; object dictionary = UpdateDictionary(controllerContext, dictionaryBindingContext, keyType, valueType); return dictionary; } Type enumerableType = TypeHelpers.ExtractGenericInterface(modelType, typeof(IEnumerable<>)); if (enumerableType != null) { Type elementType = enumerableType.GetGenericArguments()[0]; Type collectionType = typeof(ICollection<>).MakeGenericType(elementType); if (collectionType.IsInstanceOfType(model)) { ModelBindingContext collectionBindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, modelType), ModelName = bindingContext.ModelName, ModelState = bindingContext.ModelState, PropertyFilter = bindingContext.PropertyFilter, ValueProvider = bindingContext.ValueProvider }; object collection = UpdateCollection(controllerContext, collectionBindingContext, elementType); return collection; } } // otherwise, just update the properties on the complex type BindComplexElementalModel(controllerContext, bindingContext, model); return model; }
在它里面有关键的一句
if (model == null) { model = CreateModel(controllerContext, bindingContext, modelType); }
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) { Type typeToCreate = modelType; // we can understand some collection interfaces, e.g. IList<>, IDictionary<,> if (modelType.IsGenericType) { Type genericTypeDefinition = modelType.GetGenericTypeDefinition(); if (genericTypeDefinition == typeof(IDictionary<,>)) { typeToCreate = typeof(Dictionary<,>).MakeGenericType(modelType.GetGenericArguments()); } else if (genericTypeDefinition == typeof(IEnumerable<>) || genericTypeDefinition == typeof(ICollection<>) || genericTypeDefinition == typeof(IList<>)) { typeToCreate = typeof(List<>).MakeGenericType(modelType.GetGenericArguments()); } } // fallback to the type's default constructor return Activator.CreateInstance(typeToCreate); }
很明显 在绑定的时候发现复杂类型实例为null,这时DefaultModelBinder会创建一个默认实例。这就是为什么action参数为object的时候,再绑定后都不为null。然而string类型的数据他们走的是BindSimpleModel。
为了便于大家调试代码,证实以上结论,大家可以创建一个类继承与DefaultModelBinder,如CustBinder
然后再Application_Start() 中添加一句 ModelBinders.Binders.DefaultBinder = new CustBinder();
CustBinder代码:
public class CustBinder : DefaultModelBinder { public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { //return base.BindModel(controllerContext, 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); ValueProviderResult vpResult = (bindingContext.ValueProvider as IUnvalidatedValueProvider).GetValue(bindingContext.ModelName, skipValidation: !performRequestValidation); if (vpResult != null) { return BindSimpleModel(controllerContext, bindingContext, vpResult); } } if (!bindingContext.ModelMetadata.IsComplexType) { return null; } return BindComplexModel(controllerContext, bindingContext); } protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) { Type typeToCreate = modelType; // we can understand some collection interfaces, e.g. IList<>, IDictionary<,> if (modelType.IsGenericType) { Type genericTypeDefinition = modelType.GetGenericTypeDefinition(); if (genericTypeDefinition == typeof(IDictionary<,>)) { typeToCreate = typeof(Dictionary<,>).MakeGenericType(modelType.GetGenericArguments()); } else if (genericTypeDefinition == typeof(IEnumerable<>) || genericTypeDefinition == typeof(ICollection<>) || genericTypeDefinition == typeof(IList<>)) { typeToCreate = typeof(List<>).MakeGenericType(modelType.GetGenericArguments()); } } // fallback to the type's default constructor return Activator.CreateInstance(typeToCreate); } 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, 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; } internal object BindComplexModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { object model = bindingContext.Model; Type modelType = bindingContext.ModelType; // if we're being asked to create an array, create a list instead, then coerce to an array after the list is created if (model == null && modelType.IsArray) { Type elementType = modelType.GetElementType(); Type listType = typeof(List<>).MakeGenericType(elementType); object collection = CreateModel(controllerContext, bindingContext, listType); ModelBindingContext arrayBindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => collection, listType), ModelName = bindingContext.ModelName, ModelState = bindingContext.ModelState, PropertyFilter = bindingContext.PropertyFilter, ValueProvider = bindingContext.ValueProvider }; IList list = (IList)UpdateCollection(controllerContext, arrayBindingContext, elementType); if (list == null) { return null; } Array array = Array.CreateInstance(elementType, list.Count); list.CopyTo(array, 0); return array; } if (model == null) { model = CreateModel(controllerContext, bindingContext, modelType); } // special-case IDictionary<,> and ICollection<> Type dictionaryType = TypeHelpers.ExtractGenericInterface(modelType, typeof(IDictionary<,>)); if (dictionaryType != null) { Type[] genericArguments = dictionaryType.GetGenericArguments(); Type keyType = genericArguments[0]; Type valueType = genericArguments[1]; ModelBindingContext dictionaryBindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, modelType), ModelName = bindingContext.ModelName, ModelState = bindingContext.ModelState, PropertyFilter = bindingContext.PropertyFilter, ValueProvider = bindingContext.ValueProvider }; object dictionary = UpdateDictionary(controllerContext, dictionaryBindingContext, keyType, valueType); return dictionary; } Type enumerableType = TypeHelpers.ExtractGenericInterface(modelType, typeof(IEnumerable<>)); if (enumerableType != null) { Type elementType = enumerableType.GetGenericArguments()[0]; Type collectionType = typeof(ICollection<>).MakeGenericType(elementType); if (collectionType.IsInstanceOfType(model)) { ModelBindingContext collectionBindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, modelType), ModelName = bindingContext.ModelName, ModelState = bindingContext.ModelState, PropertyFilter = bindingContext.PropertyFilter, ValueProvider = bindingContext.ValueProvider }; object collection = UpdateCollection(controllerContext, collectionBindingContext, elementType); return collection; } } // otherwise, just update the properties on the complex type BindComplexElementalModel(controllerContext, bindingContext, model); return model; } internal void BindComplexElementalModel(ControllerContext controllerContext, ModelBindingContext bindingContext, object model) { // need to replace the property filter + model object and create an inner binding context ModelBindingContext newBindingContext = CreateComplexElementalModelBindingContext(controllerContext, bindingContext, model); // validation if (OnModelUpdating(controllerContext, newBindingContext)) { BindProperties(controllerContext, newBindingContext); OnModelUpdated(controllerContext, newBindingContext); } } internal ModelBindingContext CreateComplexElementalModelBindingContext(ControllerContext controllerContext, ModelBindingContext bindingContext, object model) { BindAttribute bindAttr = (BindAttribute)GetTypeDescriptor(controllerContext, bindingContext).GetAttributes()[typeof(BindAttribute)]; Predicate newPropertyFilter = (bindAttr != null) ? propertyName => bindAttr.IsPropertyAllowed(propertyName) && bindingContext.PropertyFilter(propertyName) : bindingContext.PropertyFilter; ModelBindingContext newBindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, bindingContext.ModelType), ModelName = bindingContext.ModelName, ModelState = bindingContext.ModelState, PropertyFilter = newPropertyFilter, ValueProvider = bindingContext.ValueProvider }; return newBindingContext; } internal object UpdateCollection(ControllerContext controllerContext, ModelBindingContext bindingContext, Type elementType) { bool stopOnIndexNotFound; IEnumerable indexes; GetIndexes(bindingContext, out stopOnIndexNotFound, out indexes); IModelBinder elementBinder = Binders.GetBinder(elementType); // build up a list of items from the request List