日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证

發布時間:2024/4/17 asp.net 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
asp.net mvc源碼分析-DefaultModelBinder 自定義的普通數據類型的綁定和驗證 原文:asp.net mvc源碼分析-DefaultModelBinder 自定義的普通數據類型的綁定和驗證

在前面的文章中我們曾經涉及到ControllerActionInvoker類GetParameterValue方法中有這么一句代碼:

? ?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
? ? ? ? ? ? };

這里的PropertyFilter屬性表示在綁定的時候參數是否需要綁定數據,為true表示綁定數據,ValueProvider 屬性表示什么就很明白,ModelName 為綁定信息的Prefix屬性或則是我們的參數名。同時我們還知道ModelMetadataProviders.Current默認就是DataAnnotationsModelMetadataProvider。而DataAnnotationsModelMetadataProvider的GetMetadataForType方法具體實現是在 其父類AssociatedMetadataProvider中實現的:

? public override ModelMetadata GetMetadataForType(Func<object> modelAccessor, Type modelType) {
? ? ? ? ? ? if (modelType == null) {
? ? ? ? ? ? ? ? throw new ArgumentNullException("modelType");
? ? ? ? ? ? }

? ? ? ? ? ? IEnumerable<Attribute> attributes = GetTypeDescriptor(modelType).GetAttributes().Cast<Attribute>();
? ? ? ? ? ? ModelMetadata result = CreateMetadata(attributes, null /* containerType */, modelAccessor, modelType, null /* propertyName */);
? ? ? ? ? ? ApplyMetadataAwareAttributes(attributes, result);
? ? ? ? ? ? return result;
? ? ? ? }

? ? IEnumerable<Attribute> attributes = GetTypeDescriptor(modelType).GetAttributes().Cast<Attribute>();這句查找當前modelType的所有特性(這里的modelType主要是自定義的那些強類型,如果是內置類型就沒有意義了)。

? ModelMetadata result = CreateMetadata(attributes, null /* containerType */, modelAccessor, modelType, null /* propertyName */);這句是真正創建ModelMetadata 的地方,在DataAnnotationsModelMetadataProvider類中從寫了。

protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName) {List<Attribute> attributeList = new List<Attribute>(attributes);DisplayColumnAttribute displayColumnAttribute = attributeList.OfType<DisplayColumnAttribute>().FirstOrDefault();DataAnnotationsModelMetadata result = new DataAnnotationsModelMetadata(this, containerType, modelAccessor, modelType, propertyName, displayColumnAttribute);// Do [HiddenInput] before [UIHint], so you can override the template hintHiddenInputAttribute hiddenInputAttribute = attributeList.OfType<HiddenInputAttribute>().FirstOrDefault();if (hiddenInputAttribute != null) {result.TemplateHint = "HiddenInput";result.HideSurroundingHtml = !hiddenInputAttribute.DisplayValue;}// We prefer [UIHint("...", PresentationLayer = "MVC")] but will fall back to [UIHint("...")]IEnumerable<UIHintAttribute> uiHintAttributes = attributeList.OfType<UIHintAttribute>();UIHintAttribute uiHintAttribute = uiHintAttributes.FirstOrDefault(a => String.Equals(a.PresentationLayer, "MVC", StringComparison.OrdinalIgnoreCase))?? uiHintAttributes.FirstOrDefault(a => String.IsNullOrEmpty(a.PresentationLayer));if (uiHintAttribute != null) {result.TemplateHint = uiHintAttribute.UIHint;}DataTypeAttribute dataTypeAttribute = attributeList.OfType<DataTypeAttribute>().FirstOrDefault();if (dataTypeAttribute != null) {result.DataTypeName = dataTypeAttribute.ToDataTypeName();}EditableAttribute editable = attributes.OfType<EditableAttribute>().FirstOrDefault();if (editable != null) {result.IsReadOnly = !editable.AllowEdit;}else {ReadOnlyAttribute readOnlyAttribute = attributeList.OfType<ReadOnlyAttribute>().FirstOrDefault();if (readOnlyAttribute != null) {result.IsReadOnly = readOnlyAttribute.IsReadOnly;}}DisplayFormatAttribute displayFormatAttribute = attributeList.OfType<DisplayFormatAttribute>().FirstOrDefault();if (displayFormatAttribute == null && dataTypeAttribute != null) {displayFormatAttribute = dataTypeAttribute.DisplayFormat;}if (displayFormatAttribute != null) {result.NullDisplayText = displayFormatAttribute.NullDisplayText;result.DisplayFormatString = displayFormatAttribute.DataFormatString;result.ConvertEmptyStringToNull = displayFormatAttribute.ConvertEmptyStringToNull;if (displayFormatAttribute.ApplyFormatInEditMode) {result.EditFormatString = displayFormatAttribute.DataFormatString;}if (!displayFormatAttribute.HtmlEncode && String.IsNullOrWhiteSpace(result.DataTypeName)) {result.DataTypeName = DataTypeUtil.HtmlTypeName;}}ScaffoldColumnAttribute scaffoldColumnAttribute = attributeList.OfType<ScaffoldColumnAttribute>().FirstOrDefault();if (scaffoldColumnAttribute != null) {result.ShowForDisplay = result.ShowForEdit = scaffoldColumnAttribute.Scaffold;}DisplayAttribute display = attributes.OfType<DisplayAttribute>().FirstOrDefault();string name = null;if (display != null) {result.Description = display.GetDescription();result.ShortDisplayName = display.GetShortName();result.Watermark = display.GetPrompt();result.Order = display.GetOrder() ?? ModelMetadata.DefaultOrder;name = display.GetName();}if (name != null) {result.DisplayName = name;}else {DisplayNameAttribute displayNameAttribute = attributeList.OfType<DisplayNameAttribute>().FirstOrDefault();if (displayNameAttribute != null) {result.DisplayName = displayNameAttribute.DisplayName;}}RequiredAttribute requiredAttribute = attributeList.OfType<RequiredAttribute>().FirstOrDefault();if (requiredAttribute != null) {result.IsRequired = true;}return result;}這里的具體創建就不說了,創建了一個DataAnnotationsModelMetadata實例,其中?containerType,propertyName默認是null,modelAccessor是返回null的一個方法,propertyName就是參數名。

ApplyMetadataAwareAttributes(attributes, result);這個方法就是給result設置相應的屬性,具體的實現是通過調用IMetadataAware實例的OnMetadataCreated方法。默認有AdditionalMetadataAttribute、AllowHtmlAttribute實現了IMetadataAware接口。

controllerContext.Controller.ViewData.ModelState默認返回的是一個ModelStateDictionary(private readonly ModelStateDictionary _modelState),

默認情況下ModelState里面是沒有任何元素的。

由前面的文章我們知道,默認的強類型參數如:

[HttpPost]
??????? public ActionResult Index(UserInfo Info)
??????? {
??????????? return View(Info);
??????? }

這個Info參數的綁定都是走的BindComplexModel->BindComplexElementalModel方法。

?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);

??????????? }
??????? }

?? ModelBindingContext newBindingContext = CreateComplexElementalModelBindingContext(controllerContext, bindingContext, model);這句是創建新的ModelBindingContext,和現有的ModelBindingContext有何不同了,其中ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, bindingContext.ModelType),這里的() => mode究竟有什么影響了,在這個東東對應modelAccessor參數,在ModelMetadata中有一個Model屬性。

? public object Model {
??????????? get {
??????????????? if (_modelAccessor != null) {
??????????????????? _model = _modelAccessor();
??????????????????? _modelAccessor = null;
??????????????? }

??????????????? return _model;
??????????? }
??????????? set {
??????????????? _model = value;
??????????????? _modelAccessor = null;
??????????????? _properties = null;
??????????????? _realModelType = null;
??????????? }
??????? }

同時新的ModelBindingContext的PropertyFilter有所改變,

?Predicate<string> newPropertyFilter = (bindAttr != null)
??????????????? ? propertyName => bindAttr.IsPropertyAllowed(propertyName) && bindingContext.PropertyFilter(propertyName)
??????????????? : bindingContext.PropertyFilter;

現在新的ModelBindingContext已經創建。

現在剩下的

if (OnModelUpdating(controllerContext, newBindingContext)) {
??????????????? BindProperties(controllerContext, newBindingContext);
??????????????? OnModelUpdated(controllerContext, newBindingContext);
??????????? }

這幾句的意思也很好明白,?? BindProperties(controllerContext, newBindingContext)這是真正綁定數據的地方,綁定數據前后都可以調用相應的方法一個做預處理,一個做后置處理。默認OnModelUpdating直接返回true。BindProperties處理就比較復雜了。

?private void BindProperties(ControllerContext controllerContext, ModelBindingContext bindingContext) {
??????????? IEnumerable<PropertyDescriptor> properties = GetFilteredModelProperties(controllerContext, bindingContext);
??????????? foreach (PropertyDescriptor property in properties) {
??????????????? BindProperty(controllerContext, bindingContext, property);
??????????? }
??????? }

