19

I have an MVC3 view model defined as:

[Validator(typeof(AccountsValidator))]
public class AccountViewModel
{
    public List<string> Accounts { get; set; }
}

With the validation defined using FluentValidation (v3.3.1.0) as:

public class AccountsValidator : AbstractValidator<AccountViewModel>
{
    public AccountsValidator()
    {
        RuleFor(x => x.Accounts).SetCollectionValidator(new AccountValidator()); //This won't work
    }
}

And the account validation would possibly be defined:

public class AccountValidator : AbstractValidator<string> {
    public OrderValidator() {
        RuleFor(x => x).NotNull();
        //any other validation here
    }
}

I would like each account in the list to be valdiated as described in the documentation. However, the call to SetCollectionValidator doesn't work as this is not an option when using a List<string> although the option would be there if it were defined as List<Account>. Am I missing something? I could change my model to use List<Account> and then define an Account class but I don't really want to change my model to suit the validation.

For reference, this is the view that I am using:

@model MvcApplication9.Models.AccountViewModel

@using (Html.BeginForm())
{
    @*The first account number is a required field.*@
    <li>Account number* @Html.EditorFor(m => m.Accounts[0].Account) @Html.ValidationMessageFor(m => m.Accounts[0].Account)</li>

    for (int i = 1; i < Model.Accounts.Count; i++)
    {
        <li>Account number @Html.EditorFor(m => m.Accounts[i].Account) @Html.ValidationMessageFor(m => m.Accounts[i].Account)</li>
    }

    <input type="submit" value="Add more..." name="add"/>
    <input type="submit" value="Continue" name="next"/>
}
Dangerous
  • 4,818
  • 3
  • 33
  • 48

4 Answers4

23

The following should work:

public class AccountsValidator : AbstractValidator<AccountViewModel>
{
    public AccountsValidator()
    {
        RuleFor(x => x.Accounts).SetCollectionValidator(
            new AccountValidator("Accounts")
        );
    }
}

public class AccountValidator : AbstractValidator<string> 
{
    public AccountValidator(string collectionName)
    {
        RuleFor(x => x)
            .NotEmpty()
            .OverridePropertyName(collectionName);
    }
}
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • 1
    Thank you, SetCollectionValidator did work. I'm not quite sure why this statement was not available before! However, its still not working quite right. The validation is being used but any errors are now written to the model state with a key of "Accounts[0].Accounts". I use ValidationMessageFor(m => m.Accounts[i]) in the view to display each account number therefore the ModelState should have a key of "Accounts[0]". I have tried changing OverridePropertyName(collectionName) using a blank name and removing this statement entirely but still cannot get it to work. – Dangerous Apr 17 '12 at 15:12
  • I could get it working using @Html.ValidationMessage("Accounts[i].Accounts") but again this seems like a work around? I appreciate your thoughts on this? Thanks again. – Dangerous Apr 17 '12 at 15:13
  • 1
    I've decided to use an Account model rather than a string. Therefore, the errors are now reported correctly. The above collection name is therefore not required as a parameter. – Dangerous Apr 17 '12 at 16:26
  • I wish there was a shorthand for validating every string in an array since it should almost always require only 1 fluent string to validate each. The extra validator is boilerplate. – smurtagh Oct 17 '18 at 14:10
6

Try to use:

public class AccountsValidator : AbstractValidator<AccountViewModel>
{
   public AccountsValidator()
   {
       RuleForEach(x => x.Accounts).NotNull()
   }
}
R.Titov
  • 3,115
  • 31
  • 35
1

You could use RuleForEach There is an easy class with a list of string

   public class Request
    {
        public IEnumerable<string> UserIds { get; set; }      
        public string  Body { get; set; }        
    }

I created the next validator

public class RequestValidator : AbstractValidator<Request>
    {
        public RequestValidator()
        {        
            RuleForEach(x => x.UserIds).NotNull().NotEmpty();            
            RuleFor(x => x.Body).NotNull().NotEmpty();      
        }
    }
Larissa Savchekoo
  • 6,412
  • 1
  • 13
  • 7
0

Validation classes:

using FluentValidation;
using System.Collections.Generic;

namespace Test.Validator
{

    public class EmailCollection
    {
        public IEnumerable<string> email { get; set; }

    }

    public class EmailValidator:  AbstractValidator<string>
    {
        public EmailValidator()
        {
            RuleFor(x => x).Length(0, 5);
        }

    }

    public class EmailListValidator: AbstractValidator<EmailCollection>
    {
        public EmailListValidator()
        {
            RuleFor(x => x.email).SetCollectionValidator(new EmailValidator());
        }

    }



}

Note: I used latest MVC 5 (Nuget) version of fluentvalidation.

Giovanni Romio
  • 1,008
  • 1
  • 11
  • 30