RA.Utilities.Core.ValueObjects
10.2.0
Prefix Reserved
dotnet add package RA.Utilities.Core.ValueObjects --version 10.2.0
NuGet\Install-Package RA.Utilities.Core.ValueObjects -Version 10.2.0
<PackageReference Include="RA.Utilities.Core.ValueObjects" Version="10.2.0" />
<PackageVersion Include="RA.Utilities.Core.ValueObjects" Version="10.2.0" />
<PackageReference Include="RA.Utilities.Core.ValueObjects" />
paket add RA.Utilities.Core.ValueObjects --version 10.2.0
#r "nuget: RA.Utilities.Core.ValueObjects, 10.2.0"
#:package RA.Utilities.Core.ValueObjects@10.2.0
#addin nuget:?package=RA.Utilities.Core.ValueObjects&version=10.2.0
#tool nuget:?package=RA.Utilities.Core.ValueObjects&version=10.2.0
RA.Utilities.Core.ValueObjects
RA.Utilities.Core.ValueObjects provides strongly-typed, validated value objects like Email, Currency, SSN, Money, and IBAN.
It solves the problem of passing raw, unvalidated strings and decimals around your domain, where formatting inconsistencies and invalid data can silently propagate.
By creating an instance of a value object, the input is validated and normalized once, at the boundary of your system. After that, an Email that exists is always a valid, normalized email address.
- Type Safety: You can't accidentally pass a currency code where an email is expected; the compiler enforces intent.
- Validation at the Boundary: Invalid input throws a
BadRequestExceptionwith structuredValidationErrordetails (error codes, property names, attempted values) thatRA.Utilities.Apimiddleware can translate into standardized HTTP 400 responses automatically. - Consistent Normalization: Values are trimmed and case-normalized on creation, so
new Email(" User@Gmail.com ")andnew Email("user@gmail.com")are equal. - JSON Support: Built-in
System.Text.Jsonconverters serialize value objects as plain strings and deserialize them back with validation. - Value Equality: All value objects are records, so they compare by value, not reference.
Table of Contents
- Getting started
- How It Works
- Available Value Objects
EmailCurrencySSNMoneyIBAN
- JSON Serialization
- Extensibility
- Best Practices
- Additional documentation
- Contributing
Getting started
You can install the package via the .NET CLI:
dotnet add package RA.Utilities.Core.ValueObjects
Or through the NuGet Package Manager in Visual Studio.
How It Works
This package is designed to fail fast at the edges of your system.
- Boundary Validation: When user input (e.g., from a request DTO) creates a value object, the input is validated and normalized immediately. Invalid input throws a
BadRequestException. - Guaranteed Validity: Inside your domain, an
Email,Currency, orSSNinstance that exists is always valid and normalized. No re-validation, no defensive checks. - Automatic Error Responses: In your API project (e.g., using
RA.Utilities.Api), a global error-handling middleware catches theBadRequestExceptionand translates it into a standardized HTTP 400 Bad Request response with structured validation details.
Available Value Objects
Email
A validated, normalized email address. Values are trimmed and lower-cased on creation, must not exceed 254 characters, and must match a standard email format.
Usage:
public void RegisterUser(string emailInput)
{
var email = new Email(emailInput); // throws BadRequestException if invalid
// LocalPart == "user", Domain == "gmail.com"
var existing = _userRepository.FindByEmail(email);
if (existing is not null)
{
throw new ConflictException("User", email);
}
// ... registration logic
}
Key members:
Value: the normalized address (e.g.,user@gmail.com).LocalPart/Domain: the parts before and after the@sign.Parse/TryParse:IParsable<Email>support for route, query, and header binding in minimal APIs.- Implicit conversion to
string.
Currency
A validated currency code. Values are trimmed and upper-cased on creation and must be exactly 3 characters, as defined by ISO 4217 (e.g., EUR, USD).
Usage:
public Money GetPrice(string currencyCode)
{
var currency = new Currency(currencyCode); // throws BadRequestException if not exactly 3 characters
return new Money(19.99m, currency);
}
Key members:
Value: the normalized code (e.g.,EUR).Parse/TryParse:IParsable<Currency>support for minimal API binding.- Implicit conversion to
string.
SSN
A validated social security number. Values are trimmed and upper-cased on creation, must not exceed 10 characters, and must match the format ^[A-Z][0-9]{8}[A-Z]$ (a letter, eight digits, a letter).
Usage:
public void UpdateEmployee(string ssnInput)
{
var ssn = new SSN(ssnInput); // throws BadRequestException if invalid
_employeeRepository.Update(ssn, update);
}
Key members:
Value: the normalized SSN (e.g.,J01234567R).Parse/TryParse:IParsable<SSN>support for minimal API binding.- Implicit conversion to
string.
Money
A monetary amount paired with a Currency. Arithmetic is guarded so you can't combine amounts in different currencies.
Usage:
var price = Money.PositiveMoney(19.99m, new Currency("EUR")); // throws BadRequestException if negative
var shipping = Money.PositiveMoney(4.99m, new Currency("EUR"));
var discount = Money.PositiveMoney(4.00m, new Currency("EUR"));
var total = price.Add(shipping); // 24.98 EUR
var net = price.Subtract(discount); // 15.99 EUR
// total.Add(new Money(5m, new Currency("USD"))) would throw BadRequestException (CurrencyMismatch)
Key members:
Amount(decimal) andCurrency.Money.PositiveMoney(amount, currency): factory that rejects negative amounts.Add(other): adds two amounts; throwsBadRequestExceptionif the currencies differ.Subtract(other): subtracts two amounts; throwsBadRequestExceptionif the currencies differ.
IBAN
A validated, normalized International Bank Account Number. Values are trimmed, stripped of grouping separators (spaces, hyphens, backslashes, apostrophes) and upper-cased on creation, checked against the ISO 13616 structural pattern (13–34 alphanumeric characters), and verified using the ISO 7064 mod-97 check digit algorithm.
Usage:
public void ProcessPayment(string rawIban)
{
var iban = new IBAN(rawIban); // throws BadRequestException if invalid
// Value == "GB82WEST12345698765432", CountryCode == "GB"
_paymentGateway.Transfer(iban, amount);
}
Key members:
Value: the normalized, compact IBAN (e.g.GB82WEST12345698765432).CountryCode: the 2-letter ISO 3166-1 alpha-2 country code (e.g.GB).Parse/TryParse:IParsable<IBAN>support for minimal API binding.- Implicit conversion to
string.
JSON Serialization
Every value object carries a built-in System.Text.Json converter (ValueObjectJsonConverter<T>). Value objects serialize as plain strings and deserialize back through validation:
string json = JsonSerializer.Serialize(new Email("User@Gmail.com"));
// json == "\"user@gmail.com\""
Email? email = JsonSerializer.Deserialize<Email>("\" User@Gmail.com \"");
// email.Value == "user@gmail.com" (normalized)
// Invalid input throws JsonException, as expected from a JsonConverter
JsonSerializer.Deserialize<Currency>("\"EURO\""); // throws JsonException
Extensibility
The shared behavior is defined by IValueObject<TSelf> (a normalized Value string plus IParsable<TSelf>). To create your own string-based value object with the same JSON converter support, implement the interface and reuse the generic converter:
[JsonConverter(typeof(ValueObjectJsonConverter<Sku>))]
public sealed partial record Sku : IValueObject<Sku>
{
public string Value { get; }
public Sku(string? value)
{
// validate and normalize...
Value = value.Trim().ToUpperInvariant();
}
public static Sku Parse(string s, IFormatProvider? provider) => new(s);
public static bool TryParse(string? s, IFormatProvider? provider, out Sku result) { /* ... */ }
}
Best Practices
Validate at the Boundary, Trust Inside the Domain: Create value objects as early as possible (e.g., when mapping request DTOs). After that, rely on the type system: an
Emailthat exists is valid.Prefer Value Objects over Primitive Strings in Signatures: Passing
Emailinstead ofstringmakes invalid states unrepresentable and eliminates duplicated validation logic across your application.Let the API Layer Handle Failures: Don't catch
BadRequestExceptionin your domain or application layers for flow control. Let it bubble up to theRA.Utilities.Apimiddleware, which produces a standardized, structured HTTP 400 response.Use
Moneyfor Amounts with Currency: Never store a monetary amount as a baredecimal.Moneyprevents cross-currency arithmetic mistakes at compile time and runtime.
Additional documentation
For more information on how this package fits into the larger RA.Utilities ecosystem, please see the main repository documentation.
Contributing
Contributions are welcome! If you have a suggestion for a new value object or find a bug, please open an issue to discuss it. Please follow the contribution guidelines outlined in the other projects in this repository.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. 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. |
-
net10.0
- RA.Utilities.Core.Exceptions (>= 10.1.0)
NuGet packages (3)
Showing the top 3 NuGet packages that depend on RA.Utilities.Core.ValueObjects:
| Package | Downloads |
|---|---|
|
RA.Utilities.OpenApi
This package provides utilities to generate and transform OpenAPI documents for ASP.NET Core web APIs. |
|
|
RA.Utilities.Data.EntityFramework
Provides generic repository base classes for implementing the Repository and Unit of Work patterns with Entity Framework Core. |
|
|
RA.Utilities.Application.Validation
RA.Utilities.Application.Validation provides reusable validators and helpers built on FluentValidation for validating incoming requests. It aggregates multiple validators asynchronously and converts FluentValidation failures into structured BadRequestException/ValidationError results. |
GitHub repositories
This package is not used by any popular GitHub repositories.