joelmdev
joelmdev

Reputation: 11773

Troubles with implementing IClientValidatable- GetClientValidationRules never fires

I am building a custom validator for my MVC4 application. To keep things simple and focus on the purpose of this question let's say that I'm reimplementing the RequiredAttribute class's server and client side validation:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class MyRequiredAttribute: ValidationAttribute, IClientValidatable
{
    public MyRequiredAttribute() : base("The {0} field is required.")
    {
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (value == null)
        {
            return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
        }
        return null;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        yield return new ModelClientValidationRequiredRule(ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()));
    }
}

To test this I have a very simple controller:

public class TestController : Controller
{
    public ActionResult Index()
    {
        return View(new MyThing {Value = "whatever"});
    }
    [HttpPost]
    public ActionResult Index(MyThing thing)
    {
        if (ModelState.IsValid)
        {
            return Content("Good choice!");
        }
        return View(thing);
    }
}

and a very simple model:

public class MyThing
{
    [MyRequired]
    public string Value { get; set; }
    [Required]
    public string OtherValue { get; set; }
}

and finally the Index View:

@model MyThing

@{
    Layout = "~/Views/Shared/_LayoutWithAllTheAppropriateValidationScripts.cshtml";
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div>
        @using(Html.BeginForm()){
        @Html.ValidationSummary(true)
        @Html.EditorForModel()
        <input type="submit" value="submit"/>
        }
    </div>
</body>
</html>

You'll note that I have Value which is decorated with the custom MyRequiredAttribute and OtherValue which is decorated with the standard RequiredAttribute.

Both of these work beautifully server-side and return the appropriate validation messages on postback, but only the standard RequiredAttribute works on the client side. Since I'm using a ModelClientValidationRequiredRule instead of a ModelClientValidationRule with a custom ValidationType property I'm assuming that no custom clientside validation rules need to be defined, but I don't know that for sure because I haven't been able to get that far. The debugger never breaks inside of the GetClientValidationRules method and the input html element generated for the Value property on my model does not have the data-val-required and data-val attributes as does the OtherValue field, which I'm assuming is what the GetClientValidationRules method is responsible for doing.

I must be missing something simple here... what is it?

Upvotes: 1

Views: 3650

Answers (2)

joelmdev
joelmdev

Reputation: 11773

This turned out to be user error, but I'll post the solution anyway in hopes that it helps someone else with a similar problem down the road.

The code in the original question is in a separate assembly from the MVC project that uses it. The MVC project had recently been upgraded from MVC3 to MVC4. I had followed the instructions provided by Microsoft on upgrading, or so I thought. I missed one piece in the web.config:

<runtime>
   <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
     <dependentAssembly>
       <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35"/>
       <bindingRedirect oldVersion="1.0.0.0-2.0.0.0" newVersion="3.0.0.0"/>
     </dependentAssembly>
   </assemblyBinding>
</runtime>

This tells the MVC project how to play nice with assemblies on which it depends that were compiled with a reference to an earlier version of the System.Web.Mvc assembly. After upgrading my MVC project it referenced System.Web.Mvc v4 and my "Common" project (where the extended ValidatorAttribute implementing IClientValidatable lived) referenced v3. The configuration above should have been updated with the upgrade but it got missed. The issue did not manifest in any other way except for the client-side validation issue outlined in the original question.

The offending line here was

       <bindingRedirect oldVersion="1.0.0.0-2.0.0.0" newVersion="3.0.0.0"/>

which should have been

       <bindingRedirect oldVersion="1.0.0.0-4.0.0.0" newVersion="4.0.0.0"/>

which fixed the problem.

Upvotes: 3

Admir Tuzović
Admir Tuzović

Reputation: 11177

Add this to your Global.asax:

DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(MyRequiredAttribute), typeof(RequiredAttributeAdapter));

This should solve your client-side validation issue.

Upvotes: 0

Related Questions