首先需要獲取那些屬性需要綁定,然后在循環一次綁定每個屬性。

其中GetFilteredModelProperties的實現如下:

? protected IEnumerable<PropertyDescriptor> GetFilteredModelProperties(ControllerContext controllerContext, ModelBindingContext bindingContext) {
??????????? PropertyDescriptorCollection properties = GetModelProperties(controllerContext, bindingContext);
??????????? Predicate<string> propertyFilter = bindingContext.PropertyFilter;

??????????? return from PropertyDescriptor property in properties
?????????????????? where ShouldUpdateProperty(property, propertyFilter)
?????????????????? select property;
??????? }

首先獲取類型的所有屬性描述集合PropertyDescriptorCollection,然后一次過濾調我們不需要綁定的屬性。過濾條件的實現是ShouldUpdateProperty方法中。

??? private static bool ShouldUpdateProperty(PropertyDescriptor property, Predicate<string> propertyFilter) {
??????????? if (property.IsReadOnly && !CanUpdateReadonlyTypedReference(property.PropertyType)) {
??????????????? return false;
??????????? }

??????????? // if this property is rejected by the filter, move on
??????????? if (!propertyFilter(property.Name)) {
??????????????? return false;
??????????? }

??????????? // otherwise, allow
??????????? return true;
??????? }
CanUpdateReadonlyTypedReference這個方法很簡單,通過property.PropertyType是值類型、數組、string就返回true。BindProperty的實現就比較復雜了。

protected virtual void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor) {// need to skip properties that aren't part of the request, else we might hit a StackOverflowExceptionstring fullPropertyKey = CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name);if (!bindingContext.ValueProvider.ContainsPrefix(fullPropertyKey)) {return;}// call into the property's model binderIModelBinder propertyBinder = Binders.GetBinder(propertyDescriptor.PropertyType);object originalPropertyValue = propertyDescriptor.GetValue(bindingContext.Model);ModelMetadata propertyMetadata = bindingContext.PropertyMetadata[propertyDescriptor.Name];propertyMetadata.Model = originalPropertyValue;ModelBindingContext innerBindingContext = new ModelBindingContext() {ModelMetadata = propertyMetadata,ModelName = fullPropertyKey,ModelState = bindingContext.ModelState,ValueProvider = bindingContext.ValueProvider};object newPropertyValue = GetPropertyValue(controllerContext, innerBindingContext, propertyDescriptor, propertyBinder);propertyMetadata.Model = newPropertyValue;// validationModelState modelState = bindingContext.ModelState[fullPropertyKey];if (modelState == null || modelState.Errors.Count == 0) {if (OnPropertyValidating(controllerContext, bindingContext, propertyDescriptor, newPropertyValue)) {SetProperty(controllerContext, bindingContext, propertyDescriptor, newPropertyValue);OnPropertyValidated(controllerContext, bindingContext, propertyDescriptor, newPropertyValue);}}else {SetProperty(controllerContext, bindingContext, propertyDescriptor, newPropertyValue);// Convert FormatExceptions (type conversion failures) into InvalidValue messagesforeach (ModelError error in modelState.Errors.Where(err => String.IsNullOrEmpty(err.ErrorMessage) && err.Exception != null).ToList()) {for (Exception exception = error.Exception; exception != null; exception = exception.InnerException) {if (exception is FormatException) {string displayName = propertyMetadata.GetDisplayName();string errorMessageTemplate = GetValueInvalidResource(controllerContext);string errorMessage = String.Format(CultureInfo.CurrentCulture, errorMessageTemplate, modelState.Value.AttemptedValue, displayName);modelState.Errors.Remove(error);modelState.Errors.Add(errorMessage);break;}}}}}我們首先看看ModelBindingContext的PropertyMetadata屬性是什么東東吧。

?public IDictionary<string, ModelMetadata> PropertyMetadata {
??????????? get {
??????????????? if (_propertyMetadata == null) {
??????????????????? _propertyMetadata = ModelMetadata.Properties.ToDictionary(m => m.PropertyName, StringComparer.OrdinalIgnoreCase);
??????????????? }

??????????????? return _propertyMetadata;
??????????? }
??????? }

而ModelMetadata的Properties屬性定義如下:

?public virtual IEnumerable<ModelMetadata> Properties {
??????????? get {
??????????????? if (_properties == null) {
??????????????????? _properties = Provider.GetMetadataForProperties(Model, RealModelType).OrderBy(m => m.Order);
??????????????? }
??????????????? return _properties;
??????????? }
??????? }

GetMetadataForProperties的實現是在AssociatedMetadataProvider類中實現的,循環RealModelType類型的每個屬性,每個屬性都會創建一個ModelMetadata,創建ModelMetadata的方法還是調用CreateMetadata實現的。所以我們知道ModelBindingContext的PropertyMetadata屬性是一個字典集合,key就是屬性名,value為一個ModelMetadata

現在回到BindProperty方法中,它主要是獲取屬性綁定名稱,通過屬性類型獲取IModelBinder實例,同過bindingContext獲取屬性對應的ModelMetadata實例,進而創建新的ModelBindingContext實例,從而調用新的IModelBinder實例BindModel方法,獲取屬性對應的值,最后設置屬性對應的值。設置屬性對應的值是用過SetProperty方法來實現的。這個方法的代碼有點多,實際上很多都不執行的。

現在屬性都綁定完了,讓我們回到BindComplexElementalModel方法中來,該調用OnModelUpdated方法了:

protected virtual void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext) {Dictionary<string, bool> startedValid = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);foreach (ModelValidationResult validationResult in ModelValidator.GetModelValidator(bindingContext.ModelMetadata, controllerContext).Validate(null)) {string subPropertyName = CreateSubPropertyName(bindingContext.ModelName, validationResult.MemberName);if (!startedValid.ContainsKey(subPropertyName)) {startedValid[subPropertyName] = bindingContext.ModelState.IsValidField(subPropertyName);}if (startedValid[subPropertyName]) {bindingContext.ModelState.AddModelError(subPropertyName, validationResult.Message);}}}

