Search code examples
asp.net-mvcvalidationasp.net-mvc-2idataerrorinfo

Is IDataErrorInfo ignored during model validation in MVC 2?


I currently migrated my project to MVC 2 and IDataErrorInfo doesn't seem to work when using default model binding and validation. Is it cut out?


Solution

  • SUMMARY

    I posted this error to MVC 2 issue tracker: http://aspnet.codeplex.com/WorkItem/View.aspx?WorkItemId=4891

    It will be resolved in next preview release.


    DefaultModelBinder in MVC 1.0:

    protected virtual void OnPropertyValidated(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value)
    {
        IDataErrorInfo model = bindingContext.Model as IDataErrorInfo;
        if (model != null)
        {
            string str = model[propertyDescriptor.Name];
            if (!string.IsNullOrEmpty(str))
            {
                string key = CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name);
                bindingContext.ModelState.AddModelError(key, str);
            }
        }
    }
    

    DefaultModelBinder in MVC 2.0 beta:

    protected virtual void OnPropertyValidated(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value)
    {
        ModelMetadata metadata = bindingContext.PropertyMetadata[propertyDescriptor.Name];
        metadata.Model = value;
        string prefix = CreateSubPropertyName(bindingContext.ModelName, metadata.PropertyName);
        foreach (ModelValidator validator in metadata.GetValidators(controllerContext))
        {
            foreach (ModelValidationResult result in validator.Validate(bindingContext.Model))
            {
                bindingContext.ModelState.AddModelError(CreateSubPropertyName(prefix, result.MemberName), result.Message);
            }
        }
        if ((bindingContext.ModelState.IsValidField(prefix) && (value == null)) && !TypeHelpers.TypeAllowsNullValue(propertyDescriptor.PropertyType))
        {
            bindingContext.ModelState.AddModelError(prefix, GetValueRequiredResource(controllerContext));
        }
    }
    

    It doesn't use IDataErrorInfo this[string columnName] property... Seems like a bug, because DefaultModelBinder still uses Error property. It is inconsistency at least.

    EDIT

    I used reflector and noticed that DataErrorInfoPropertyModelValidator doesn't seem to be used, so I created my own class:

    public class DataErrorInfoModelPropertyValidatorProvider : ModelValidatorProvider
    {
        // Methods
        public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context)
        {
            if (metadata == null)
            {
                throw new ArgumentNullException("metadata");
            }
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }
    
            var validators = new List<ModelValidator>();
            validators.Add(new DataErrorInfoPropertyModelValidator(metadata, context));
            return validators;
        }
    
        internal sealed class DataErrorInfoPropertyModelValidator : ModelValidator
        {
            // Methods
            public DataErrorInfoPropertyModelValidator(ModelMetadata metadata, ControllerContext controllerContext)
                : base(metadata, controllerContext)
            {
            }
    
            public override IEnumerable<ModelValidationResult> Validate(object container)
            {
                if (container != null)
                {
                    IDataErrorInfo info = container as IDataErrorInfo;
                    if (info != null)
                    {
                        string str = info[Metadata.PropertyName];
                        if (!string.IsNullOrEmpty(str))
                        {
                            ModelValidationResult[] resultArray = new ModelValidationResult[1];
                            ModelValidationResult result = new ModelValidationResult();
                            result.Message = str;
                            resultArray[0] = result;
                            return resultArray;
                        }
                    }
                }
                return Enumerable.Empty<ModelValidationResult>();
            }
        }
    }
    

    Then I used:

    ModelValidatorProviders.Providers.Add(new DataErrorInfoModelPropertyValidatorProvider());
    

    And it works:) This is just temporary solution. Will have to be corrected in final MVC 2.

    EDIT

    I also changed if (base.Metadata.Model != null) to if (container != null) in Validate() method of DataErrorInfoPropertyModelValidator.