FunctionalDdd.Asp
3.0.0-alpha.72
dotnet add package FunctionalDdd.Asp --version 3.0.0-alpha.72
NuGet\Install-Package FunctionalDdd.Asp -Version 3.0.0-alpha.72
<PackageReference Include="FunctionalDdd.Asp" Version="3.0.0-alpha.72" />
<PackageVersion Include="FunctionalDdd.Asp" Version="3.0.0-alpha.72" />
<PackageReference Include="FunctionalDdd.Asp" />
paket add FunctionalDdd.Asp --version 3.0.0-alpha.72
#r "nuget: FunctionalDdd.Asp, 3.0.0-alpha.72"
#:package FunctionalDdd.Asp@3.0.0-alpha.72
#addin nuget:?package=FunctionalDdd.Asp&version=3.0.0-alpha.72&prerelease
#tool nuget:?package=FunctionalDdd.Asp&version=3.0.0-alpha.72&prerelease
FunctionalDdd.Asp — ASP.NET Core Extensions
Comprehensive ASP.NET Core integration for functional domain-driven design, providing:
- Automatic Scalar Value Validation — Property-aware error messages with comprehensive error collection
- Result-to-HTTP Conversion — Seamless
Result<T>to HTTP response mapping - Model Binding — Automatic binding from route/query/form/headers
- Optional Value Objects —
Maybe<T>support for JSON, model binding, and MVC validation - Native AOT Support — Optional source generator for zero-reflection overhead
Installation
dotnet add package FunctionalDdd.Asp
Scalar Value Validation
Automatically validate types implementing IScalarValue<TSelf, TPrimitive> during JSON deserialization and model binding with property-aware error messages.
Note: This includes DDD value objects (like
ScalarValueObject<T>) as well as any custom implementations ofIScalarValue.
Quick Start
1. Define Value Objects
public class EmailAddress : ScalarValueObject<EmailAddress, string>,
IScalarValue<EmailAddress, string>
{
private EmailAddress(string value) : base(value) { }
public static Result<EmailAddress> TryCreate(string? value, string? fieldName = null)
{
var field = fieldName ?? "email";
if (string.IsNullOrWhiteSpace(value))
return Error.Validation("Email is required.", field);
if (!value.Contains('@'))
return Error.Validation("Email must contain @.", field);
return new EmailAddress(value);
}
}
2. Use in DTOs
public record RegisterUserDto
{
public EmailAddress Email { get; init; } = null!;
public FirstName FirstName { get; init; } = null!;
public string Password { get; init; } = null!;
}
3. Setup Validation
var builder = WebApplication.CreateBuilder(args);
// For MVC Controllers
builder.Services
.AddControllers()
.AddScalarValueValidation();
// For Minimal APIs
builder.Services.AddScalarValueValidationForMinimalApi();
var app = builder.Build();
app.UseScalarValueValidation(); // Required middleware
app.Run();
4. Automatic Validation
[HttpPost]
public IActionResult Register(RegisterUserDto dto)
{
// If we reach here, dto is fully validated!
return Ok(User.Create(dto.Email, dto.FirstName, dto.Password));
}
Request:
{
"email": "invalid",
"firstName": "",
"password": "test"
}
Response (400 Bad Request):
{
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Email": ["Email must contain @."],
"FirstName": ["Name cannot be empty."]
}
}
MVC Controllers
builder.Services
.AddControllers()
.AddScalarValueValidation(); // Adds JSON validation + model binding
var app = builder.Build();
app.UseScalarValueValidation(); // Middleware
app.MapControllers();
app.Run();
- ✅ JSON deserialization with validation
- ✅ Model binding from route/query/form/headers
- ✅ Automatic 400 responses via
ScalarValueValidationFilter - ✅ Integrates with
[ApiController]attribute
Minimal APIs
builder.Services.AddScalarValueValidationForMinimalApi();
var app = builder.Build();
app.UseScalarValueValidation();
app.MapPost("/users", (RegisterUserDto dto) => ...)
.WithScalarValueValidation(); // Add filter to each endpoint
app.Run();
- ✅ JSON deserialization with validation
- ✅ Endpoint filter for automatic 400 responses
Model Binding
Value objects automatically bind from various sources in MVC:
// Route parameters
[HttpGet("{userId}")]
public IActionResult GetUser(UserId userId) => Ok(user);
// Query parameters
[HttpGet]
public IActionResult Search([FromQuery] EmailAddress email) => Ok(results);
// Headers
[HttpGet]
public IActionResult GetProfile([FromHeader(Name = "X-User-Id")] UserId userId) => Ok();
Optional Value Objects with Maybe<T>
Use Maybe<T> for optional value object properties in DTOs. No additional setup is needed — AddScalarValueValidation() automatically registers the JSON converter, model binder, and validation suppression for Maybe<T> properties.
| JSON Value | Result |
|---|---|
null or absent |
Maybe.None (no error) |
| Valid value | Maybe.From(validated) |
| Invalid value | Validation error collected |
public record RegisterUserDto
{
public FirstName FirstName { get; init; } = null!; // Required
public EmailAddress Email { get; init; } = null!; // Required
public Maybe<Url> Website { get; init; } // Optional
}
Native AOT Support
For Native AOT applications, add the source generator package:
dotnet add package FunctionalDdd.AspSourceGenerator
[GenerateScalarValueConverters] // ← Add this
[JsonSerializable(typeof(RegisterUserDto))]
public partial class AppJsonSerializerContext : JsonSerializerContext { }
The generator automatically detects all IScalarValue types, generates AOT-compatible converters, and adds [JsonSerializable] attributes.
Note: The source generator is optional. Without it, the library uses reflection (works for standard .NET).
Result Conversion
Convert Railway Oriented Programming Result<T> types to HTTP responses.
MVC Controllers
[HttpPost]
public ActionResult<User> Register([FromBody] RegisterRequest request) =>
FirstName.TryCreate(request.FirstName)
.Combine(LastName.TryCreate(request.LastName))
.Combine(EmailAddress.TryCreate(request.Email))
.Bind((firstName, lastName, email) =>
User.TryCreate(firstName, lastName, email, request.Password))
.ToActionResult(this);
Minimal APIs
userApi.MapPost("/register", (RegisterUserRequest request) =>
FirstName.TryCreate(request.FirstName)
.Combine(LastName.TryCreate(request.LastName))
.Combine(EmailAddress.TryCreate(request.Email))
.Bind((firstName, lastName, email) =>
User.TryCreate(firstName, lastName, email, request.Password))
.ToHttpResult());
HTTP Status Mapping
| Result Type | HTTP Status | Description |
|---|---|---|
| Success | 200 OK | Success with content |
| Success (Unit) | 204 No Content | Success without content |
| ValidationError | 400 Bad Request | Validation errors with details |
| UnauthorizedError | 401 Unauthorized | Authentication required |
| ForbiddenError | 403 Forbidden | Access denied |
| NotFoundError | 404 Not Found | Resource not found |
| ConflictError | 409 Conflict | Resource conflict |
| DomainError | 422 Unprocessable Entity | Domain rule violation |
| RateLimitError | 429 Too Many Requests | Rate limit exceeded |
| UnexpectedError | 500 Internal Server Error | Unexpected error |
| ServiceUnavailableError | 503 Service Unavailable | Service unavailable |
Property-Aware Error Messages
When the same value object type is used for multiple properties, errors correctly show property names (not type names):
{
"errors": {
"FirstName": ["Name cannot be empty."],
"LastName": ["Name cannot be empty."]
}
}
This requires the fieldName parameter in TryCreate:
public static Result<Name> TryCreate(string? value, string? fieldName = null)
{
var field = fieldName ?? "name";
if (string.IsNullOrWhiteSpace(value))
return Error.Validation("Name cannot be empty.", field);
return new Name(value);
}
Reflection vs Source Generator
| Feature | Reflection | Source Generator |
|---|---|---|
| Setup | Simple (no generator) | Requires analyzer reference |
| Performance | ~50μs overhead at startup | Zero overhead |
| AOT Support | ❌ No | ✅ Yes |
| Trimming | ⚠️ May break | ✅ Safe |
| Use Case | Prototyping, standard .NET | Production, Native AOT |
Best Practices
- Always use
fieldNameparameter — Enables property-aware errors - Call validation setup in
Program.cs— Required for automatic validation - Add
UseScalarValueValidation()middleware — Creates validation scope - Use
[ApiController]in MVC — Enables automatic validation responses - Use async variants for async operations —
ToActionResultAsync,ToHttpResultAsync - Combine approaches — Automatic validation for DTOs, manual
Resultchaining for business rules
Related Packages
- FunctionalDdd.RailwayOrientedProgramming — Core
Result<T>type - FunctionalDdd.PrimitiveValueObjects — Base value object types
- FunctionalDdd.DomainDrivenDesign — Entity and aggregate patterns
- FunctionalDdd.AspSourceGenerator — AOT source generator
License
MIT — see LICENSE for details.
| 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
- FunctionalDdd.RailwayOrientedProgramming (>= 3.0.0-alpha.72)
- Microsoft.Extensions.DependencyModel (>= 10.0.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 3.0.0-alpha.72 | 52 | 2/8/2026 |
| 3.0.0-alpha.60 | 52 | 2/4/2026 |
| 3.0.0-alpha.59 | 51 | 2/1/2026 |
| 3.0.0-alpha.56 | 52 | 1/31/2026 |
| 3.0.0-alpha.55 | 51 | 1/31/2026 |
| 3.0.0-alpha.44 | 60 | 1/13/2026 |
| 3.0.0-alpha.20 | 60 | 1/6/2026 |
| 3.0.0-alpha.19 | 64 | 1/5/2026 |
| 3.0.0-alpha.13 | 62 | 1/5/2026 |
| 3.0.0-alpha.9 | 56 | 1/5/2026 |
| 3.0.0-alpha.3 | 164 | 12/20/2025 |
| 2.1.10 | 716 | 12/3/2025 |
| 2.1.9 | 300 | 11/21/2025 |
| 2.1.1 | 260 | 4/26/2025 |
| 2.1.0-preview.3 | 149 | 4/26/2025 |
| 2.0.1 | 256 | 1/23/2025 |
| 2.0.0-alpha.62 | 98 | 1/8/2025 |
| 2.0.0-alpha.61 | 111 | 1/7/2025 |
| 2.0.0-alpha.60 | 124 | 12/7/2024 |
| 2.0.0-alpha.55 | 110 | 11/22/2024 |