Search code examples
c#jqueryajaxasp.net-mvc-5fluentvalidation

FluentValidation predicate validator doesn't work


I have model class:

[FluentValidation.Attributes.Validator(typeof(CrcValidator))]
public class CrcModel
{
    [Display(Name = "Binary value")]
    public string binaryValue { get; set; }

    [Display(Name = "Generator")]
    public string generator { get; set; }
}

And validator class with predicate:

public class CrcValidator : AbstractValidator<CrcModel>
{
    public CrcValidator()
    {
        RuleFor(x => x.binaryValue)
            .NotEmpty().WithMessage("Binary value is required")
            .Matches(@"(0|1)*").WithMessage("This value is not valid binary value");

        RuleFor(x => x.generator)
            .NotEmpty().WithMessage("Generator is required")
            .Matches(@"(0|1)*").WithMessage("Generator must be valid binary value")
            .Must(CompareLength).WithMessage("Length must be lesser than length of binary value - 1");
    }

    private bool CompareLength(CrcModel model, string value)
    {
        return model.binaryValue.Length - 1 > model.generator.Length;
    }
}

I placed breakpoint inside the CompareLength function and every value is properly read from form. The problem is that my form passes validation even though my predicate function returns false. NotEmpty and Matches rules work perfectly fine only Must seems to be ommited.

EDIT

jQuery for submit button (of type "button"):

$(function () {
$("#Button1").click(function () {
    var form = $("#Form1");
    if ($(form).valid()) {
        $.ajax({
            type: 'POST',
            url: 'Compute',
            data: $(form).serialize(),
            success: function (result) {
                $("#remainder").val(result.remainder);
                $("#signal").val(result.signal);
            }
        });
    }
}); 
});

Controller action handling form submit:

[HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Compute([Bind(Include = "binaryValue,generator")] CrcModel model)
    {
        if (ModelState.IsValid)
        {
            model.remainder = ComputeFrame(model.binaryValue, model.generator);
            model.signal = model.binaryValue + model.remainder;
        }

        return Json(new { remainder = model.remainder, signal = model.signal });
    }

Validation from Must rule works on server side but the message doesn't show up.


Solution

  • Edit: After a few comments, the validations defined in all calls to Must in FluentValidation are only executed on the server. There is no JavaScript equivalent to arbitrary C# code. FluentValidation rules in C# get translated by the MVC framework to various HTML5 data-* attributes on the form field tags in HTML, which jQuery Validate reads during client side validation. Since there is no way to translate validation rules defined in predicates (delegate methods) to HTML5 data-* attributes, those validation rules are only enforceable server side.

    Edit #2: Seeing as the Must validation is triggered by an AJAX call, you need to do a couple things:

    1. Set the HTTP Status code to a non-200 response to trigger the error handling mechanism in jQuery. Plus, set the HTTP status to 422 (Unprocessable Entity)

      HttpPost]
      [ValidateAntiForgeryToken]
      public ActionResult Compute([Bind(Include = "binaryValue,generator")] CrcModel model)
      {
          if (ModelState.IsValid)
          {
              model.remainder = ComputeFrame(model.binaryValue, model.generator);
              model.signal = model.binaryValue + model.remainder;
      
              return Json(new { remainder = model.remainder, signal = model.signal });
          }
          else
          {
              Response.StatusCode = 422;
      
              return Json(new { errors = ModelState.Errors });
          }
      }
      
    2. Change the jQuery.ajax call:

      $(function () {
          $("#Button1").click(function () {
              var form = $("#Form1");
              if ($(form).valid()) {
                  $.ajax({
                      type: 'POST',
                      url: 'Compute',
                      data: $(form).serialize(),
                      success: function (result) {
                          $("#remainder").val(result.remainder);
                          $("#signal").val(result.signal);
                      },
                      error: function(xhr) {
                          if (xhr.status == 422) {
                              var errors = JSON.parse(xhr.responseText);
      
                              console.log(errors);
                          }
                      }
                  });
              }
          }); 
      });
      

    An alternative would be to just render a Partial that displays one or more error messages as HTML, then set that HTML in a DIV and show the DIV tag.