Skip to content

Latest commit

 

History

History

README.md

SampleMinimalApiNoAot - Reflection Fallback Example

This example demonstrates that Trellis.Asp works perfectly without source generation, using automatic reflection fallback for standard .NET applications.

What This Example Proves

No source generator required - The library works out of the box with reflection ✅ No [GenerateScalarValueConverters] attribute neededNo JsonSerializerContext requiredNo Native AOT constraintsSame functionality as AOT version - All features work identically ✅ Minimal overhead - Reflection overhead is ~50μs on first use (negligible for most apps)

Key Differences from SampleMinimalApi (AOT Version)

Feature SampleMinimalApi (AOT) SampleMinimalApiNoAot (Reflection)
PublishAot ✅ true ❌ false (standard .NET)
Source Generator ✅ Referenced ❌ Not referenced
JsonSerializerContext ✅ Required ❌ Not needed
[GenerateScalarValueConverters] ✅ Required ❌ Not needed
Startup Performance Fastest (pre-generated) ~50μs slower (one-time reflection cost)
Runtime Performance Identical Identical
Trimming Support Full Reflection may be trimmed
Functionality All features All features

Project Structure

SampleMinimalApiNoAot/
├── Program.cs              # Simple setup without JsonSerializerContext
├── API/
│   └── UserRoutes.cs       # User registration, validation, and error demo endpoints
└── SampleMinimalApiNoAot.csproj  # No PublishAot, no generator reference

Setup (Ultra Simple!)

1. Add Package

dotnet add package Trellis.Asp

2. Configure Services

var builder = WebApplication.CreateBuilder(args);

// That's it! No JsonSerializerContext needed
builder.Services.AddScalarValueValidationForMinimalApi();

var app = builder.Build();
app.UseScalarValueValidation();

3. Use Value Objects

// Value objects automatically validate during JSON deserialization
app.MapPost("/users/register", (RegisterUserDto dto) =>
    User.TryCreate(dto.FirstName, dto.LastName, dto.Email, dto.Password)
        .ToHttpResult())
    .WithScalarValueValidation();

Running the Sample

cd Examples/SampleMinimalApiNoAot
dotnet run

Visit http://localhost:5002 to see the API welcome page with all available endpoints.

Test Endpoints

Valid Request

POST http://localhost:5000/users/registerWithAutoValidation
Content-Type: application/json

{
  "firstName": "John",
  "lastName": "Doe",
  "email": "john@example.com",
  "password": "SecurePass123!"
}

# Response: 200 OK with User object

Invalid Request (Tests Reflection Fallback Validation)

POST http://localhost:5000/users/registerWithAutoValidation
Content-Type: application/json

{
  "firstName": "",
  "lastName": "D",
  "email": "invalid",
  "password": "weak"
}

# Response: 400 Bad Request with validation errors
{
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "FirstName": ["First name cannot be empty."],
    "LastName": ["Last name must be at least 2 characters."],
    "Email": ["Email must contain @."],
    "Password": ["Password must be at least 8 characters."]
  }
}

Test Property Name Attribution

POST http://localhost:5000/users/registerWithSharedNameType
Content-Type: application/json

{
  "firstName": "",
  "lastName": "",
  "email": "test@example.com"
}

# Response: 400 Bad Request with property-specific errors
# Even though FirstName and LastName use the same Name type,
# errors correctly show "FirstName" and "LastName", not "name"!

How Reflection Fallback Works

When you don't use the source generator, the library automatically:

  1. Detects scalar value types at runtime using reflection
  2. Creates JSON converters dynamically for IScalarValue<TSelf, TPrimitive> types
  3. Validates during deserialization by calling TryCreate() via static abstract interface members
  4. Collects all errors before returning HTTP 400 Bad Request
  5. Caches reflection results to minimize performance impact

Performance Impact

First request:  +50μs (one-time reflection cost)
Later requests: 0μs (identical to AOT version)

For a typical web API serving 1000 requests/second, the reflection overhead is:

  • Total cost: 50μs once on startup
  • Per-request cost: 0μs (after first use)
  • Impact: Negligible for 99.9% of applications

When to Use This Approach

Use reflection fallback (this example) when:

  • Building standard .NET applications
  • Prototyping or developing new features
  • Don't need Native AOT deployment
  • Want simplest possible setup
  • Don't care about 50μs startup overhead

Use source generator (SampleMinimalApi) when:

  • Targeting Native AOT deployment
  • Need maximum startup performance
  • Want trimming-safe code
  • Publishing as self-contained single-file executable

Comparison with AOT Version

Both examples provide identical functionality:

  • ✅ Automatic value object validation
  • ✅ Property-aware error messages
  • ✅ Comprehensive error collection
  • ✅ Result-to-HTTP conversion
  • ✅ Integration with Minimal API filters

The only difference is:

  • AOT version uses compile-time code generation
  • This version uses runtime reflection fallback

Runtime behavior is identical!

Related Documentation