Trigger IValidatableObject.Validate When ModelState.IsValid is false

I recently came across an ASP.NET MVC issue at work where the validation for my Model was not firing correctly. The Model implemented the IValidatableObject interface and thus the Validate method which did some specific logic to ensure the state of the Model (the ModelState). This Model also had some DataAnnotation attributes on it to validate basic input.

Long story short, the issue I encountered was that when ModelState.IsValid == false due to failure of the DataAnnotation validation, the IValidatableObject.Validate method is not fired, even though I needed it to be. This problem arose due to a rare situation in which ModeState.IsValid was initially false but was later set to true in the Controller’s Action Method by some logic that removed errors from the ModelState.

I did some research and learned that the DefaultModelBinder of ASP.NET MVC short-circuits it’s logic: if the ModelState is not valid (AKA is false), the IValidatableObject logic that runs the Validate method is never fired.

To thwart this, I created a custom Model Binder, a custom Model Binder Provider (to serve my custom Model Binder), and then registered the Model Binder Provider in the Application_Start method of Global.asax.cs. Here’s the code for a custom Model Binder that always fires the IValidatableObject.Validate method, even if ModelState.IsValid == false:

ForceValidationModelBinder:

/// <summary>
/// A custom model binder to force an IValidatableObject to execute the Validate method, even when the ModelState is not valid.
/// </summary>
public class ForceValidationModelBinder : DefaultModelBinder
{
    protected override void OnModelUpdated( ControllerContext controllerContext, ModelBindingContext bindingContext )
    {
        base.OnModelUpdated( controllerContext, bindingContext );

        ForceModelValidation( bindingContext );
    }

    private static void ForceModelValidation( ModelBindingContext bindingContext )
    {
        // Only run this code for an IValidatableObject model
        IValidatableObject model = bindingContext.Model as IValidatableObject;
        if( model == null )
        {
            // Nothing to do
            return;
        }

        // Get the model state
        ModelStateDictionary modelState = bindingContext.ModelState;

        // Get the errors
        IEnumerable<ValidationResult> errors = model.Validate( new ValidationContext( model, null, null ) );

        // Define the keys and values of the model state
        List<string> modelStateKeys = modelState.Keys.ToList();
        List<ModelState> modelStateValues = modelState.Values.ToList();

        foreach( ValidationResult error in errors )
        {
            // Account for errors that are not specific to a member name
            List<string> errorMemberNames = error.MemberNames.ToList();
            if( errorMemberNames.Count == 0 )
            {
                // Add empty string for errors that are not specific to a member name
                errorMemberNames.Add( string.Empty );
            }

            foreach( string memberName in errorMemberNames )
            {
                // Only add errors that haven't already been added.
                // (This can happen if the model's Validate(...) method is called more than once, which will happen when there are no property-level validation failures)
                int index = modelStateKeys.IndexOf( memberName );

                // Try and find an already existing error in the model state
                if( index == -1 || !modelStateValues[index].Errors.Any( i => i.ErrorMessage == error.ErrorMessage ) )
                {
                    // Add error
                    modelState.AddModelError( memberName, error.ErrorMessage );
                }
            }
        }
    }
}

ForceValidationModelBinderProvider:

/// <summary>
/// A custom model binder provider to provide a binder that forces an IValidatableObject to execute the Validate method, even when the ModelState is not valid.
/// </summary>
public class ForceValidationModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder( Type modelType )
    {
        return new ForceValidationModelBinder();
    }
}

Global.asax.cs:

protected void Application_Start()
{
    // Register the force validation model binder provider
    ModelBinderProviders.BinderProviders.Clear();
    ModelBinderProviders.BinderProviders.Add( new ForceValidationModelBinderProvider() );
}

Tags: , , ,

5 Responses to “Trigger IValidatableObject.Validate When ModelState.IsValid is false”

  1. skoop says :

    Nice job, thank you.

  2. Joey says :

    Fantastic solution. Thanks for sharing!

  3. Matthias Jansen says :

    Creative, but i would consider setting properties in the Validate method a side effect. Personally i would consider this to imply that my architecture is faultet.

  4. David says :

    Excellent, cheers!

  5. Lee Reynolds says :

    Any ideas around unit testing this? I’m using NUnit and Fluent Assertions. What I’m looking at doing is setting up the binding and controller contexts, calling BindModel for both the ForcedValidationModelBinder and the DefaultModelBinder, and comparing the output ModelStateDictionaries, under the assumption that the DefaultModelBinder should only return the custom validation once all attribute-based validation has passed successfully. But it doesn’t feel right…

Leave a Reply

Your email address will not be published. Required fields are marked *