這個方法意思很簡單,驗證數據的有效性。我們先看ModelStateDictionary的IsValidField方法是如何實現的:

?return DictionaryHelpers.FindKeysWithPrefix(this, key).All(entry => entry.Value.Errors.Count == 0);是否有錯誤信息,有表示沒有通過驗證,沒有通過的驗證記錄相應的驗證信息。ModelStateDictionary的AddModelError方法:

public void AddModelError(string key, string errorMessage) {
??????????? GetModelStateForKey(key).Errors.Add(errorMessage);
??????? }

我們知道每一個key對應一個ModelState,這個方法就是把錯誤信息寫到ModelState對應的Errors屬性里面。

下面我們來看看究竟是如何驗證數據的。

首先ModelValidator.GetModelValidator方法返回的是一個CompositeModelValidator實例,實際上的驗證是調用的CompositeModelValidator的Validate方法:

public override IEnumerable<ModelValidationResult> Validate(object container) {bool propertiesValid = true;foreach (ModelMetadata propertyMetadata in Metadata.Properties) {foreach (ModelValidator propertyValidator in propertyMetadata.GetValidators(ControllerContext)) {foreach (ModelValidationResult propertyResult in propertyValidator.Validate(Metadata.Model)) {propertiesValid = false;yield return new ModelValidationResult {MemberName = DefaultModelBinder.CreateSubPropertyName(propertyMetadata.PropertyName, propertyResult.MemberName),Message = propertyResult.Message};}}}if (propertiesValid) {foreach (ModelValidator typeValidator in Metadata.GetValidators(ControllerContext)) {foreach (ModelValidationResult typeResult in typeValidator.Validate(container)) {yield return typeResult;}}}}整個驗證分類2部分一部分驗證屬性,一部分驗證類型,先驗證屬性,如果屬性驗證沒有通過則直接返回驗證結果。其中ModelMetadata的GetValidators的實現如下:return ModelValidatorProviders.Providers.GetValidators(this, context);ModelValidatorProviders的定義如下:

public static class ModelValidatorProviders {private static readonly ModelValidatorProviderCollection _providers = new ModelValidatorProviderCollection() {new DataAnnotationsModelValidatorProvider(),new DataErrorInfoModelValidatorProvider(),new ClientDataTypeModelValidatorProvider()};public static ModelValidatorProviderCollection Providers {get {return _providers;}}}所以這里的GetValidators實際上就是調用Providers里面的每個GetValidators方法,這里我們可以添加自己驗證ModelValidatorProvider,ModelValidatorProviders.Providers.Add(new xxxx());

這里驗證結束后,我們的參數綁定也就結束了。

相信大家現在多我們自定義數據類型的綁定已經有一個基本的了解了吧。

posted on 2014-06-28 13:43 NET未來之路 閱讀(...) 評論(...) 編輯 收藏

轉載于:https://www.cnblogs.com/lonelyxmas/p/3813279.html

總結

以上是生活随笔為你收集整理的asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。