FluentValidation
validation rules
C#
.NET
conditional validation

FluentValidation Check if one of two fields are empty

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

A common validation rule is "at least one of these two fields must be provided." In FluentValidation, that is usually best expressed as an object-level rule, because the condition depends on more than one property at the same time. Once you frame it that way, the implementation becomes straightforward and easy to test.

Model And Validation Goal

Suppose a request is valid if the user provides either an email address or a phone number.

csharp
1public class ContactRequest
2{
3    public string? Email { get; set; }
4    public string? PhoneNumber { get; set; }
5}

The business rule is:

  • email can be present
  • phone number can be present
  • both can be present
  • but both cannot be missing or whitespace

That rule spans the whole object, not a single field.

Use RuleFor(x => x) With Must

The cleanest FluentValidation pattern is an object-level rule.

csharp
1using FluentValidation;
2
3public class ContactRequestValidator : AbstractValidator<ContactRequest>
4{
5    public ContactRequestValidator()
6    {
7        RuleFor(x => x)
8            .Must(request =>
9                !string.IsNullOrWhiteSpace(request.Email) ||
10                !string.IsNullOrWhiteSpace(request.PhoneNumber))
11            .WithMessage("Either Email or PhoneNumber must be provided.");
12    }
13}

This works because RuleFor(x => x) lets the predicate inspect the entire object.

It is usually better than attaching the rule to only one property, because the error message clearly describes a relationship between fields rather than pretending one field is solely responsible.

Add Field-Specific Rules Too

Cross-field validation does not replace normal field validation. You can still validate format only when a field is present.

csharp
1using FluentValidation;
2
3public class ContactRequestValidator : AbstractValidator<ContactRequest>
4{
5    public ContactRequestValidator()
6    {
7        RuleFor(x => x)
8            .Must(request =>
9                !string.IsNullOrWhiteSpace(request.Email) ||
10                !string.IsNullOrWhiteSpace(request.PhoneNumber))
11            .WithMessage("Either Email or PhoneNumber must be provided.");
12
13        When(x => !string.IsNullOrWhiteSpace(x.Email), () =>
14        {
15            RuleFor(x => x.Email!)
16                .EmailAddress()
17                .WithMessage("Email is not valid.");
18        });
19    }
20}

That keeps the intent clear:

  • one rule ensures at least one field exists
  • another rule checks the format of the optional email when it is supplied

If You Mean "Exactly One" Instead Of "At Least One"

Sometimes the real requirement is different: exactly one of the two fields must be filled, not both. That logic needs a different predicate.

csharp
1RuleFor(x => x)
2    .Must(request =>
3    {
4        bool hasEmail = !string.IsNullOrWhiteSpace(request.Email);
5        bool hasPhone = !string.IsNullOrWhiteSpace(request.PhoneNumber);
6        return hasEmail ^ hasPhone;
7    })
8    .WithMessage("Provide either Email or PhoneNumber, but not both.");

This is a good reminder to state the business rule precisely before writing the validator. "One of two fields" can mean two different things in real systems.

Testing The Validator

Because this logic is cross-field, unit tests are especially useful.

csharp
1var validator = new ContactRequestValidator();
2
3Console.WriteLine(validator.Validate(new ContactRequest()).IsValid);
4Console.WriteLine(validator.Validate(new ContactRequest { Email = "[email protected]" }).IsValid);
5Console.WriteLine(validator.Validate(new ContactRequest { PhoneNumber = "123456" }).IsValid);

Testing both valid and invalid combinations helps prevent regressions when the validator grows.

Common Pitfalls

The most common mistake is attaching the rule to only one property with RuleFor(x => x.Email) and then trying to inspect the other field indirectly. That usually produces awkward messages and unclear intent.

Another issue is forgetting to treat whitespace-only strings as empty. string.IsNullOrWhiteSpace is usually the right choice here.

It is also easy to confuse "at least one" with "exactly one." Those are different business rules and need different predicates.

Finally, do not stop at the cross-field rule if the individual fields have their own format requirements. Presence and format are separate concerns.

Summary

  • Use an object-level FluentValidation rule when the condition depends on multiple fields.
  • 'RuleFor(x => x).Must(...) is the cleanest way to express "at least one field is required."'
  • Keep format validation separate from cross-field presence checks.
  • Be explicit about whether the rule means "at least one" or "exactly one."
  • Test multiple field combinations so the validator stays trustworthy.

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.