Source Generators
Termina uses C# source generators to eliminate boilerplate code while maintaining AOT compatibility.
Why Source Generators?
Source generators provide:
- No Reflection - Code is generated at compile time, not runtime
- AOT Compatible - Works with Native AOT publishing
- IDE Support - Generated code appears in IntelliSense
- Debuggable - You can step into generated code
Termina uses source generators for route parameter injection via the [FromRoute] attribute.
The [FromRoute] Generator
What You Write
public partial class DetailViewModel : ReactiveViewModel
{
[FromRoute] private int _id;
}What Gets Generated
// DetailViewModel.g.cs (generated)
partial class DetailViewModel
{
public int Id { get; private set; }
internal void ApplyRouteParameters(IDictionary<string, object> parameters)
{
if (parameters.TryGetValue("id", out var idValue))
{
Id = Convert.ToInt32(idValue);
}
}
}WARNING
Your ViewModel class must be partial when using [FromRoute] for the source generator to work.
Viewing Generated Code
To see the generated code in your IDE, add to your project file:
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)Generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>Generated files appear in obj/Generated/.
Key Requirements
Partial Classes (for [FromRoute])
Your ViewModel class must be partial when using [FromRoute]:
// ✓ Correct
public partial class MyViewModel : ReactiveViewModel
{
[FromRoute] private int _id;
}
// ✗ Won't work - missing partial
public class MyViewModel : ReactiveViewModel
{
[FromRoute] private int _id;
}TIP
If your ViewModel doesn't use [FromRoute], it does not need to be partial. Reactive state is managed with ReactiveProperty<T> which doesn't require source generation.
Field Naming
[FromRoute] fields must start with _ and use camelCase:
// ✓ Correct
[FromRoute] private int _id; // → Id
[FromRoute] private string _userName; // → UserName
// ✗ Won't work correctly
[FromRoute] private int id; // No underscore
[FromRoute] private int Id; // PascalCaseBenefits for AOT
Traditional frameworks use reflection to:
- Find properties at runtime
- Create bindings dynamically
- Invoke property changed notifications
Termina's approach:
[FromRoute]generates route parameter binding at compile timeReactiveProperty<T>provides observable state without reflection- Full support for
PublishAot=true
# Works because no reflection is needed
dotnet publish -c Release -r win-x64 --self-contained /p:PublishAot=true