This example demonstrates that Trellis.Asp works perfectly without source generation, using automatic reflection fallback for standard .NET applications.
✅ No source generator required - The library works out of the box with reflection
✅ No [GenerateScalarValueConverters] attribute needed
✅ No JsonSerializerContext required
✅ No Native AOT constraints
✅ Same functionality as AOT version - All features work identically
✅ Minimal overhead - Reflection overhead is ~50μs on first use (negligible for most apps)
| 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 |
SampleMinimalApiNoAot/
├── Program.cs # Simple setup without JsonSerializerContext
├── API/
│ └── UserRoutes.cs # User registration, validation, and error demo endpoints
└── SampleMinimalApiNoAot.csproj # No PublishAot, no generator reference
dotnet add package Trellis.Aspvar builder = WebApplication.CreateBuilder(args);
// That's it! No JsonSerializerContext needed
builder.Services.AddScalarValueValidationForMinimalApi();
var app = builder.Build();
app.UseScalarValueValidation();// 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();cd Examples/SampleMinimalApiNoAot
dotnet runVisit http://localhost:5002 to see the API welcome page with all available endpoints.
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 objectPOST 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."]
}
}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"!When you don't use the source generator, the library automatically:
- Detects scalar value types at runtime using reflection
- Creates JSON converters dynamically for
IScalarValue<TSelf, TPrimitive>types - Validates during deserialization by calling
TryCreate()via static abstract interface members - Collects all errors before returning HTTP 400 Bad Request
- Caches reflection results to minimize 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
✅ 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
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!
- Asp/docs/REFLECTION-FALLBACK.md - Comprehensive guide to reflection fallback
- Asp/README.md - Main library documentation
- Asp/generator/README.md - Source generator documentation
- SampleMinimalApi - AOT version with source generator