views:

415

answers:

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?

+1  A: 

It's certainly in MVC 2 Preview 2. Look at DefaultModelBinder.OnPropertyValidating and OnModelUpdated.

Craig Stuntz
It is removed from OnPropertyValidating, but not OnModelUpdated. OnPropertyValidating is what I needed. There is propably bug, because it shows inconsistency.
LukLed
Likely, i found bugs like this in the early MVC 1.0 previews. It's supposed to be supported but maybe you're right. I also used xVal to get my functionality in MVC 1.0 but will look to the additions in MVC 2.0. Good to know this might need work.
cottsak
+2  A: 

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.

LukLed
The call to ModelValidator.Validate() in your sample above should eventually be calling into IDataErrorInfo. Can you please explain "doesn't seem to work" in more detail?
Levi
Hi5. Nice work around. Should post this bug to Scott Gu's or one of the other MVC guys blogs - prob get a quick fix then.
cottsak
@cottsak: I did post it to issue tracker. Next preview release should remove the problem: http://aspnet.codeplex.com/WorkItem/View.aspx?WorkItemId=4891
LukLed
is this fixed now? can you edit question or add new answer if it is. i'm left a little confused - but perhaps thats because its 450am
Simon_Weaver
@Simon: They scheduled fix for MVC 2 RC. MVC 2 RC is here, so it should be fixed. I didn't try it.
LukLed