Validated.Core 1.0.4

dotnet add package Validated.Core --version 1.0.4
                    
NuGet\Install-Package Validated.Core -Version 1.0.4
                    
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="Validated.Core" Version="1.0.4" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Validated.Core" Version="1.0.4" />
                    
Directory.Packages.props
<PackageReference Include="Validated.Core" />
                    
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 Validated.Core --version 1.0.4
                    
#r "nuget: Validated.Core, 1.0.4"
                    
#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 Validated.Core@1.0.4
                    
#: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=Validated.Core&version=1.0.4
                    
Install as a Cake Addin
#tool nuget:?package=Validated.Core&version=1.0.4
                    
Install as a Cake Tool

.NET Coverage Status

icon Validated

A functional approach to validation in C#.

Validated provides a composable, functional validation framework for .NET applications. It’s designed to make validation predictable, testable, and reusable — from simple property checks to validating complex object graphs, collections, and multi-tenant scenarios driven by configuration.

Full documentation available at: https://code-dispenser.gitbook.io/validated-docs

Features

  • Validated<T> result type (or applicative functor for those who care) for all validations (valid or invalid with failure details).

  • MemberValidator<T> delegate as the core building block — create custom or use built-in validators.

  • Fluent ValidationBuilder<TEntity> for static/manual composition of validators.

  • TenantValidationBuilder<TEntity> for configuration-driven, multi-tenant, multi-culture validation.

  • Combine validators for objects, nested objects, collections, and recursive graphs.

  • Built-in validators for common use cases (regex, length, comparisons, etc.).

  • Fully async to support your own async validators.

1. Getting started

Add the Validated.Core nuget package to your project using Nuget Package Manager or the dotnet CLI:

dotnet add package Validated.Core

2. The basics - Validated<T> Type, used for all returns (the seed that sprouted to form this library).

Every validator returns a Validated<T>, which is either Valid (with the value) or Invalid (with a list of one or more InvalidEntry objects). But its just a type, so it can be returned from any method.

private static Validated<string> Hello(string input)  
     => input == "World"
            ? Validated<string>.Valid(input + " is correct")
                : Validated<string>.Invalid(new InvalidEntry("Expected 'World' to be entered"));

3. MemberValidator<T> deletage (the building block for all validators)

The core building block is MemberValidator<T>, which is simply a delegate (function) that takes a value (also has optional paramaters, more on those later) and returns a Validated<T>. You can implement your own validators by writing any function that matches this signature:


    public static MemberValidator<string> CreateHelloWorldValidator(string failureMessage)

        => (valueToValidate, path, _, _) => // the delegate needs a value, but we can discard optional params if not needed (path, compareTo, cancellationToken)  
        {
            /*
                * The delegate MemberValidator returns a Task<Validated<T>> so as we have no async stuff in here to await we just use Task.FromResult 
            */ 
            return (valueToValidate == "World") ? Task.FromResult(Validated<string>.Valid(valueToValidate)) 
                                                    : Task.FromResult(Validated<string>.Invalid(new InvalidEntry(failureMessage,path)));
                                                      //path is good to add as its populated when validating entities
        };   

4. Built-In Validators

The library provides a static MemberValidators class with common, pre-built validator factories for scenarios like regex, string length, ranges, and more.

You can compose validators together using the .AndThen() extension method. This chains validators together, and crucially, all validators are executed to accumulate every failure.

Note: It can sometimes be beneficial to create separate validators even if one can do the job.
For example regex patterns can include lengths, but it may be better to have two separate validators to gain two failure messages rather than trying to squeeze all the information into a long failure message, when in reality its only one part of the data that was errant like the length

// Create a validator for a name that must start with a capital, not have double spaces,apostrophes or double dashes 
// and be between 2 and 50 characters.

var namePattern = @"^[A-Z]+['\- ]?[A-Za-z]*['\- ]?[A-Za-z]+$";

// Chain two validators together
var nameValidator = MemberValidators.CreateStringRegexValidator(
                        namePattern, "FirstName", "First name", "Must start with a capital and not contain double spaces, apostrophes or dashes"
                        )
                    .AndThen(MemberValidators.CreateStringLengthValidator(
                            2, 50,"FirstName", "First name","Must be between 2 and 50 characters in length")
                        );

// --- Usage ---
var validatedName = await nameValidator("S"); // This will fail both checks

// The result contains two failures
Console.WriteLine(validatedName.Failures.Count); // Outputs: 2
validatedName.Failures.ToList().ForEach(f => Console.WriteLine(f.FailureMessage));
// Outputs:
// Must start with a capital and not contain double spaces, apostrophes or dashes
// Must be between 2 and 50 characters in length

5. Using the ValidationBuilder<TEntity>

For validating complex objects, the ValidationBuilder<TEntity> provides a fluent and discoverable API.


var contactValidator = ValidationBuilder<ContactDto>.Create()
    .ForMember(c => c.Title,
        MemberValidators.CreateStringRegexValidator("^(Mr|Mrs|Ms|Dr|Prof)$", "Title", "Title", "Invalid title"))
    .ForMember(c => c.GivenName,
        MemberValidators.CreateStringRegexValidator(@"^[A-Z][a-z]+$", "GivenName", "First name", "Invalid name"))
    .Build();

var contact   = new ContactDto { Title = "Mr", GivenName = "John" };
var validated = await contactValidator(contact);

Note: Reuse with shared validators.
Because MemberValidator<T> is just a delegate, you can (and should) place common ones in shared static classes to avoid duplication.

For example, you could put your frequently reused field validators in a GeneralFieldValidators class:

public static class GeneralFieldValidators
{
    public static MemberValidator<string> TitleValidator() 
        
