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 — the source generator ships inside it:
<ItemGroup>
<PackageReference Include="Elarion" Version="0.2.6" />
</ItemGroup>(When building inside this repository, reference the generator project with
OutputItemType="Analyzer" ReferenceOutputAssembly="false" instead — a ProjectReference to the
runtime project does not pull in the bundled analyzer.)
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,
services, scheduled jobs, event consumers, resilience policies, actor facades, authorization
policies, client-event topics, permissions, and variants. It
intentionally does not replace application-owned policy attributes such as [DefaultPipeline], nor
the dedicated triggers [assembly: GenerateModuleBootstrapper] (the host) or [GenerateDbSets] (the
application's DbContext).
Narrow triggers
Use the narrower triggers when an assembly intentionally wants only a subset of the generators:
[assembly: GenerateModuleHandlers]
[assembly: GenerateModuleServices]
[assembly: GenerateScheduledJobs]
[assembly: GenerateEventConsumers]
[assembly: GenerateResiliencePolicies]
[assembly: GenerateActors]
[assembly: GenerateModuleAuthorizationPolicies]
[assembly: GenerateClientEventTopics]
[assembly: GeneratePermissionCatalog]
[assembly: GenerateVariantCatalog]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. |
ContractSetRegistrationGenerator | [GenerateContractSetRegistration(typeof(TContract))] on a host-authored static partial IServiceCollection extension method (each declaration is its own opt-in; not subsumed by [UseElarion]) | The method's implementation, composing every implementation of the contract in the declaring assembly via TryAddEnumerable — pulled unconditionally by the host, never module-gated. See Services. |
ValidationResolverGenerator | Handler discovery ([assembly: UseElarion] or [assembly: GenerateModuleHandlers]) when the compilation references Elarion.Validation | A per-module IValidatableInfoResolver for the module handlers' annotated request types (constant-constructed attribute arrays — no runtime attribute reflection), registered through the module's gated ConfigureDefaultServices. See Validation. |
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. |
ResiliencePolicyRegistrationGenerator | [assembly: UseElarion] or [assembly: GenerateResiliencePolicies] | Policy name, typed Reference, per-policy and aggregate registration methods. |
ActorRegistrationGenerator | [assembly: UseElarion] or [assembly: GenerateActors] | Typed actor facades and each module's Add{Module}Actors() registration hook. See Actors. |
AuthorizationPolicyRegistrationGenerator | [assembly: UseElarion] or [assembly: GenerateModuleAuthorizationPolicies] | Per-module registration for named [AuthorizationPolicy] implementations. See Authorization. |
ClientEventRegistrationGenerator | [assembly: UseElarion] or [assembly: GenerateClientEventTopics] | Per-module topic catalog/registration from IClientEvent contracts and their subscribe-time requirements. See Client events. |
PermissionCatalogGenerator | [assembly: UseElarion] or [assembly: GeneratePermissionCatalog] | The typed ElarionPermissions catalog harvested from handler permission and role requirements. |
VariantCatalogGenerator | [assembly: UseElarion] or [assembly: GenerateVariantCatalog] | The typed ElarionVariants catalog, grouped by module and switch. See Feature flags. |
ElarionManifestGenerator | Any project containing [AppModule], [HttpEndpoint], or [Handler] | Internal assembly metadata manifests consumed by host-side generators for referenced modules and transport handlers. |
AppModuleDiscoveryGenerator | [assembly: GenerateModuleBootstrapper] on the host → the fixed-name ElarionBootstrapper static | Extension methods on their natural receivers — services.AddElarion(configuration), endpoints.MapElarionEndpoints(configuration), dispatcher.RegisterHandlers(configuration), configuration.GetMcpMetadata(), configuration.GetAllJsonTypeInfoResolvers(), configuration.IsModuleEnabled(name) — plus the parameterless GetAllModuleNames, the per-module transport extension methods Map{Module}Http / Add{Module}Handlers, and the parameterless 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 the concrete partial DbContext class (entities supplied by [EntityConfiguration] classes) | DbSet<T> members and entity configuration application via direct ApplyConfiguration<T> calls. 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 [Handler] 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: MapElarionEndpoints and RegisterHandlers map each enabled module's
generated [HttpEndpoint] routes and its [Handler] operations into the dispatcher bus respectively
(core modules always; feature modules gated by Modules:{Name}:Enabled). RegisterHandlers builds the
one operation registry that both the JSON-RPC and MCP adapters resolve, each serving only the operations
flagged for its surface. A handler picks its dispatcher surfaces with [Handler(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.