DcsvIo.D2.Contacts
0.1.1
dotnet add package DcsvIo.D2.Contacts --version 0.1.1
NuGet\Install-Package DcsvIo.D2.Contacts -Version 0.1.1
<PackageReference Include="DcsvIo.D2.Contacts" Version="0.1.1" />
<PackageVersion Include="DcsvIo.D2.Contacts" Version="0.1.1" />
<PackageReference Include="DcsvIo.D2.Contacts" />
paket add DcsvIo.D2.Contacts --version 0.1.1
#r "nuget: DcsvIo.D2.Contacts, 0.1.1"
#:package DcsvIo.D2.Contacts@0.1.1
#addin nuget:?package=DcsvIo.D2.Contacts&version=0.1.1
#tool nuget:?package=DcsvIo.D2.Contacts&version=0.1.1
DcsvIo.D2.Contacts
Audience: Backend .NET service engineers attaching contact details (names, demographics, professional info, email, phone) to their own domain entities.
Composable, self-redacting PII value objects for handlers and service code. Six immutable
sealed recordbuilding blocks —Personal,NameAffixes,Demographics,Professional,EmailAddress,PhoneNumber— each constructed through aCreate(...)smart constructor returningD2Result<T>. The value objects fold into a host's own entities; the reusable Entity Framework Core mapping ships separately inDcsvIo.D2.Contacts.EntityFrameworkCore.
Install
dotnet add package DcsvIo.D2.Contacts
Purpose
Each value object validates a dumb structural floor at the top of its Create(...) factory — length caps drawn from the shared FieldConstraints catalog plus shape / coherence rules — before constructing the record. Email and phone additionally accept an optional caller-injected smart validator (IEmailValidator / IPhoneValidator); when supplied the validator is the sole authority and its normalized output + failure messages are propagated verbatim, when omitted the structural floor applies. Every failure carries a TK.* translation key, never a bare string.
Pure-domain layer policy: depends only on DcsvIo.D2.Result (D2Result factories), DcsvIo.D2.Validation.Abstractions (the FieldConstraints caps, the NamePrefix / NameSuffix / BiologicalSex taxonomy enums, and the validator contracts), DcsvIo.D2.Utilities (boundary helpers + the [RedactData] attribute + the hash canonicalizer), and DcsvIo.D2.Geo.Abstractions (the CountryCode forwarded to the phone validator). No infrastructure deps, no Entity Framework, no observability surface.
Public API surface
All six records live in DcsvIo.D2.Contacts.ValueObjects; one record per file.
ValueObjects/Personal.cs— requiredFirstNameplus optionalMiddleName,LastName,PreferredName, each cleaned and length-capped. Carries a stable correlationHashId("v1." + SHA-256 hex) derived from the first / middle / last names — the preferred name is excluded so a display-name change leaves the identity digest stable. Case-, diacritic-, and whitespace-equivalent inputs hash identically.Personal.Create(firstName, middleName?, lastName?, preferredName?)→D2Result<Personal>.
ValueObjects/NameAffixes.cs— optional honorificPrefix(NamePrefix?) +Suffix(NameSuffix?) drawn from closed taxonomies, each with anOtherescape hatch backed by a custom free-text value. A custom value is required when (and only when) its enum isOther. The all-null record is rejected.NameAffixes.Create(prefix?, prefixCustom?, suffix?, suffixCustom?)→D2Result<NameAffixes>.
ValueObjects/Demographics.cs— optionalDateOfBirth(NodaTime.LocalDate?, not in the future, not more than 150 years in the past) +BiologicalSex(BiologicalSex?). The all-null record is rejected. The date-of-birth bounds resolve "today" from an injectableDcsvIo.D2.Time.IClock(defaults toSystemClock), viaclock.GetCurrentInstant().InUtc().Date, so boundary behavior is deterministic under test.Demographics.Create(dateOfBirth?, biologicalSex?, clock?)→D2Result<Demographics>.
ValueObjects/Professional.cs— requiredCompanyNameplus optionalJobTitle,Department, andCompanyWebsite. The website is accepted as raw text and stored as an absolutehttp/httpsUri?(raw input length-capped before parsing).Professional.Create(companyName, jobTitle?, department?, companyWebsite?)→D2Result<Professional>.
ValueObjects/EmailAddress.cs— thin wrapper over a normalizedValue. Floor mode trims / collapses / lowercases / shape-checks and enforces the address-length cap; validator mode bubbles the injectedIEmailValidatorresult.EmailAddress.Create(value, validator?)→D2Result<EmailAddress>.
ValueObjects/PhoneNumber.cs— thin wrapper over a normalizedValue. Floor mode strips non-digits, enforces the 7-15 digit envelope and the raw-length cap, and stores a digit string; validator mode forwards the optionalCountryCode regionto the injectedIPhoneValidatorand bubbles its (typically E.164) result. The region is ignored in floor mode.PhoneNumber.Create(value, validator?, region?)→D2Result<PhoneNumber>.
Floor vs. validator seam
EmailAddress and PhoneNumber model a dumb-floor / smart-validator split:
- Floor (no validator) — a structural check reusing the shared
TryParseEmail()/TryParsePhoneNumber()boundary helpers. Structural-invalid failures bubble the commonTK.Common.Validation.EMAIL_INVALID/PHONE_INVALIDkeys; the contacts-specific length ceilings addTK.Contacts.Validation.EMAIL_TOO_LONG/PHONE_TOO_LONGon top. - Validator (injected) — when a caller passes an
IEmailValidator/IPhoneValidator, that validator is the sole authority. Its normalized output is trusted without a second length check (double-bounding could falsely reject a legitimately longer normalized form), and its failure result — messages, input errors, and all metadata — is propagated verbatim viaD2Result<T>.BubbleFail.
Security / PII
The value objects self-redact: PII properties carry [RedactData(Reason = RedactReason.PersonalInformation)] and are masked automatically by the Serilog destructuring policy.
| Value object | Redacted (masked in logs) | Visible (left unredacted) |
|---|---|---|
Personal |
FirstName, MiddleName, LastName, PreferredName |
HashId (one-way SHA-256 digest, correlation-safe) |
NameAffixes |
PrefixCustom, SuffixCustom (free text) |
Prefix, Suffix (closed-list enums, coarse) |
Demographics |
DateOfBirth, BiologicalSex (special-category) |
— |
Professional |
CompanyName, JobTitle, Department |
CompanyWebsite (public URL, not identifying) |
EmailAddress |
Value |
— |
PhoneNumber |
Value |
— |
Personal.HashId is a one-way SHA-256 digest of the normalized name fields — opaque, non-reversible, and safe to correlate in logs and traces without leaking the underlying names.
Telemetry
N/A — pure-domain value-object lib, no telemetry surface by design. Consumer-side handlers carry the telemetry surface.
Configuration / Options
N/A — no env vars, no appsettings, no Options record. The length caps come from the shared FieldConstraints catalog; the smart-validator seam is a per-call argument.
Usage examples
using DcsvIo.D2.Contacts.ValueObjects;
using DcsvIo.D2.Geo.Abstractions;
using DcsvIo.D2.Validation.Abstractions;
// 1. Personal — required first name; carries a correlation HashId.
var personalResult = Personal.Create("John", middleName: "Quincy", lastName: "Adams");
if (!personalResult.Success)
return personalResult.AsFailure<MyAggregate>(); // propagate ValidationFailed
// 2. EmailAddress — floor mode (structural validation only).
var emailResult = EmailAddress.Create(" John.Adams@Example.com ");
// emailResult.Data.Value == "john.adams@example.com"
// 3. PhoneNumber — validator mode (smart validator is the authority).
var phoneResult = PhoneNumber.Create(
"212 555 1234",
validator: _phoneValidator, // injected IPhoneValidator
region: CountryCode.US); // forwarded to the validator only
Important / usage notes
The value objects are immutable. Composition into a host entity + the reusable EF Core per-VO mapping helpers (complex-type and value-converter wiring, max-length application, anonymization defaults) ship separately in DcsvIo.D2.Contacts.EntityFrameworkCore; this core library carries no Entity Framework dependency and no migrations.
Personal.HashId stability matters: it is a content digest, so a change to the normalization algorithm would silently shift the digest for previously-identical inputs. The PersonalTests suite pins determinism, case / diacritic / whitespace collapse, and the preferred-name-excluded invariant.
References
DcsvIo.D2.Validation.Abstractions— theFieldConstraintscaps, the taxonomy enums, and theIEmailValidator/IPhoneValidatorcontracts this lib consumes.DcsvIo.D2.Utilities— the boundary helpers (CleanStr,Falsey,Truthy,ToNullIfEmpty),NormalizeForHash, and theTryParseEmail/TryParsePhoneNumberfloor helpers.
| 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
- DcsvIo.D2.Geo.Abstractions (>= 0.1.1)
- DcsvIo.D2.Result (>= 0.1.1)
- DcsvIo.D2.Time (>= 0.1.1)
- DcsvIo.D2.Utilities (>= 0.1.1)
- DcsvIo.D2.Validation.Abstractions (>= 0.1.1)
- dotenv.net (>= 4.0.2)
- JetBrains.Annotations (>= 2025.2.4)
- Microsoft.EntityFrameworkCore (>= 10.0.7)
- Microsoft.EntityFrameworkCore.Relational (>= 10.0.7)
- Microsoft.Extensions.Caching.Abstractions (>= 10.0.7)
- Microsoft.Extensions.Caching.Memory (>= 10.0.7)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.7)
- Microsoft.Extensions.DependencyInjection (>= 10.0.7)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.7)
- Microsoft.Extensions.Logging (>= 10.0.7)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.7)
- Microsoft.Extensions.Options (>= 10.0.7)
- Microsoft.IdentityModel.Tokens (>= 8.16.0)
- NodaTime (>= 3.2.2)
- Npgsql (>= 10.0.2)
- Npgsql.EntityFrameworkCore.PostgreSQL (>= 10.0.1)
- Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime (>= 10.0.1)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on DcsvIo.D2.Contacts:
| Package | Downloads |
|---|---|
|
DcsvIo.D2.Contacts.EntityFrameworkCore
Reusable Entity Framework Core mapping for the DcsvIo.D2.Contacts value objects, applied via infra IEntityTypeConfiguration. |
GitHub repositories
This package is not used by any popular GitHub repositories.