EmiratesKit.FluentValidation 1.0.1

dotnet add package EmiratesKit.FluentValidation --version 1.0.1
                    
NuGet\Install-Package EmiratesKit.FluentValidation -Version 1.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="EmiratesKit.FluentValidation" Version="1.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="EmiratesKit.FluentValidation" Version="1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="EmiratesKit.FluentValidation" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add EmiratesKit.FluentValidation --version 1.0.1
                    
#r "nuget: EmiratesKit.FluentValidation, 1.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package EmiratesKit.FluentValidation@1.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=EmiratesKit.FluentValidation&version=1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=EmiratesKit.FluentValidation&version=1.0.1
                    
Install as a Cake Tool

EmiratesKit.FluentValidation

NuGet Build License: MIT

FluentValidation rule extensions for UAE documents. Adds .ValidEmiratesId(), .ValidUaeIban(), .ValidUaeTrn(), .ValidUaeMobile(), and .ValidUaePassport() extension methods to FluentValidation rule builders.

Depends on EmiratesKit.Core and FluentValidation 11.x, both installed automatically.


Installation

dotnet add package EmiratesKit.FluentValidation

EmiratesKit.Core and FluentValidation are installed automatically as dependencies.


When to Use This Package

Use EmiratesKit.FluentValidation when you are already using FluentValidation in your project and want UAE validation rules that integrate naturally into your existing validator classes.

Choose this over EmiratesKit.Annotations when you need:

  • Conditional validation with .When() and .Unless()
  • Cross-field rules that depend on other properties
  • Custom error messages per rule without polluting your model
  • Chaining UAE rules with other FluentValidation rules like .NotEmpty(), .MaximumLength(), or .WithSeverity()
  • Testable validators through FluentValidation's own test helpers

Available Extensions

All extensions are on IRuleBuilder<T, string?> and work with nullable string properties.

Extension Validates
.ValidEmiratesId() Emirates ID format 784-YYYY-NNNNNNN-C with Luhn check
.ValidUaeIban() UAE IBAN starting with AE, 23 characters, Mod-97 check
.ValidUaeTrn() 15-digit TRN starting with 100
.ValidUaeMobile() UAE mobile in any accepted format with carrier prefix check
.ValidUaePassport() 1 uppercase letter followed by 7 digits

Basic Usage

using FluentValidation;
using EmiratesKit.FluentValidation.Extensions;

public class CreateCustomerValidator : AbstractValidator<CreateCustomerRequest>
{
    public CreateCustomerValidator()
    {
        RuleFor(x => x.EmiratesId)
            .NotEmpty()
            .ValidEmiratesId();

        RuleFor(x => x.Mobile)
            .NotEmpty()
            .ValidUaeMobile();

        RuleFor(x => x.BankAccount)
            .ValidUaeIban()
            .When(x => x.BankAccount is not null);

        RuleFor(x => x.TaxRegistrationNumber)
            .ValidUaeTrn()
            .When(x => x.TaxRegistrationNumber is not null);

        RuleFor(x => x.PassportNumber)
            .ValidUaePassport()
            .When(x => x.PassportNumber is not null);
    }
}

Custom Error Messages

Pass a custom message directly to the extension method:

RuleFor(x => x.EmiratesId)
    .NotEmpty().WithMessage("Emirates ID is required.")
    .ValidEmiratesId("The Emirates ID you entered is not valid. Expected format: 784-YYYY-NNNNNNN-C.");

RuleFor(x => x.Mobile)
    .NotEmpty().WithMessage("Mobile number is required.")
    .ValidUaeMobile("Enter a valid UAE mobile number (e.g. 050 123 4567 or +971 50 123 4567).");

RuleFor(x => x.BankAccount)
    .ValidUaeIban("Bank account must be a valid UAE IBAN starting with AE.")
    .When(x => x.BankAccount is not null);

If no message is passed, a default message is used that includes the error code from the underlying validator (e.g. INVALID_CHECKSUM).


Conditional Validation

Use .When() to apply a rule only under certain conditions:

// Validate TRN only when the customer is VAT-registered
RuleFor(x => x.TaxRegistrationNumber)
    .NotEmpty().WithMessage("TRN is required for VAT-registered customers.")
    .ValidUaeTrn()
    .When(x => x.IsVatRegistered);

