FluentValidation: Check if one of two fields are empty
Asked Answered
G

7

83

I have this model

public class Person
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }  
}

I want to create a validation where either FirstName or LastName must be filled in by user. I installed FluentValidation and created a customvalidator class

public class PersonValidator:AbstractValidator<Person>
{
    public PersonValidator()
    {
        RuleFor((person=>person.FirstName)//don't know how to check if one is empty
    }
}

To check just one field I could just do RuleFor(person => person.FirstName).NotNull();

But how do I check if one of them is null.

Also, is it possible, once validation is created via fluentValidation, use it on the client side to show error?

Edit1

    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
        FluentValidationModelValidatorProvider.Configure();
    }
//creating validation
namespace WebApplication1.Models.CustomValidator
{
    public class PersonValidator:AbstractValidator<Person>
    {
        public PersonValidator()
        {
            RuleFor(m => m.FirstName).NotEmpty().When(m => string.IsNullOrEmpty(m.LastName)).WithMessage("*Either First Name or Last Name is required");
            RuleFor(m => m.LastName).NotEmpty().When(m => string.IsNullOrEmpty(m.FirstName)).WithMessage("*Either First Name or Last Name is required");
        }
    }

}
//model class
[Validator(typeof(PersonValidator))]
public class Person
{
    public Person()
    {
        InterestList = new List<string>();
    }
    public int Id { get; set; }
    public int ContactId { get; set; }
    [RequiredIfEmpty("LastName")]
    public string FirstName { get; set; }
    [RequiredIfEmpty("FirstName")]
    public string LastName { get; set; }
    public string EmailAddress { get; set; }
    public string Phone { get; set; }
    public string Country { get; set; }
    public List<string> InterestList { get; set; } 
}
//view
@model WebApplication1.Models.Person

<script src="@Url.Content("~/Scripts/jquery-1.8.2.min.js")"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")"></script>
<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>

@Html.ValidationSummary(true)
@using(Html.BeginForm("AddPerson","Person",FormMethod.Post))
{
    <div class="label">First Name</div>
    <div class="input-block-level">@Html.TextBoxFor(m=>m.FirstName)@Html.ValidationMessageFor(m=>m.FirstName)</div>
    <br/>
    <div class="label">Last Name</div>
    <div class="input-block-level">@Html.TextBoxFor(m=>m.LastName)@Html.ValidationMessageFor(m=>m.LastName)</div>
    <button type="submit" class="btn-primary">Submit</button>
}
Gualterio answered 14/1, 2014 at 13:49 Comment(1)
What is the RequiredIfEmpty attribute?Heavenly
H
138

You can use When/Unless condition:

RuleFor(m => m.FirstName).NotEmpty().When(m => string.IsNullOrEmpty(m.LastName));
RuleFor(m => m.LastName).NotEmpty().When(m => string.IsNullOrEmpty(m.FirstName));

or

RuleFor(m => m.FirstName).NotEmpty().Unless(m => !string.IsNullOrEmpty(m.LastName));
RuleFor(m => m.LastName).NotEmpty().Unless(m => !string.IsNullOrEmpty(m.FirstName));

As for your second question, FluentValidation works with client-side validation, but not all rules are supported. Here you can find validators, that are supported on the client-side:

  1. NotNull/NotEmpty
  2. Matches (regex)
  3. InclusiveBetween (range)
  4. CreditCard
  5. Email
  6. EqualTo (cross-property equality comparison)
  7. Length

For rules that are not in the list you have to write your own FluentValidationPropertyValidator and implement GetClientValidationRules. You can find a few samples of this on the StackOverflow by doing simple search.

Heavenly answered 14/1, 2014 at 14:16 Comment(4)
I'm using .NotEmpty and .When so to show client side error what shoud i do? I'll edit my question so you can see what new i have added.Gualterio
I just realized it doesn't work for when and unless. But still the fluentValidation is not workingGualterio
Is there any way to do this but without having to populate both properties with the model state error?Consanguinity
not working for me, it is still complaining one field is missing while the other is populatedNorvell
G
22

Try this

