1
votes

I am trying to figure out why my FluentValidation is not unobtrusively validating some of my View Model properties.

I installed, via NuGet, the FluentValidation package for MVC3:

  Install-Package FluentValidation.MVC3

Results:

<package id="FluentValidation" version="5.0.0.1" targetFramework="net45" />
<package id="FluentValidation.MVC3" version="5.0.0.1" targetFramework="net45" />

The properties that are not being validated are some members of a Guitar class object, declared in my View model. The Guitar.ProductionYear property is getting the unobtrusive validation code but the other 2 properties of the Guitar object are not.

public Guitar Guitar1 { get; set; }
public Guitar Guitar2 { get; set; }
public Guitar Guitar3 { get; set; }

I read the FluentValidation docs, and now specifically about the limitations regarding FluentValidation's client-side validation of lists but this is not a list so I am hoping someone can see what's wrong and/or offer some suggestions.

I also tried using a custom Guitar object validator but still get the same results

Observations

  • Client-side validation is being generated and fires for properties FirstName,LasteName,Phone and Email

  • I did notice that ModelState is not valid for these objects when they are not filled in by the user but I don't know why the client-side validation is not being generated and subsequently firing.

  • The generated form HTML for the Guitar objects are missing the unobtrusive validation attributes. The other properties like FirstName, LastName, Email and Phone are validating fine
  • Example

        <!-- Unobtrusive JS generated-->
            <input type="text" value="" name="FirstName" id="FirstName" data-val-required="'First Name' must not be empty." data-val="true" class="input-validation-error">
            <div class="messageBottom"> 
                    <span data-valmsg-replace="true" data-valmsg-for="FirstName" class="field-validation-error"><span for="FirstName" generated="true" class="">'First Name' must not be empty.</span>
       </span>
     </div>
    
        <!-- Unobtrusive JS Partially generated for Guitar object-->
       <input type="text" value="" name="Guitar.Make" id="Guitar_Make" placeholder="Make">
            <div class="messageBottom">
            <span data-valmsg-replace="true" data-valmsg-for="Guitar1.Make" class="field-validation-valid"></span>
            </div>
    
            <input type="text" value="" name="Guitar1.Model" id="Guitar1_Model" placeholder="Model">
            <div class="messageBottom">
                <span data-valmsg-replace="true" data-valmsg-for="Guitar1.Model" class="field-validation-valid"></span>
            </div>
    
            <input type="text" value="" name="Guitar1.ProductionYear" id="Guitar1_ProductionYear" data-val-number="The field ProductionYear must be a number." data-val="true" placeholder="Production Year" class="valid">
            <div class="messageBottom">
            <span data-valmsg-replace="true" data-valmsg-for="Guitar1.ProductionYear" class="field-validation-valid">
            </span>
            </div>
    

    Thanks

    Global.asax.cs

    FluentValidationModelValidatorProvider.Configure();
    

    The View Model

    [FluentValidation.Attributes.Validator(typeof(CustomerViewModelValidator))]
    public class CustomerViewModel
    {
        [Display(Name = "First Name")]
        public string FirstName { get; set; }
    
        [Display(Name = "Last Name")]
        public string LastName { get; set; }
    
        [Display(Name = "Phone")]
        public string Phone { get; set; }
    
        [Display(Name = "Email")]
        public string EmailAddress { get; set; }
    
        [Display(Name = "Guitar 1")]
        public Guitar Guitar1 { get; set; }
    
        [Display(Name = "Guitar 2")]
        public Guitar Guitar2 { get; set; }
    
        [Display(Name = "Guitar 3")]
        public Guitar Guitar3 { get; set; }
    }
    

    The Custom Guitar Object Validator

    public class GuitarValidator : AbstractValidator<Guitar>
    {
        public GuitarValidator()
        {
            RuleFor(x => x.Make).NotEmpty();
            RuleFor(x => x.Model).NotEmpty();
            RuleFor(x => x.ProductionYear).NotEmpty();
        }
    }
    

    The View Model Validator

    public class CustomerViewModelValidator : AbstractValidator<CustomerViewModel>
    {
        public CustomerViewModelValidator()
        {
            RuleFor(x => x.FirstName).NotNull();
            RuleFor(x => x.LastName).NotNull();
            RuleFor(x => x.Phone).NotNull();
            RuleFor(x => x.EmailAddress).NotNull();
    
            //1st Guitar Object is Required
            RuleFor(x => x.Guitar).SetValidator(new GuitarValidator());
    
    
    
        }
    }
    

    The View

    <!-- Guitar Object #1 -->
         <div id="cosponsorsTemplate_1">
                <div class="formColumn1">@Html.LabelFor(x=>x.Guitar1)</div>
                <div class="formColumn2">@Html.TextBoxFor(x => x.Guitar1.Make, new { Placeholder = "Make" })
                    <div class="messageBottom">@Html.ValidationMessageFor(x => x.Guitar1.Make)</div>
                </div>
                <div class="formColumn3">@Html.TextBoxFor(x => x.Guitar1.Model, new { Placeholder = "Model" })
                    <div class="messageBottom">@Html.ValidationMessageFor(x => x.Guitar1.Model)</div>
                </div>
                <div class="formColumn4">@Html.TextBoxFor(x =>x.Guitar1.ProductionYear, new { Placeholder = "Production Year" })
                    <div class="messageBottom">@Html.ValidationMessageFor(x => x.Guitar1.ProductionYear)</div>
                    <a class="icon delete" data-delete-id="1">Delete</a>
                </div>
            </div>
    
       <!-- Guitar Object #2 -->
            <div id="cosponsorsTemplate_2">
                <div class="formColumn1">@Html.LabelFor(x=>x.Guitar2)</div>
                <div class="formColumn2">@Html.TextBoxFor(x => x.Guitar2.Make, new { Placeholder = "Make" })
                    <div class="messageBottom">@Html.ValidationMessageFor(x => x.Guitar2.Make)</div>
                </div>
                <div class="formColumn3">@Html.TextBoxFor(x => x.Guitar2.Model, new { Placeholder = "Model" })
                    <div class="messageBottom">@Html.ValidationMessageFor(x => x.Guitar2.Model)</div>
                </div>
                <div class="formColumn4">@Html.TextBoxFor(x => x.Guitar2.ProductionYear, new { Placeholder = "Production Year" })
                    <div class="messageBottom">@Html.ValidationMessageFor(x => x.Guitar2.ProductionYear)</div>
                    <a class="icon delete" data-delete-id="2">Delete</a>
                </div>
            </div>
    
        <!-- Guitar Object #3 -->
            <div id="cosponsorsTemplate_3">
                <div class="formColumn1">@Html.LabelFor(x=>x.Guitar3)</div>
                <div class="formColumn2">@Html.TextBoxFor(x => x.Guitar3.Make, new { Placeholder = "Make" })
                    <div class="messageBottom">@Html.ValidationMessageFor(x => x.Guitar3.Make)</div>
                </div>
                <div class="formColumn3">@Html.TextBoxFor(x => x.Guitar3.Model, new { Placeholder = "Model" })
                    <div class="messageBottom">@Html.ValidationMessageFor(x => x.Guitar3.Model)</div>
                </div>
                <div class="formColumn4">@Html.TextBoxFor(x => x.Guitar3.ProductionYear, new { Placeholder = "Production Year" })
                    <div class="messageBottom">@Html.ValidationMessageFor(x => x.Guitar3.ProductionYear)</div>
                    <a class="icon delete" data-delete-id="3">Delete</a>
                </div>
            </div>
    
    2

    2 Answers

    2
    votes

    The documentation states that complex graphs should use a specialized validator. You can utilize it like so:

    public class Customer {
      public string Name { get; set; }
      public Address Address { get; set; }
    }
    
    public class Address {
     public string Line1 { get; set; }
     public string Line2 { get; set; }
     public string Town { get; set; }
     public string County { get; set; }
     public string Postcode { get; set; }
    }
    
    public class AddressValidator : AbstractValidator<Address> {
      public AddressValidator() {
        RuleFor(address => address.Postcode).NotNull();
        //etc
      }
    }
    
    public class CustomerValidator : AbstractValidator<Customer> {
      public CustomerValidator() {
        RuleFor(customer => customer.Name).NotNull();
        RuleFor(customer => customer.Address).SetValidator(new AddressValidator())
      }
    

    }

    From: Re-using Validators for Complex Properties

    http://fluentvalidation.codeplex.com/wikipage?title=CreatingAValidator&referringTitle=Documentation

    For your use case, you would create a Guitar Validator and a model validator for the aggregation object. I remember a friend running into similar problems because the validator doesn't know how to traverse the object graph without the intermediary validator.

    0
    votes

    I figured out the issue.

    The Guitar class

    public class Guitar
    {
        public string Model { get; set; }
        public int? ProductionYear { get; set; } 
        public string Make { get; set; }
    }
    

    Needs a Validator type decorator

     [Validator(typeof(GuitarValidator))]
    



    Correct

        [Validator(typeof(GuitarValidator))]
        public class Guitar
        {
            public string Model { get; set; }
            public int? ProductionYear { get; set; } 
            public string Make { get; set; }
        }