Validated.Primitives 0.1.20

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

Validated Primitives

Build & Test NuGet NuGet Downloads License: MIT

A .NET library that provides strongly-typed, self-validating primitive value objects to eliminate primitive obsession and enforce domain constraints at compile-time.

What are Validated Primitives?

Validated Primitives are value objects that encapsulate primitive types (strings, dates, numbers) with built-in validation rules. Instead of passing raw strings or dates throughout your application, you use strongly-typed objects that guarantee their validity.

The Problem: Primitive Obsession

// ❌ Traditional approach - primitive obsession
public class User
{
    public string Email { get; set; }           // Could be null, empty, or invalid
    public string PhoneNumber { get; set; }     // No format validation
    public DateTime DateOfBirth { get; set; }   // Could be in the future!
    public string PostalCode { get; set; }      // No country-specific validation
}

// Validation logic scattered everywhere
if (string.IsNullOrWhiteSpace(user.Email) || !IsValidEmail(user.Email))
{
    throw new ArgumentException("Invalid email");
}

The Solution: Validated Primitives

// ✅ With Validated Primitives
public class User
{
    public EmailAddress Email { get; set; }       // Always valid or null
    public PhoneNumber PhoneNumber { get; set; }  // Always properly formatted
    public DateOfBirth DateOfBirth { get; set; }  // Always in the past
    public PostalCode PostalCode { get; set; }    // Country-specific validation
}

// Validation happens at creation - guaranteed valid everywhere else
var (result, email) = EmailAddress.TryCreate(userInput);
if (!result.IsValid)
{
    // Handle validation errors
    Console.WriteLine(result.ToBulletList());
    return;
}

// email is guaranteed to be valid here
user.Email = email;

Key Benefits

  • Type Safety: Compiler prevents mixing up different string types
  • Self-Validating: Validation logic lives with the data
  • Immutable: Value objects are records - thread-safe by default
  • Explicit Intent: Code clearly communicates business rules
  • Centralized Validation: No scattered validation logic
  • Rich Error Messages: Detailed validation feedback

Installation

dotnet add package Validated.Primitives

Available Value Objects

Email & Communication

  • EmailAddress - Valid email format, max 256 characters
  • PhoneNumber - Valid phone number format
  • WebsiteUrl - Valid HTTP/HTTPS URLs

Location

  • PostalCode - Country-specific postal code validation for 30+ countries

Network

  • IpAddress - Valid IPv4 or IPv6 addresses

Date & Time

  • DateOfBirth - Must be in the past
  • FutureDate - Must be in the future
  • BetweenDatesSelection - Date within a specified range
  • DateRange - Represents a range between two DateTimes
  • DateOnlyRange - Represents a range between two DateOnly values
  • TimeOnlyRange - Represents a range between two TimeOnly values

Usage Examples

Basic Usage

using Validated.Primitives.ValueObjects;

// Create a validated email address
var (result, email) = EmailAddress.TryCreate("user@example.com");

if (result.IsValid)
{
    Console.WriteLine($"Email: {email.Value}");
    // Use email.Value to access the underlying string
}
else
{
    // Handle validation errors
    foreach (var error in result.Errors)
    {
        Console.WriteLine($"Error: {error.Message}");
    }
}

Postal Codes with Country Validation

using Validated.Primitives.ValueObjects;

// Validate a US ZIP code
var (result, usZip) = PostalCode.TryCreate(CountryCode.UnitedStates, "12345");
if (result.IsValid)
{
    Console.WriteLine($"Valid US ZIP: {usZip.Value}");
    Console.WriteLine($"Country: {usZip.GetCountryName()}"); // "United States"
}

// Validate a UK postal code
var (result2, ukCode) = PostalCode.TryCreate(CountryCode.UnitedKingdom, "SW1A 1AA");
if (result2.IsValid)
{
    Console.WriteLine($"Valid UK postcode: {ukCode.Value}");
}

// Accept any format with Unknown or All
var (result3, genericCode) = PostalCode.TryCreate(CountryCode.All, "XYZ-123");
if (result3.IsValid)
{
    Console.WriteLine($"Generic postal code: {genericCode.Value}");
}

// Country-specific validation will fail for wrong formats
var (result4, invalid) = PostalCode.TryCreate(CountryCode.UnitedStates, "ABCDE");
if (!result4.IsValid)
{
    Console.WriteLine(result4.ToSingleMessage()); 
    // "PostalCode is not a valid postal code format for UnitedStates."
}