// Validate passport only when Emirates ID is not provided
RuleFor(x => x.PassportNumber)
    .NotEmpty().WithMessage("Either Emirates ID or Passport Number is required.")
    .ValidUaePassport()
    .When(x => string.IsNullOrEmpty(x.EmiratesId));

// Validate IBAN only when payment method is bank transfer
RuleFor(x => x.BankAccount)
    .NotEmpty().WithMessage("Bank account is required for bank transfer.")
    .ValidUaeIban()
    .When(x => x.PaymentMethod == "BankTransfer");

Chaining with Other Rules

The extensions return the same IRuleBuilder so you can chain additional FluentValidation rules before or after:

RuleFor(x => x.EmiratesId)
    .NotEmpty()
    .MaximumLength(20)       // runs before UAE validation
    .ValidEmiratesId()
    .WithSeverity(Severity.Error);

RuleFor(x => x.Mobile)
    .NotEmpty()
    .ValidUaeMobile()
    .WithName("Phone Number");   // changes the field name in error messages

Registering with ASP.NET Core

Register your validators with the DI container using FluentValidation's built-in extension:

// Program.cs
using FluentValidation;
using FluentValidation.AspNetCore;

builder.Services.AddFluentValidationAutoValidation();
builder.Services.AddValidatorsFromAssemblyContaining<CreateCustomerValidator>();

With AddFluentValidationAutoValidation(), validation runs automatically on every request — the same behaviour as DataAnnotations, but using your FluentValidation validators. Controllers do not need to check ModelState.IsValid.


Manual Validation

To validate manually outside of the request pipeline:

var validator = new CreateCustomerValidator();
var result    = await validator.ValidateAsync(request);

if (!result.IsValid)
{
    foreach (var error in result.Errors)
    {
        Console.WriteLine($"{error.PropertyName}: {error.ErrorMessage}");
        // EmiratesId: The Emirates ID you entered is not valid.
    }
}

Testing Your Validators

Use FluentValidation's TestValidate helper to write clean unit tests:

using FluentValidation.TestHelper;

public class CreateCustomerValidatorTests
{
    private readonly CreateCustomerValidator _validator = new();

    [Fact]
    public void Should_Pass_With_Valid_Emirates_Id()
    {
        var model = new CreateCustomerRequest
        {
            EmiratesId = "784-1990-1234567-6",
            Mobile     = "+971501234567"
        };

        _validator.TestValidate(model).ShouldNotHaveAnyValidationErrors();
    }

    [Fact]
    public void Should_Fail_When_Emirates_Id_Has_Wrong_Checksum()
    {
        var model = new CreateCustomerRequest
        {
            EmiratesId = "784-1990-0000000-0",
            Mobile     = "+971501234567"
        };

        _validator.TestValidate(model)
                  .ShouldHaveValidationErrorFor(x => x.EmiratesId);
    }

    [Fact]
    public void Should_Not_Validate_Iban_When_Null()
    {
        var model = new CreateCustomerRequest
        {
            EmiratesId  = "784-1990-1234567-6",
            Mobile      = "+971501234567",
            BankAccount = null
        };

        _validator.TestValidate(model)
                  .ShouldNotHaveValidationErrorFor(x => x.BankAccount);
    }

    [Fact]
    public void Should_Fail_With_Invalid_Iban()
    {
        var model = new CreateCustomerRequest
        {
            EmiratesId  = "784-1990-1234567-6",
            Mobile      = "+971501234567",
            BankAccount = "AE000000000000000000000"
        };

        _validator.TestValidate(model)
                  .ShouldHaveValidationErrorFor(x => x.BankAccount);
    }
}

Null and Empty Behaviour

All extension methods treat null and empty string as passing — they do not add a validation error for missing values. This matches FluentValidation's own convention.

To require a value, chain .NotEmpty() before the UAE extension:

// Required — fails if null, empty, or invalid
RuleFor(x => x.EmiratesId)
    .NotEmpty()
    .ValidEmiratesId();

// Optional — only validated when a value is provided
RuleFor(x => x.PassportNumber)
    .ValidUaePassport()
    .When(x => !string.IsNullOrEmpty(x.PassportNumber));

Package Purpose
EmiratesKit.Core Core validators, static API, dependency injection support
EmiratesKit.Annotations DataAnnotation attributes for automatic model binding validation

License

MIT License. Copyright 2026 Akhil P Vijayan.

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.1 79 2/21/2026