I’m trying to use Simon Ince’s conditional validation attributes for one of my view models. The logic seems to be working spot on, but the attribute’s error message isn’t appearing in my view’s ValidationFor() methods.
The attribute:
public class RequiredIfAttribute : ValidationAttribute
{
private RequiredAttribute innerAttribute = new RequiredAttribute();
public string DependentProperty { get; set; }
public object TargetValue { get; set; }
public RequiredIfAttribute(string dependentProperty, object targetValue)
{
this.DependentProperty = dependentProperty;
this.TargetValue = targetValue;
}
public override bool IsValid(object value)
{
return innerAttribute.IsValid(value);
}
}
The validator:
public class RequiredIfValidator : DataAnnotationsModelValidator<RequiredIfAttribute>
{
public RequiredIfValidator(ModelMetadata metadata, ControllerContext context, RequiredIfAttribute attribute)
: base(metadata, context, attribute)
{
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
// no client validation - I might well blog about this soon!
return base.GetClientValidationRules();
}
public override IEnumerable<ModelValidationResult> Validate(object container)
{
// get a reference to the property this validation depends upon
var field = Metadata.ContainerType.GetProperty(Attribute.DependentProperty);
if (field != null)
{
// get the value of the dependent property
var value = field.GetValue(container, null);
// compare the value against the target value
if ((value == null && Attribute.TargetValue == null) ||
(value.Equals(Attribute.TargetValue)))
{
// match => means we should try validating this field
if (!Attribute.IsValid(Metadata.Model))
// validation failed - return an error
yield return new ModelValidationResult { Message = ErrorMessage };
}
}
}
}
How they’re hooked up (Global.asax.cs):
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory());
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute), typeof(RequiredIfValidator));
}
}
And how I’m trying to use it:
public class AdminGameViewModel
{
public bool IsCreated { get; set; }
[Required]
public int GameID { get; set; }
[Required(ErrorMessage = "A game must have a title")]
[DisplayFormat(ConvertEmptyStringToNull=false)]
public string GameTitle { get; set; }
[Required(ErrorMessage = "A short URL must be supplied")]
[DisplayFormat(ConvertEmptyStringToNull=false)]
public string Slug { get; set; }
[RequiredIf("IsCreated", true, ErrorMessage = "A box art image must be supplied")]
public HttpPostedFileBase BoxArt { get; set; }
[RequiredIf("IsCreated", true, ErrorMessage = "A large image for the index page is required")]
public HttpPostedFileBase IndexImage { get; set; }
// other props of the class....
}
I don’t know enough about the inner workings of MVC’s validation mechanism in order to troubleshoot my problem. Any ideas?
Have you tried updating to my MVC3 implementation? It is cleaner than the hack needed with the validator in MVC2.
One thing that is missing from even the MVC3 code is the need to override FormatErrorMessage on the attribute, which will likely be similar to what you’re seeing here. For the MVC 3 code I use;
HTH
Simon