Supported Countries (30+):

  • United States, United Kingdom, Canada, Australia
  • Germany, France, Italy, Spain, Netherlands, Belgium, Switzerland, Austria
  • Sweden, Norway, Denmark, Finland, Poland, Czech Republic, Hungary, Portugal, Ireland
  • Japan, China, India, Brazil, Mexico, South Africa, New Zealand, Singapore, South Korea, Russia

Special Country Codes:

  • CountryCode.Unknown - Accepts any postal code format (basic validation only)
  • CountryCode.All - Accepts any postal code format (basic validation only)

Handling Validation Errors

var (result, phoneNumber) = PhoneNumber.TryCreate("invalid");

if (!result.IsValid)
{
    // Single line message
    Console.WriteLine(result.ToSingleMessage());
    
    // Bullet list format
    Console.WriteLine(result.ToBulletList());
    
    // Dictionary format (useful for APIs)
    var errors = result.ToDictionary();
    foreach (var kvp in errors)
    {
        Console.WriteLine($"{kvp.Key}: {string.Join(", ", kvp.Value)}");
    }
}

Domain Models

public class Customer
{
    public Guid Id { get; init; }
    public EmailAddress Email { get; init; }
    public PhoneNumber Phone { get; init; }
    public DateOfBirth BirthDate { get; init; }
    public PostalCode PostalCode { get; init; }
    public WebsiteUrl? Website { get; init; }
    
    public static (ValidationResult, Customer?) Create(
        string email,
        string phone,
        DateTime birthDate,
        CountryCode country,
        string postalCode,
        string? website = null)
    {
        var validationResult = ValidationResult.Success();
        
        var (emailResult, emailValue) = EmailAddress.TryCreate(email);
        validationResult.Merge(emailResult);
        
        var (phoneResult, phoneValue) = PhoneNumber.TryCreate(phone);
        validationResult.Merge(phoneResult);
        
        var (birthResult, birthValue) = DateOfBirth.TryCreate(birthDate);
        validationResult.Merge(birthResult);
        
        var (postalResult, postalValue) = PostalCode.TryCreate(country, postalCode);
        validationResult.Merge(postalResult);
        
        WebsiteUrl? websiteValue = null;
        if (!string.IsNullOrWhiteSpace(website))
        {
            var (websiteResult, webValue) = WebsiteUrl.TryCreate(website);
            validationResult.Merge(websiteResult);
            websiteValue = webValue;
        }
        
        if (!validationResult.IsValid)
            return (validationResult, null);
        
        var customer = new Customer
        {
            Id = Guid.NewGuid(),
            Email = emailValue!,
            Phone = phoneValue!,
            BirthDate = birthValue!,
            PostalCode = postalValue!,
            Website = websiteValue
        };
        
        return (validationResult, customer);
    }
}

// Usage
var (result, customer) = Customer.Create(
    "john.doe@example.com",
    "+1-555-123-4567",
    new DateTime(1990, 5, 15),
    CountryCode.UnitedStates,
    "12345",
    "https://johndoe.com"
);

if (result.IsValid)
{
    // customer is guaranteed to have valid data
    Console.WriteLine($"Created customer: {customer.Email}");
}
else
{
    Console.WriteLine("Validation failed:");
    Console.WriteLine(result.ToBulletList());
}

Date Ranges

// Working with date ranges
var startDate = DateTime.Now;
var endDate = DateTime.Now.AddDays(7);

var (result, dateRange) = DateRange.TryCreate(startDate, endDate);
if (result.IsValid)
{
    Console.WriteLine($"Range: {dateRange.Start} to {dateRange.End}");
    Console.WriteLine($"Duration: {dateRange.Duration.TotalDays} days");
    
    // Check if a date is within the range
    var isInRange = dateRange.Contains(DateTime.Now.AddDays(3));
}

// DateOnly ranges
var (result2, dateOnlyRange) = DateOnlyRange.TryCreate(
    DateOnly.FromDateTime(startDate),
    DateOnly.FromDateTime(endDate)
);

Custom Property Names

// Customize validation error property names for better error messages
var (result, email) = EmailAddress.TryCreate(input, "CustomerEmail");

// Error message will reference "CustomerEmail" instead of default "Email"
if (!result.IsValid)
{
    // Output: "CustomerEmail: Invalid email format"
    Console.WriteLine(result.ToSingleMessage());
}

// Also works with postal codes
var (result2, postalCode) = PostalCode.TryCreate(
    CountryCode.UnitedStates, 
    input, 
    "ShippingPostalCode"
);

