EricksonLopez.DomainPrimitives.AspNetCore.SourceGenerators
1.0.0
See the version list below for details.
dotnet add package EricksonLopez.DomainPrimitives.AspNetCore.SourceGenerators --version 1.0.0
NuGet\Install-Package EricksonLopez.DomainPrimitives.AspNetCore.SourceGenerators -Version 1.0.0
<PackageReference Include="EricksonLopez.DomainPrimitives.AspNetCore.SourceGenerators" Version="1.0.0" />
<PackageVersion Include="EricksonLopez.DomainPrimitives.AspNetCore.SourceGenerators" Version="1.0.0" />
<PackageReference Include="EricksonLopez.DomainPrimitives.AspNetCore.SourceGenerators" />
paket add EricksonLopez.DomainPrimitives.AspNetCore.SourceGenerators --version 1.0.0
#r "nuget: EricksonLopez.DomainPrimitives.AspNetCore.SourceGenerators, 1.0.0"
#:package EricksonLopez.DomainPrimitives.AspNetCore.SourceGenerators@1.0.0
#addin nuget:?package=EricksonLopez.DomainPrimitives.AspNetCore.SourceGenerators&version=1.0.0
#tool nuget:?package=EricksonLopez.DomainPrimitives.AspNetCore.SourceGenerators&version=1.0.0
EricksonLopez.DomainPrimitives π‘οΈ
DomainPrimitives is a BCL-native, AOT-first domain primitive library for .NET 8+. It uses Roslyn Incremental Source Generators to produce strictly valid, immutable domain types with the deepest BCL interface coverage in the .NET ecosystem β including IUtf8SpanParsable<T>, ISpanFormattable, and IUtf8SpanFormattable.
Stop writing validation boilerplate. DomainPrimitives generates strongly-typed, BCL-native domain types from a single attribute β with built-in security gates, zero-alloc paths, and full Native AOT support out of the box.
π Why DomainPrimitives?
| Capability | DomainPrimitives | Vogen | Thinktecture | StronglyTypedId |
|---|---|---|---|---|
IUtf8SpanParsable<T> generated (NET8+) |
β | β | β | β |
ISpanFormattable generated |
β | β | β | β |
IUtf8SpanFormattable generated |
β | β | β | β |
| Declarative normalization ([Trim], [LowerCase]...) | β | β | β | β |
| NFC Unicode normalization (SEC-004) | β | β | β | β |
| ReDoS-resistant regex (NonBacktracking + 100ms) | β | β | β | β |
| 30 semantic domain type shortcuts | β | β | β | β |
| Auto-discovered EF Core & Dapper (no annotations) | β | β | β | β |
| Multi-property Value Object | β | β | β | β |
| Smart Enum (source-generated, AOT-safe) | β | β | β | β |
TryCreate(out result, out error) (zero-alloc success) |
β | β | β | β |
| Native AOT compatible | β | β | β | β |
Table notes:
βmeans the library does not expose this capability as part of its generated surface (not tested, not documented, or explicitly out of scope for that library).
Not in DomainPrimitives yet: Discriminated Unions (Thinktecture only), Newtonsoft.Json converters (Vogen/StronglyTypedId), class-based primitives. See docs/feature-gaps.md for the full gap list.
π¦ Supported Primitives
| Type | Description | Example |
|---|---|---|
[StringPrimitive] |
String-backed primitive with validation pipeline. | FirstName, Description |
[NumericPrimitive<T>] |
Numeric-backed (int, decimal, double, etc.). | Age, Money, Score |
[DatePrimitive] |
Date-backed (DateOnly, DateTime). | BirthDate, ExpirationDate |
[StrongId] |
Strongly-typed IDs (Guid, int, long, string). | UserId, OrderId |
[ValueObject] |
Multi-property immutable value objects. | Address, Money |
[SmartEnum] |
Strongly-typed enums with behavior and AOT-safe static list. | OrderStatus, Role |
π― Semantic Shortcut Attributes
Instead of repeating validations, use built-in shortcut attributes that combine validation and normalization rules:
String shortcuts (15 types):
- Identity:
[Email],[Username],[PasswordHash] - Network:
[Url],[IPAddress],[MacAddress] - Commerce:
[Phone],[CountryCode],[CurrencyCode],[LanguageCode],[IBAN] - Content:
[Slug],[HexColor],[ISBN],[VIN]
Numeric shortcuts (15 types):
[Money], [Percentage], [Latitude], [Longitude], [Age], [Weight], [Height], [Distance], [Temperature], [Score], [Quantity], [Price], [TaxRate], [Discount], [Rating]
β‘ Quick Start
dotnet add package EricksonLopez.DomainPrimitives
using EricksonLopez.DomainPrimitives;
// 1. Define your primitive
[CountryCode] // Implies: [StringPrimitive], [Trim], [UpperCase], [Length(2, 2)]
public readonly partial record struct CountryIsoCode;
// 2. Create β throws DomainPrimitiveValidationException on invalid input
var code = CountryIsoCode.Create(" us "); // Output: CountryIsoCode { Value = "US" } (trimmed + uppercased)
// 3. TryCreate β out-based, zero allocation on success
if (CountryIsoCode.TryCreate("us", out var validCode, out var error))
Console.WriteLine($"Valid: {validCode}"); // Output: Valid: US
else
Console.WriteLine($"Error [{error.Code}]: {error.Message}");
// 4. Parse from Span<char> β allocation-minimized path
if (CountryIsoCode.TryParse("us".AsSpan(), null, out var parsedCode))
Console.WriteLine($"Parsed: {parsedCode}"); // Output: Parsed: US
// 5. Parse from UTF-8 bytes β native for HTTP/gRPC/Kafka scenarios (NET8+)
ReadOnlySpan<byte> utf8 = "us"u8;
if (CountryIsoCode.TryParse(utf8, null, out var utf8Code))
Console.WriteLine($"UTF-8 parsed: {utf8Code}"); // Output: UTF-8 parsed: US
π Security Gates
DomainPrimitives is the only domain primitive library with built-in security gates applied automatically to all string types:
| Gate | Rule | Protection |
|---|---|---|
| SEC-001 | Default 4096-character limit on all string types without explicit MaxLength |
Prevents memory exhaustion attacks |
| SEC-002 | RegexOptions.NonBacktracking on .NET 7+ |
Eliminates ReDoS vulnerabilities |
| SEC-003 | 100ms regex timeout on older TFMs | Caps worst-case regex time |
| SEC-004 | NFC Unicode normalization on all string inputs before validation | Prevents Unicode homoglyph attacks |
| SEC-005 | No PII echoed in error messages on sensitive types | Prevents information leakage |
| SEC-006 | Stackalloc β€ 256 chars on char-span path; β€ 256 bytes on UTF-8 byte path; ArrayPool<char> for larger inputs |
Prevents stack overflow on large inputs |
β‘ Performance & Benchmarks
BenchmarkDotNet v0.15.8 Β· .NET 10.0.10 (10.0.1026.32716) Β· AMD Ryzen 7 9800X3D
DomainPrimitives is built with extreme performance and zero-allocation in mind. See the full benchmark methodology and results.
Hot Path Benchmarks
| Benchmark | Mean | Allocated | Zero-alloc? |
|---|---|---|---|
RawGuid (baseline β no wrapper) |
0.00 ns | 0 B | β |
PrimitiveGuid.Create(Guid) |
0.00 ns | 0 B | β Same as raw |
PrimitiveGuid.TryParse(string) |
12.63 ns | 0 B | β Zero allocation |
EmailAddress.Create(string) |
49.53 ns | 0 B | β Zero allocation |
EmailAddress JSON serialize |
102.34 ns | 64 B | β οΈ JSON infra |
EmailAddress JSON deserialize |
95.58 ns | 120 B | β οΈ JSON infra |
Note: JSON allocation is from the
Utf8JsonReader/Utf8JsonWriterinfrastructure, not from the domain primitive itself. TheTryParsehot path (called internally during deserialization) is zero-allocation.
vs. Industry Competitors (StrongId<Guid>)
| Method | Create | Parse | Allocated |
|---|---|---|---|
| Raw Guid (baseline) | 0.00 ns | 15.32 ns | 0 B |
| DomainPrimitives | 0.17 ns | 15.81 ns | 0 B |
| Vogen | 0.01 ns | 15.22 ns | 0 B |
| StronglyTypedId | 0.00 ns | 15.29 ns | 0 B |
| ValueOf | 2.51 ns | 16.99 ns | 32 B |
Results show DomainPrimitives maintains zero-allocation in hot paths and performs virtually identically to raw Guid and other struct-based generators, while avoiding the heap allocation overhead seen in class-based wrappers (e.g., ValueOf).
Allocation Model Audit
DomainPrimitives minimizes heap allocations in hot paths. Here is the honest per-path allocation audit:
| Path | Allocations | Notes |
|---|---|---|
TryCreate(string) β success, no normalization |
0 | Zero new heap objects |
TryCreate(string) β success, with normalization |
1 | NFC .Normalize(FormC) β required by SEC-004 |
TryCreate(string) β failure |
1 | Error message string |
TryParse(ReadOnlySpan<char>) β€ 256 chars |
1 | stackalloc + 1 string for NFC + storage |
TryParse(ReadOnlySpan<char>) > 256 chars |
1 + pool | ArrayPool<char> + 1 string |
TryParse(ReadOnlySpan<byte>) β€ 256 chars (NET8+) |
1 | stackalloc decode + 1 string |
JSON deserialize via Utf8JsonReader.ValueSpan |
1 | Direct span read + 1 string for NFC + storage |
TryFormat(Span<char>) β formatting |
0 | Writes into caller-provided span |
| EF Core materialization (struct types) | 0 | ValueConverter β no boxing for structs |
Why 1 unavoidable allocation? Unicode NFC normalization (SEC-004) requires producing a
System.Stringβ normalization can change character count (combining characters β composed), so the result cannot be stored as a span. The stored domain value is always NFC-normalized, which is correct and prevents homoglyph attacks.
No Result<T> overhead.
TryCreate(out result, out error)is zero-allocation on the success path because bothresult(a struct) anderror(a struct) live on the caller's stack. UnlikeResult<T>wrapper patterns, no heap object is created.
π§© Ecosystem Integrations
DomainPrimitives provides seamless integration via dedicated packages. Converters are auto-discovered β no per-type attributes needed in your domain layer:
| Package | Integration | Auto-discovery |
|---|---|---|
EricksonLopez.DomainPrimitives.AspNetCore |
Model binding, route params, OpenAPI | β |
EricksonLopez.DomainPrimitives.EFCore |
ValueConverter for all domain types |
β |
EricksonLopez.DomainPrimitives.Dapper |
SqlMapper.TypeHandler for all domain types |
β |
EricksonLopez.DomainPrimitives.Mapster |
Mapster type mapping for composite ValueObjects (source-generated) | β |
EricksonLopez.DomainPrimitives.OpenApi |
Swagger/OpenAPI schema filters | β |
EricksonLopez.DomainPrimitives.Testing |
Assertions, builders, fakes for xUnit | β |
Known gap: Newtonsoft.Json converters are not yet supported. If your project uses Newtonsoft.Json, consider Vogen or StronglyTypedId for this scenario. Track progress in docs/feature-gaps.md.
Mapster note: For scalar primitives (
[StringPrimitive],[StrongId],[NumericPrimitive<T>]), Mapster resolves the generatedexplicit operatorautomatically β no package needed. AddEricksonLopez.DomainPrimitives.Mapsteronly when mapping composite[ValueObject]types or when using Mapster in AOT source-generation mode. See ADR-017.
ποΈ Architecture
Target Framework Requirements
| Package | Minimum TFM | Notes |
|---|---|---|
EricksonLopez.DomainPrimitives |
net8.0 | Full feature set: generators, analyzers, integrations. |
EricksonLopez.DomainPrimitives.Abstractions |
netstandard2.0 | Attributes, interfaces, and PrimitiveError only. No generators. |
EricksonLopez.DomainPrimitives.Generators |
netstandard2.0 | Source Generator β compile-time only, no runtime reference needed. |
Minimum runtime: net8.0.
IUtf8SpanParsable<T>,RegexOptions.NonBacktracking,System.Buffers.ArrayPool<T>, andMemoryExtensions.ToLowerInvariantall require .NET 8+. UseEricksonLopez.DomainPrimitives.Abstractionsfor shared contracts in netstandard2.0 projects.
Primary development target: net10.0 LTS. All benchmarks and new feature development target NET 10. See ADR-016 for the full rationale.
Native AOT
All generated code is AOT-compatible:
- Zero reflection in hot paths (
Type.GetMethod(),Activator,Expression<>are never used) SmartEnum.GetAll()is a static readonly array β no runtime reflectionIsAotCompatible=truein project metadata- CI gate:
dotnet publishwith Native AOT verifies compatibility on every commit
Generated BCL Interfaces
For every domain primitive, the generator emits:
| Interface | String | Numeric | Date | StrongId | ValueObject |
|---|---|---|---|---|---|
IParsable<T> |
β | β | β | β | π v2.0 |
ISpanParsable<T> |
β | β | β | β | π v2.0 |
IUtf8SpanParsable<T> (NET8+) |
β | β | β | β | π v2.0 |
IFormattable |
β | β | β | β | β |
ISpanFormattable |
β | β | β | β | β |
IUtf8SpanFormattable (NET8+) |
β | β | β | β | π v2.0 |
IComparable<T> |
β | β | β | β | N/A* |
IEqualityOperators<T,T,bool> |
β | β | β | β | β |
*
IComparable<T>on ValueObject is intentionally not generated β composite types have no canonical ordering unless the domain explicitly defines one.
π Documentation
- π‘οΈ Security Gates β SEC-001 through SEC-006 explained
- πΊοΈ Feature Gaps β What's missing and what we explicitly reject
- π³ Cookbook β Common problems solved with DomainPrimitives
- π API Reference β Interfaces, exceptions, and factory methods
- π¦ Packages β All 15 NuGet packages, TFM matrix, and dependency graph
- π Migration from Vogen β Step-by-step migration guide
- π Migration from StronglyTypedId β Step-by-step migration guide
- π Benchmark Results β Performance data and allocation audit
- π Benchmark Plan β 16 BenchmarkDotNet scenarios
- ποΈ System Overview β Architecture and project dependency diagram
- βοΈ CI/CD Pipelines β Build, test, quality gates, and supply chain security
- πΊοΈ Roadmap β NOW / NEXT / LATER horizon planning
- π Changelog β All notable changes per version
π Sample Projects
The samples/OfficialSample/ folder contains step-by-step integration examples. Numbers are non-consecutive by design β they match the chapter index of the full documentation and leave room for future samples to be inserted without renumbering.
| # | Sample | Description |
|---|---|---|
| 1 | 1-GettingStarted |
Core concepts and Quick Start |
| 4 | 4-ValueObjects |
Structural validation and built-in primitive catalog |
| 5 | 5-StronglyTypedIds |
Eliminating Primitive Obsession |
| 15 | 15-AspNetCoreIntegration |
ASP.NET Core, HTTP validation and JSON |
| 16 | 16-EFCoreIntegration |
Domain persistence with EF Core |
| 17 | 17-MediatRIntegration |
Advanced pipeline behavior integration |
| 20 | 20-EndToEndApplication |
Enterprise architecture (scalability, observability, error handling) |
| 23 | 23-DapperIntegration |
Dapper TypeHandlers with auto-discovery (DapperDomainPrimitivesRegistration.RegisterAll()) |
| 24 | 24-OpenApiIntegration |
Swagger/OpenAPI schema filters β domain primitives shown as correct JSON types |
π€ Contributing & Community
- Contributing Guide β Build, test, and PR process
- Code of Conduct β Contributor Covenant v2.1
- Security Policy β Vulnerability reporting and supply chain security
- Support β Getting help and support channels
- Governance β RFC process and design principles
- License β MIT
Learn more about Target Frameworks and .NET Standard.
This package has no dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.