Validated.Primitives
1.1.30
See the version list below for details.
dotnet add package Validated.Primitives --version 1.1.30
NuGet\Install-Package Validated.Primitives -Version 1.1.30
<PackageReference Include="Validated.Primitives" Version="1.1.30" />
<PackageVersion Include="Validated.Primitives" Version="1.1.30" />
<PackageReference Include="Validated.Primitives" />
paket add Validated.Primitives --version 1.1.30
#r "nuget: Validated.Primitives, 1.1.30"
#:package Validated.Primitives@1.1.30
#addin nuget:?package=Validated.Primitives&version=1.1.30
#tool nuget:?package=Validated.Primitives&version=1.1.30
Validated Primitives
A .NET library that provides strongly-typed, self-validating primitive value objects to eliminate primitive obsession and enforce domain constraints at compile-time.
Packages
This repository contains two NuGet packages:
📦 Validated.Primitives (Core)
The foundation library providing low-level validated primitive value objects.
dotnet add package Validated.Primitives
What it provides:
- Email addresses, phone numbers, URLs, IP addresses
- Postal codes with country-specific validation (30+ countries)
- Date/time value objects (DateOfBirth, FutureDate, date ranges)
- Financial primitives (Money, Percentage, CurrencyCode)
- Credit card primitives (CreditCardNumber, CreditCardSecurityNumber, CreditCardExpiration)
- Text primitives (HumanName, AddressLine, City, StateProvince)
- Validation framework and error handling
- JSON serialization support
📦 Validated.Primitives.Domain (Composition)
Higher-level domain models composed from validated primitives.
dotnet add package Validated.Primitives.Domain
What it provides:
- PersonName - Complete human name (first, last, middle) with computed properties (FullName, FormalName, Initials)
- Address - Physical mailing address with country-specific postal code validation
- ContactInformation - Email, primary/secondary phone, optional website
- CreditCardDetails - Complete payment card (number, CVV, expiration) with masking and expiration checking
Think of it as:
Validated.Primitives= LEGO bricks (EmailAddress, PhoneNumber, PostalCode)Validated.Primitives.Domain= Complete LEGO models (Address, PersonName, ContactInformation)
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- RFC 5322 compliant email format, max 256 charactersPhoneNumber- International phone number validation with country code supportWebsiteUrl- Valid HTTP/HTTPS URLs with proper scheme validation
📍 Location & Geography
PostalCode- Country-specific postal code validation for 30+ countriesCity- City names, max 100 characters, letters/spaces/hyphens/apostrophesStateProvince- State or province names, max 100 charactersAddressLine- Street address lines, max 200 characters
🌐 Network
IpAddress- Valid IPv4 or IPv6 addresses
📅 Date & Time
DateOfBirth- Must be in the past, cannot be future dateFutureDate- Must be in the future, cannot be past dateBetweenDatesSelection- Date within a specified rangeDateRange- Represents a range between two DateTimes with duration calculationDateOnlyRange- Represents a range between two DateOnly valuesTimeOnlyRange- Represents a range between two TimeOnly values
💳 Financial & Payment
Money- Monetary amounts with currency codes and precision validationSmallUnitMoney- Monetary amounts in smallest currency unit (e.g., cents)Percentage- Percentage values with configurable decimal placesCurrencyCode- ISO 4217 currency codes (USD, EUR, GBP, etc.)CreditCardNumber- Luhn-validated card numbers, 13-19 digits, rejects all-same-digit patternsCreditCardSecurityNumber- CVV/CVC security codes, 3-4 digitsCreditCardExpiration- Card expiration date with automatic 2-digit year normalization
👤 Personal Information
HumanName- Individual name parts (first, middle, last), 1-50 characters, letters/hyphens/apostrophes/spaces
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);
}
}
Using Validated.Primitives.Domain
For more complex scenarios, use the Validated.Primitives.Domain package which provides pre-built domain aggregates:
Complete User Profile Example
using Validated.Primitives.Domain;
using Validated.Primitives.ValueObjects;
// Create a complete address
var (addressResult, address) = Address.TryCreate(
street: "123 Main Street",
addressLine2: "Apt 4B",
city: "New York",
country: CountryCode.UnitedStates,
postalCode: "10001",
stateProvince: "NY"
);
// Create a person name
var (nameResult, personName) = PersonName.TryCreate(
firstName: "John",
lastName: "Doe",
middleName: "Michael"
);
// Create contact information
var (contactResult, contact) = ContactInformation.TryCreate(
countryCode: CountryCode.UnitedStates,
email: "john.doe@example.com",
primaryPhone: "+1-555-123-4567",
secondaryPhone: null,
website: "https://johndoe.com"
);
// Create credit card details
var (cardResult, card) = CreditCardDetails.TryCreate(
cardNumber: "4111 1111 1111 1111",
securityNumber: "123",
expirationMonth: 12,
expirationYear: 2025
);
if (addressResult.IsValid && nameResult.IsValid &&
contactResult.IsValid && cardResult.IsValid)
{
Console.WriteLine($"Name: {personName.FullName}");
Console.WriteLine($"Formal: {personName.FormalName}");
Console.WriteLine($"Initials: {personName.Initials}");
Console.WriteLine($"\nAddress: {address}");
Console.WriteLine($"\nContact: {contact.Email.Value}");
Console.WriteLine($"Phone: {contact.PrimaryPhone.Value}");
Console.WriteLine($"\nCard: {card.GetMaskedCardNumber()}");
Console.WriteLine($"Expires: {card.Expiration}");
Console.WriteLine($"Is Expired: {card.IsExpired()}");
}
Benefits of Domain Models
- Grouped Validation - Validate all related fields in one call
- Rich Behavior -
FullName,FormalName,Initials,GetMaskedCardNumber(),IsExpired() - Cohesive APIs - Pass
ContactInformationinstead of Email + Phone + Website separately - Guaranteed Consistency - If you have an
Address, all its parts are valid
Domain Models Available:
PersonName- FirstName + LastName + MiddleName with computed propertiesAddress- Complete mailing address with country-specific validationContactInformation- Email + Phones + WebsiteCreditCardDetails- CardNumber + CVV + Expiration with masking
??? Builder Pattern for Complex Types
For easier construction of complex domain models, use the fluent builder pattern:
AddressBuilder
Build addresses with a fluent interface that handles validation automatically:
using Validated.Primitives.Domain.Builders;
using Validated.Primitives.ValueObjects;
// Simple address with required fields
var (result, address) = new AddressBuilder()
.WithStreet("123 Main Street")
.WithCity("New York")
.WithCountry(CountryCode.UnitedStates)
.WithPostalCode("10001")
.Build();
if (result.IsValid)
{
Console.WriteLine(address!.ToString());
// Output: 123 Main Street, New York, 10001, United States
}
// Complete address with all optional fields
var (result, address) = new AddressBuilder()
.WithStreet("456 Oak Avenue")
.WithAddressLine2("Apartment 4B")
.WithCity("Los Angeles")
.WithStateProvince("California")
.WithCountry(CountryCode.UnitedStates)
.WithPostalCode("90001")
.Build();
// Using the convenience method
var (result, address) = new AddressBuilder()
.WithAddress(
street: "789 Elm Street",
city: "Chicago",
country: CountryCode.UnitedStates,
postalCode: "60601",
addressLine2: "Suite 200",
stateProvince: "Illinois")
.Build();
CreditCardBuilder
Build credit card details with flexible input formats:
using Validated.Primitives.Domain.Builders;
// Basic credit card details
var (result, card) = new CreditCardBuilder()
.WithCardNumber("4111 1111 1111 1111")
.WithSecurityCode("123")
.WithExpiration(12, 2025)
.Build();
// Using string expiration format
var (result, card) = new CreditCardBuilder()
.WithCardNumber("5500 0000 0000 0004")
.WithSecurityCode(456) // Can use int or string
.WithExpiration("12/25") // Supports MM/YY or MM/YYYY
.Build();
// Using DateTime
var (result, card) = new CreditCardBuilder()
.WithCardNumber("3400 0000 0000 009")
.WithSecurityCode("789")
.WithExpiration(DateTime.Now.AddYears(2))
.Build();
if (result.IsValid)
{
Console.WriteLine($"Card: {card!.GetMaskedCardNumber()}");
Console.WriteLine($"Expires: {card.Expiration}");
Console.WriteLine($"Valid: {!card.IsExpired()}");
}
else
{
// All validation errors collected at once
foreach (var error in result.Errors)
{
Console.WriteLine($"{error.MemberName}: {error.Message}");
}
}
Builder Features
? Key Benefits:
- Fluent Interface - Method chaining for readable code
- Flexible Input - Multiple ways to provide the same data
- Comprehensive Validation - All errors collected in one call
- Nullable Parameters - Accepts incomplete data and validates properly
- Reusable - Reset and reuse builder instances
- Type Safe - Compile-time checks for required parameters
?? Reusing Builders:
var builder = new AddressBuilder();
// Build first address
var (result1, address1) = builder
.WithStreet("111 First Street")
.WithCity("Denver")
.WithCountry(CountryCode.UnitedStates)
.WithPostalCode("80201")
.Build();
// Reset and build second address
var (result2, address2) = builder
.Reset()
.WithStreet("222 Second Avenue")
.WithCity("Phoenix")
.WithCountry(CountryCode.UnitedStates)
.WithPostalCode("85001")
.Build();
?? Form Integration Example:
[ApiController]
[Route("api/[controller]")]
public class AddressesController : ControllerBase
{
[HttpPost]
public IActionResult Create([FromBody] CreateAddressRequest request)
{
// Builder handles null/incomplete data gracefully
var (result, address) = new AddressBuilder()
.WithStreet(request.Street)
.WithAddressLine2(request.AddressLine2)
.WithCity(request.City)
.WithStateProvince(request.StateProvince)
.WithCountry(request.Country)
.WithPostalCode(request.PostalCode)
.Build();
if (!result.IsValid)
{
return BadRequest(new
{
errors = result.Errors.Select(e => new
{
field = e.MemberName,
message = e.Message,
code = e.Code
})
});
}
// Save valid address...
return Ok(new { id = 1, address = address!.ToString() });
}
}
Available Builders:
AddressBuilder- Required: street, city, country, postalCode | Optional: addressLine2, stateProvinceCreditCardBuilder- Required: cardNumber, securityCode, expiration | Multiple expiration formats supported
| Product | Versions 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. |
-
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.
## Credit Card Value Objects Refactored to TryCreate Pattern
### Breaking Changes
- **CreditCardNumber**: Replaced `Create()` method with `TryCreate()` pattern. Now inherits from `ValidatedValueObject<string>` and returns `(ValidationResult, CreditCardNumber?)` tuple instead of throwing exceptions.
- **CreditCardSecurityNumber**: Replaced `Create()` method with `TryCreate()` pattern. Now inherits from `ValidatedValueObject<string>` and returns `(ValidationResult, CreditCardSecurityNumber?)` tuple.
- **CreditCardExpiration**: Replaced `Create()` method with `TryCreate()` pattern. Now inherits from `ValidatedValueObject<(int Month, int Year)>` and returns `(ValidationResult, CreditCardExpiration?)` tuple.
### New Features
- **Structured Validation**: All credit card value objects now return detailed validation results with error codes (e.g., "Required", "InvalidMonth", "Expired", "Luhn check failed") instead of throwing exceptions.
- **Validators Added**: New validator classes for each credit card component:
- `CreditCardValidators`: NotEmpty, ValidDigitCount, NotAllIdenticalDigits, LuhnCheck
- `CreditCardSecurityNumberValidators`: NotEmpty, ValidLength
- `CreditCardExpirationValidators`: ValidMonth, ValidYear, NotExpired
- **Error Code Support**: All validation failures now include error codes for better error handling and categorization.
- **Nullable Parameters**: CreditCardDetails.TryCreate now accepts nullable parameters for better API flexibility.
- **Comprehensive Error Collection**: Multiple validation errors are collected and returned together, providing complete feedback in a single call.
### Improvements
- **Better Error Handling**: Validation errors are returned as structured ValidationResult objects instead of exceptions, enabling better error handling in consuming code.
- **Type Safety**: All value objects are now immutable records with proper equality and hash code implementations.
- **Year Normalization**: CreditCardExpiration automatically normalizes two-digit years (e.g., 25 → 2025).
- **Resilient Validators**: Validators safely handle invalid input without throwing exceptions during validation.
### Other Value Objects
Validated primitives including EmailAddress, PhoneNumber, WebsiteUrl, IpAddress, DateOfBirth, FutureDate, BetweenDatesSelection, DateRange, DateOnlyRange, TimeOnlyRange, Money, Percentage, and PostalCode.