API Integration Example

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    [HttpPost]
    public IActionResult CreateUser([FromBody] CreateUserRequest request)
    {
        var (emailResult, email) = EmailAddress.TryCreate(request.Email, nameof(request.Email));
        if (!emailResult.IsValid)
        {
            return BadRequest(emailResult.ToDictionary());
        }
        
        var (phoneResult, phone) = PhoneNumber.TryCreate(request.Phone, nameof(request.Phone));
        if (!phoneResult.IsValid)
        {
            return BadRequest(phoneResult.ToDictionary());
        }
        
        var (postalResult, postalCode) = PostalCode.TryCreate(
            request.CountryCode, 
            request.PostalCode, 
            nameof(request.PostalCode)
        );
        if (!postalResult.IsValid)
        {
            return BadRequest(postalResult.ToDictionary());
        }
        
        // Create user with validated primitives
        var user = new User
        {
            Email = email,
            Phone = phone,
            PostalCode = postalCode
        };
        
        // Save user...
        
        return Ok(user);
    }
}

Creating Custom Validated Primitives

You can easily create your own validated primitives by inheriting from ValidatedValueObject<T>:

using Validated.Primitives.Core;
using Validated.Primitives.Validation;
using Validated.Primitives.Validators;

public sealed record TaxId : ValidatedValueObject<string>
{
    private TaxId(string value, string propertyName = "TaxId") 
        : base(value)
    {
        Validators.Add(CommonValidators.NotNullOrWhitespace(propertyName));
        Validators.Add(CommonValidators.Length(propertyName, 9, 11));
        // Add your custom validation logic
    }

    public static (ValidationResult Result, TaxId? Value) TryCreate(
        string value, 
        string propertyName = "TaxId")
    {
        var taxId = new TaxId(value, propertyName);
        var validationResult = taxId.Validate();
        var result = validationResult.IsValid ? taxId : null;
        return (validationResult, result);
    }
}

Built-in Validators

The library includes several validator helpers:

CommonValidators

  • NotNullOrWhitespace - Ensures string is not null or whitespace
  • MaxLength - Maximum string length
  • MinLength - Minimum string length
  • Length - Exact or range of acceptable lengths

DateTimeValidators

  • BeforeToday - Date must be in the past
  • AfterToday - Date must be in the future
  • Between - Date within a range

EmailValidators

  • EmailFormat - Valid email format

PhoneValidators

  • PhoneNumber - Valid phone number format

UrlValidators

  • ValidUrl - Valid URL format
  • HttpOrHttps - Must be HTTP or HTTPS scheme

IpValidators

  • ValidIpAddress - Valid IPv4 or IPv6 address

PostalCodeValidators

  • ValidFormat - Basic postal code format (alphanumeric, spaces, hyphens)
  • ValidateCountryFormat - Country-specific postal code validation

Design Principles

  1. Immutability: All value objects are records, ensuring thread safety
  2. Fail Fast: Validation happens at creation time
  3. Explicit over Implicit: TryCreate pattern makes validation explicit
  4. Rich Feedback: Detailed validation errors with member names and codes
  5. Composability: Easy to combine multiple validators
  6. Zero Magic: No reflection, attributes, or hidden behavior

Requirements

  • .NET 8.0 or higher

Contributing

Contributions are welcome! Please feel free to submit pull requests or open issues.

License

MIT

Resources

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 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.
  • net8.0

    • No dependencies.

NuGet packages (3)

Showing the top 3 NuGet packages that depend on Validated.Primitives:

Package Downloads
Validated.Primitives.Domain

Domain models and complex types built from Validated.Primitives value objects. Provides strongly-typed, self-validating domain entities for common use cases like Address, ContactInformation, PersonName, and CreditCardDetails.

Mayordomo.Web.Extractor

Small, modular .NET 10 library to extract article content, images and metadata from web pages.

ReadableWeb

ReadableWeb extracts readable article content, images and metadata from HTML. Small, modular .NET 10 library for article extraction and readability parsing.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.67 383 12/15/2025
1.1.30 476 12/8/2025
1.1.28 786 12/8/2025 1.1.28 is deprecated because it has critical bugs.
1.1.22 131 11/29/2025
1.0.7 127 11/28/2025
1.0.6 128 11/28/2025
1.0.5 138 11/28/2025
0.1.20 118 11/29/2025
0.1.18 129 11/29/2025
0.1.0 156 11/28/2025

Validated primitives including EmailAddress, PhoneNumber, WebsiteUrl, IpAddress, DateOfBirth, FutureDate, BetweenDatesSelection, DateRange, DateOnlyRange, TimeOnlyRange, and PostalCode.