Elarion

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

GeneratorTriggerGenerated 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.
ElarionManifestGeneratorAny 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 classConfigureAllServices, 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 interfaceDbSet<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.

On this page