RuleFor(person => person).Must(person => !string.IsNullOrEmpty(person.FirstName) || !string.IsNullOrEmpty(person.LastName))
Gilead answered 4/9, 2014 at 14:7 Comment(5)
This fails when using ValidateAsync :(Raffle
This doesn't work for me. I got the following error: Property name could not be automatically determined for expression item => item. Please specify either a custom property name by calling 'WithName'.Hl
@VladSchnakovszki what's item?Gilead
@FatShogun, it's the equivalent of person in your example.Hl
@VladSchnakovszki afaik that error is produced when trying to validate something other than a property. Paste your relevant code and I'll see if I can provide you with a better answer.Gilead
S
6

I did like this to check charges entered are same to previous one or not. If charges are same as previous one than it'll give an error. This worked for me.

public class CasualMealChargeValidator : AbstractValidator<CasualMealCharge>
{
    public CasualMealChargeValidator(CasualMealCharge CMC)
    {
        //RuleFor(x => x.BankName).NotEmpty().When(pm => pm.PaymentMode == "Cheque").WithMessage("Enter Bank.");
        RuleFor(x => x).Must(x => x.DN != CMC.DN || x.BF != CMC.BF || x.LN != CMC.LN).WithMessage("Not Saved - Meal charges are same as current charges.").WithName("CMFor");
    }
}
Seem answered 6/2, 2016 at 12:17 Comment(0)
L
5

Finally, this worked for me. I wanted to validate three properties where at least one is required. It returns an error message only once.

RuleFor(p => p).Cascade(CascadeMode.StopOnFirstFailure)
            .Must(p => !string.IsNullOrWhiteSpace(p.FirstName))
            .When(p => p.Id == 0 && string.IsNullOrWhiteSpace(p.LastName)).WithMessage("At least one is required (Id, FirstName, LastName).")
            .Must(p => !string.IsNullOrWhiteSpace(p.LastName))
            .When(p => p.Id == 0 && string.IsNullOrWhiteSpace(p.FirstName)).WithMessage("At least one is required (Id, FirstName, LastName).")
            .Must(p => p.Id != 0)
            .When(p => string.IsNullOrWhiteSpace(p.FirstName) && string.IsNullOrWhiteSpace(p.LastName)).WithMessage("At least one is required (Id, FirstName, LastName).");
Lampkin answered 5/7, 2018 at 23:53 Comment(3)
Surely you only need the first Must()/When()?Zippel
@Zippel Are you asking or saying that I can improve?Lampkin
Perhaps I'm missing something, but if all three values are null / 0, the first Must() would trigger. Why bother with the other two Must(). If you we're having different messages as @Pavo answer it would make sense but your messages are all the same. I copied your code as it was what I was after but once I'd written my tests I found I didn't need the other Must(), hence my comment.Zippel
C
2

I don't know that library, but if you just want to check those two properties for null, then you can use this:

RuleFor(person => person.FirstName ?? person.LastName).NotNull();

EDIT This doesn't work, because it throws an InvalidOperationException. Use Zabavsky's solution instead.

Compton answered 14/1, 2014 at 14:0 Comment(3)
And here the OP really wants to use NotEmpty() as that checks for both null or and empty string.Interfluve
The OP clearly say he's using FluentValidation. I don't know if I have outdated library, but this answer threw System.InvalidOperationException with error message: Property name could not be automatically determined for expression person => (person.FirstName ?? person.LastName). Please specify either a custom property name by calling 'WithName'.Manet
@Manet You are right. I edited the answer. Use Zabavsky's solution instead.Compton
P
1

A nice rule-set to check if one of two fields are empty, as well as coming up with meaningful error codes is the following:

public CustomerSourceValidator()
    {
        CascadeMode = CascadeMode.StopOnFirstFailure;

        RuleFor(x => x)
            .NotNull().WithErrorCode("source_id_or_email_required")
            .When(source => source.Email == null && source.Id == null);

        RuleFor(x => x.Id)
            .NotNull().WithErrorCode("source_id_required")
            .Matches(CommonValidationRegex.CustomerIdRegexString).WithErrorCode("source_id_invalid")
            .When(source => source.Id != null);

        RuleFor(x => x.Email)
            .NotNull().WithErrorCode("source_email_required")
            .Matches(CommonValidationRegex.EmailRegexString).WithErrorCode("source_email_invalid")
            .When(source => source.Email != null);
    }
Pavo answered 22/3, 2018 at 12:28 Comment(0)
P
1

For people who have similar cases but with more properties (e.g. two fields out of four need to have a value), you can use something like this:

public class PersonValidator : AbstractValidator<Person>
{
    public PersonValidator()
    {
        RuleFor(person => person)
            .Must(MustHaveAtLeastTwoProperties)
            .WithMessage("Random validation message");
    }

    private bool MustHaveAtLeastTwoProperties(Person person)
    {
        return new[] { person.FirstName, person.LastName, person.Address1, person.Address2 } // Added Address1 and Address2 as new string properties
            .Count(prop => !string.IsNullOrWhiteSpace(prop)) >= 2; // Set to how many properties you want to have a value
    }
}
Perceptive answered 24/10, 2023 at 12:40 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.