Source generation
How Elarion's Roslyn generators turn attributes and conventions into deterministic, inspectable registration code.
Source generation is the mechanism behind everything in Elarion. Instead of scanning assemblies with reflection at startup, the generators read your attributes and conventions at compile time and emit ordinary DI registration code. Startup is deterministic, AOT-friendly, and the generated code can be inspected like any other source.
Turning generators on
An application project references the runtime package and the generators as an analyzer:
<ItemGroup>
<PackageReference Include="Elarion" Version="0.1.0" />
<PackageReference Include="Elarion.Generators" Version="0.1.0" PrivateAssets="all" />
</ItemGroup>(When building inside this repository, reference the generator project with
OutputItemType="Analyzer" ReferenceOutputAssembly="false" instead.)
Then add the full framework opt-in once in the assembly:
using Elarion.Abstractions;
using MyApp.Application.Pipeline;
[assembly: DefaultPipeline]
[assembly: UseElarion][assembly: UseElarion] enables the framework-owned assembly-level generators for module handlers,
module services, module validators, scheduled jobs, event consumers, and resilience policies. It
intentionally does not replace application-owned policy attributes such as [DefaultPipeline], nor
the class-targeted host trigger [GenerateModuleBootstrapper] or [GenerateDbSets].
Narrow triggers
Use the narrower triggers when an assembly intentionally wants only a subset of the generators:
[assembly: GenerateModuleHandlers]
[assembly: GenerateModuleServices]
[assembly: GenerateModuleValidators]
[assembly: GenerateScheduledJobs]
[assembly: GenerateEventConsumers]
[assembly: GenerateResiliencePolicies]Generator reference
| Generator | Trigger | Generated API |
|---|---|---|
HandlerRegistrationGenerator | [assembly: UseElarion] or [assembly: GenerateModuleHandlers] | Add{HandlerName}() and Add{ModuleName}Handlers() extension methods. |
ModuleServiceRegistrationGenerator | [assembly: UseElarion] or [assembly: GenerateModuleServices] | Add{ServiceName}Service() and Add{ModuleName}Services() extension methods. |
ModuleValidatorRegistrationGenerator | [assembly: UseElarion] or [assembly: GenerateModuleValidators] | Add{ModuleName}Validators() extension methods. |
SchedulerRegistrationGenerator | [assembly: UseElarion] or [assembly: GenerateScheduledJobs] | Add{AssemblyName}ScheduledJobs() registering descriptors and job types. |
EventConsumerRegistrationGenerator | [assembly: UseElarion] or [assembly: GenerateEventConsumers] | Per-module Add{Module}EventConsumers() registering each [ConsumeEvent] consumer (handler or [Service] method) plus its EventSubscriptionDescriptor. See Events. |
ResiliencePolicyGenerator | [assembly: UseElarion] or [assembly: GenerateResiliencePolicies] | Policy name, typed Reference, per-policy and aggregate registration methods. |
ElarionManifestGenerator | Any project containing [AppModule], [HttpEndpoint], or [RpcMethod] | Internal assembly metadata manifests consumed by host-side generators for referenced modules and transport handlers. |
AppModuleDiscoveryGenerator | [GenerateModuleBootstrapper] on a host partial class | ConfigureAllServices, MapAllEndpoints, RegisterRpcMethods, RegisterMcpMethods, GetMcpMetadata, GetAllJsonTypeInfoResolvers, IsModuleEnabled, GetAllModuleNames, plus per-module Map{Module}Http / Add{Module}JsonRpc / Add{Module}Mcp / Get{Module}McpMetadata. The single transport-wiring path for [HttpEndpoint], JSON-RPC, and MCP. See Hosting and HTTP endpoints. |
EF Core DbSet generator | [GenerateDbSets] on a partial interface | DbSet<T> members and entity configuration application. See Entity Framework Core. |
Generated code intentionally uses explicit type names and DI factory registrations. Startup behavior remains deterministic and AOT-friendly.
Projects that contain modules or transport handlers emit compact compile-time manifests as assembly metadata.
Host-side map/bootstrapper generators read those manifests from referenced assemblies instead of recursively
scanning referenced symbols. This makes the manifest the compile-time contract for cross-assembly discovery:
generators that create [AppModule], [HttpEndpoint], or [RpcMethod] types must also emit matching Elarion
manifest metadata for those generated types. Hosts still read [AppModule] declarations in their own compilation
directly because source generators cannot consume another generator's same-compilation output.
A host wires every transport through [GenerateModuleBootstrapper], which emits module-scoped,
feature-flag-gated mapping: MapAllEndpoints, RegisterRpcMethods, and RegisterMcpMethods map each
enabled module's generated [HttpEndpoint] routes, JSON-RPC-surfaced [RpcMethod] methods, and
MCP-surfaced tools respectively (core modules always; feature modules gated by Modules:{Name}:Enabled).
A handler picks its dispatcher surfaces with [RpcMethod(Transports = ...)]. Modules are the only path —
a host with no modules declares a single core [AppModule] (which maps unconditionally). See
Hosting, MCP server, and
HTTP endpoints.
Diagnostics over surprises
Because discovery is compile-time, mistakes surface as build diagnostics rather than runtime failures: a missing trigger attribute, an invalid service contract, a scoped hosted service, an unsupported generic service, or a handler outside its module namespace each produce an actionable message. The common cases and their fixes are listed in Troubleshooting.
The generators ship analyzer release notes (AnalyzerReleases.Shipped.md /
AnalyzerReleases.Unshipped.md) so diagnostic IDs are tracked across versions — a small but real
signal that diagnostics are treated as a stable contract.