Trellis.Asp 3.0.0-alpha.99

This is a prerelease version of Trellis.Asp.
dotnet add package Trellis.Asp --version 3.0.0-alpha.99
                    
NuGet\Install-Package Trellis.Asp -Version 3.0.0-alpha.99
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Trellis.Asp" Version="3.0.0-alpha.99" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Trellis.Asp" Version="3.0.0-alpha.99" />
                    
Directory.Packages.props
<PackageReference Include="Trellis.Asp" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Trellis.Asp --version 3.0.0-alpha.99
                    
#r "nuget: Trellis.Asp, 3.0.0-alpha.99"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Trellis.Asp@3.0.0-alpha.99
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Trellis.Asp&version=3.0.0-alpha.99&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Trellis.Asp&version=3.0.0-alpha.99&prerelease
                    
Install as a Cake Tool

ASP.NET Core Extensions

NuGet Package

Comprehensive ASP.NET Core integration for functional domain-driven design, providing:

  1. Automatic Scalar Value Validation — Property-aware error messages with comprehensive error collection
  2. Result-to-HTTP Conversion — Seamless Result<T> to HTTP response mapping
  3. Model Binding — Automatic binding from route/query/form/headers
  4. Optional Value ObjectsMaybe<T> support for JSON, model binding, and MVC validation
  5. Native AOT Support — Optional source generator for zero-reflection overhead

Installation

dotnet add package Trellis.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<TSelf, T>) as well as any custom implementations of IScalarValue.

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 Trellis.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))
        .ToCreatedAtActionResult(this,
            actionName: nameof(GetUser),
            routeValues: user => new { id = user.Id });

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))
        .ToCreatedAtRouteHttpResult(
            routeName: "GetUser",
            routeValues: user => new RouteValueDictionary(new { id = user.Id })));

HTTP Status Mapping

Result Type HTTP Status Description
Success 200 OK Success with content
Success (Created) 201 Created Resource created with Location header
Success (Unit) 204 No Content Success without content
ValidationError 400 Bad Request Validation errors with details
BadRequestError 400 Bad Request General bad request
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

Custom Error Mappings

Customize how domain errors map to HTTP status codes:

builder.Services.AddTrellisAsp(options =>
{
    options.MapError<DomainError>(StatusCodes.Status400BadRequest);
});

Default mappings are applied automatically. AddTrellisAsp is optional — only needed to override specific mappings.

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

  1. Always use fieldName parameter — Enables property-aware errors
  2. Call validation setup in Program.cs — Required for automatic validation
  3. Add UseScalarValueValidation() middleware — Creates validation scope
  4. Use [ApiController] in MVC — Enables automatic validation responses
  5. Use async variants for async operationsToActionResultAsync, ToHttpResultAsync
  6. Combine approaches — Automatic validation for DTOs, manual Result chaining for business rules

License

MIT — see LICENSE for details.

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.99 0 3/4/2026
3.0.0-alpha.98 25 3/3/2026
3.0.0-alpha.95 42 3/2/2026
3.0.0-alpha.94 33 3/2/2026
3.0.0-alpha.93 42 3/1/2026
3.0.0-alpha.92 56 2/28/2026
3.0.0-alpha.83 42 2/27/2026