        => MemberValidators.CreateStringRegexValidator("^(Mr|Mrs|Ms|Dr|Prof)$", "Title", "Title", "Must be a valid title");

    public static MemberValidator<string> GivenNameValidator() 
        
        => MemberValidators.CreateStringRegexValidator(@"^(?=.{2,50}$)[A-Z][a-z]+$", "GivenName", "First name", "Must start with a capital and be 2–50 characters");

    public static MemberValidator<string> FamilyNameValidator() 
    
        => MemberValidators.CreateStringRegexValidator(@"^[A-Z][a-z]+$", "FamilyName", "Surname", "Must start with a capital letter")
            .AndThen(MemberValidators.CreateStringLengthValidator(2, 50, "FamilyName", "Surname", "Must be between 2 and 50 characters"));
}

//these are just returning functions with the provided details baked in, ready for the value to validate.

Now a ValidationBuilder can reuse the appropriate validator for any matching fields:

var contactValidator = ValidationBuilder<ContactDto>.Create()
                        .ForMember(c => c.Title, GeneralFieldValidators.TitleValidator())
                            .ForMember(c => c.GivenName, GeneralFieldValidators.GivenNameValidator())
                                .ForMember(c => c.FamilyName, GeneralFieldValidators.FamilyNameValidator())
                                    .Build();

var validated = await contactValidator(contact)

Handling Nested Objects and Nullable Properties

The builder makes it easy to handle complex object graphs.

  • Use ForNestedMember to validate a property that is itself a complex object. You can reuse another builder for the nested type.

  • Use ForNullableMember for optional properties. Validation is only triggered if the property is not null.

  • Use ForNullableNestedMember for optional complex object properties.

// First, create a validator for the nested AddressDto
var addressValidator = ValidationBuilder<AddressDto>.Create()
                        .ForMember(a => a.AddressLine, GeneralFieldValidators.AddressLineValidator())
                            .ForMember(a => a.TownCity, GeneralFieldValidators.TownCityValidator())
                                .ForMember(a => a.County, GeneralFieldValidators.CountyValidator())
                                    .ForNullableStringMember(a => a.Postcode, GeneralFieldValidators.UKPostcodeValidator()) // Nullable primitive
                                        .Build();

// Now, use it in the parent ContactDto validator
var contactValidator = ValidationBuilder<ContactDto>.Create()
                        .ForMember(c => c.GivenName, GeneralFieldValidators.GivenNameValidator())
                            .ForMember(c => c.FamilyName, GeneralFieldValidators.FamilyNameValidator())
                                .ForNullableMember(c => c.NullableAge, GeneralFieldValidators.NullableAgeValidator()) // Nullable value type
                                 .ForNestedMember(c => c.Address, addressValidator) // Required nested object
                                    .ForNullableNestedMember(c => c.NullableAddress, addressValidator) // Optional nested object
                                        .Build();

var validated = await contactValidator(contact)

Validating Collections

The builder has specific methods for validating collections:

  • ForEachCollectionMember: Validates each item in a collection of complex types.

  • ForEachPrimitiveItem: Validates each item in a collection of primitive types.

  • ForCollection: Validates the collection itself (e.g., its size).

// Validator for items in the ContactMethods collection
var contactMethodValidator = ValidationBuilder<ContactMethodDto>.Create()
                                .ForMember(c => c.MethodType, GeneralFieldValidators.MethodTypeValidator())
                                    .ForMember(c => c.MethodValue, GeneralFieldValidators.MethodValueValidator())
                                        .Build();

var contactValidator = ValidationBuilder<ContactDto>.Create()
                        // Validate each string in the 'Entries' list
                        .ForEachPrimitiveItem(c => c.Entries, GeneralFieldValidators.EntryValidator())
                            // Validate the 'Entries' list itself (e.g., must have 1-3 items)
                            .ForCollection(c => c.Entries, GeneralFieldValidators.EntryCountValidator())
                                // Validate each complex object in the 'ContactMethods' list
                                .ForEachCollectionMember(c => c.ContactMethods, contactMethodValidator)
                                    .Build();

var validated = await contactValidator(contact)

6. Advanced Usage: Dynamic Validation with TenantValidationBuilder<TEntity>

For multi-tenant applications or scenarios where validation rules need to be dynamic, the library provides the TenantValidationBuilder<TEntity>.

Instead of providing validator instances directly, this builder creates them at runtime from a list of ValidationRuleConfig objects. This configuration data can be loaded from a database, a JSON file, or any other source, and can be cached and periodically refreshed. It supports tenant- and culture-specific rule resolution. But just like the rest of the library everything is comprised of the MemberValidator<T> delegate, so just functions all the way until your Validated<T> return (all without reflection or source generators).

More detailed standalone solutions showing more advanced usage of the library will be placed in the main examples folder in the repo as they are completed.

It is also possible to validate Value Object both statically and dynamically from configuration data in such a way as not to violate (IMHO) your protected core domain project.

7. Demo Project

A Console Demo project is included within the solution containing the project for the Nuget so you can step into to source code if necessary.

Just uncomment the section you want to run in Program.cs.

8. Roadmap

Full documentation (working on).

Advanced scenario solutions to be placed in the /examples folder showing:

  • Multi-tenant configuration-driven validators (TenantValidationBuilder)
  • Validating domain value objects without polluting your domain model (Done).
  • Advanced collection and recursive scenarios (Done).
  • Creating a custom multi-tenant / dynamic config based validator (Done).
Product Compatible and additional computed target framework versions.
.NET 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 is compatible.  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.4 121 9/2/2025
1.0.3 131 9/1/2025
1.0.2 142 8/31/2025
1.0.1 172 8/27/2025
1.0.0 155 8/24/2025

Fixed issue in the ComparisonValidatorFactory that affected Tenant Member comparisons
against the value entered in the configuration data.