Hosting
Wire the module bootstrapper, handler registry, and JSON-RPC endpoint into an ASP.NET Core host.
The host turns generated registration code into a running JSON-RPC endpoint. It owns composition and transport; modules own application logic.
Host project references
<ItemGroup>
<!-- Elarion bundles the source generator; reference it directly so the generator runs in the host
(analyzer assets are not transitive). -->
<PackageReference Include="Elarion" Version="0.2.6" />
<PackageReference Include="Elarion.JsonRpc" Version="0.2.6" />
<PackageReference Include="Elarion.AspNetCore" Version="0.2.6" />
<ProjectReference Include="..\MyApp.Application\MyApp.Application.csproj" />
</ItemGroup>Generated host partial
A single partial class tells the generator to emit the cross-module wiring in the host assembly:
using Elarion.AspNetCore;
[assembly: GenerateModuleBootstrapper]ElarionBootstrapper gains extension methods on their natural receivers so the host wires modules
fluently: services.AddElarion(configuration), endpoints.MapElarionEndpoints(configuration),
registry.RegisterHandlers(configuration), configuration.GetMcpMetadata(),
configuration.GetAllJsonTypeInfoResolvers(), and
configuration.IsModuleEnabled(name) — plus the parameterless ElarionBootstrapper.GetAllModuleNames()
and per-module transport extension methods (Map{Module}Http, Add{Module}Handlers)
and the parameterless Get{Module}McpMetadata for every module that owns [HttpEndpoint] / [Handler]
handlers.
All transports are module-scoped and feature-flag-gated through the bootstrapper: a module disabled
with Modules:{Name}:Enabled = false has its services, validation metadata, MapEndpoints, [HttpEndpoint] routes,
[Handler] JSON-RPC methods, and MCP tools all left unmapped, so a disabled module disappears across
every surface. Core modules are always mapped.
Composing the host
var builder = WebApplication.CreateSlimBuilder(args);
builder.Services.AddDbContext<AppDbContext>(/* provider setup */);
builder.Services.AddScoped<DbContext>(sp => sp.GetRequiredService<AppDbContext>());
builder.Services.AddMyAppApplicationScheduledJobs();
builder.Services.AddMyAppApplicationResiliencePolicies();
builder.Services.AddElarionScheduler(builder.Configuration);
builder.AddMyAppPlatformCapabilities();
// Also contributes every enabled module's source-generated JSON context to the canonical serializer.
builder.Services.AddElarion(builder.Configuration);
// Registers JSON-RPC options and the frozen JsonRpcDispatcher singleton, gated per module. Serialization
// comes from the canonical IElarionJsonSerialization — no hand-built JsonSerializerOptions is threaded in.
builder.Services.AddElarionJsonRpc(ElarionBootstrapper.RegisterHandlers);
var app = builder.Build();
app.MapElarionEndpoints(app.Configuration);
app.MapElarionJsonRpc();
app.Run();See Serialization for the canonical IElarionJsonSerialization and
ConfigureElarionJson.
AddElarionJsonRpc(ElarionBootstrapper.RegisterHandlers) is the config-aware overload: it
resolves IConfiguration when the dispatcher singleton is built and registers only the enabled modules'
handlers. The JSON-RPC adapter then serves only the operations flagged HandlerTransports.JsonRpc.
MapElarionEndpoints does the same gating for module MapEndpoints
and generated [HttpEndpoint] routes — including hooks the host declares on behalf of a web-free module
assembly with [ModuleEndpoints].
To expose the same handlers over MCP (a thin adapter over the same
handler bus, equally gated), pair builder.Configuration.GetMcpMetadata() with the same
ElarionBootstrapper.RegisterHandlers registry via AddElarionMcp — see MCP server.
The dispatcher is frozen after registration. Freezing produces an immutable, thread-safe map and lets the build-time schema exporter read a complete, deterministic method set. A missing, unfrozen, or empty dispatcher is treated as an error rather than producing a stale schema.
The exporter reads the dispatcher as configured at build time. Because feature modules are enabled by default, the schema stays the complete contract unless a module is explicitly disabled in the configuration the schema build sees — make sure every module is enabled for the schema build if you need the full contract regardless of runtime gating.
Modules are the only hosting path
Every transport — services, HTTP, JSON-RPC, and MCP — is wired through
[GenerateModuleBootstrapper], so it all stays feature-flag-gated together. There is no flat,
ungated map: a host that conceptually has "no modules" simply declares a single core [AppModule]
(core modules map unconditionally), which gives the same always-on behavior while keeping one consistent
path. Handlers whose namespace falls under no module are reported (ELHTTP003/ELRPC001) so they are
never silently dropped. The ELRPC001 message now refers to [Handler].
The bootstrapper discovers modules and handlers both from referenced module assemblies (through
their per-assembly Elarion manifests) and from the host compilation itself, so the project layout is
your choice: the multi-project layout shown above, or a single project where Program, the
[AppModule] declarations, and every handler live in one csproj with
[assembly: GenerateModuleBootstrapper]. Both wire identically.
What the host still owns
Mapping the endpoint does not move platform concerns into the framework. The host remains responsible for:
- authentication and authorization policy setup,
- database provider configuration and migrations,
- concrete platform capability providers,
- middleware order,
- health checks, telemetry exporters, and deployment integration,
- JSON-RPC endpoint publication and transport-specific error mapping.
Capabilities
Choose the opt-in platform pieces you need — transports, policy, messaging, realtime, coordination, persistence, and operations.
Serialization
One canonical JsonSerializerOptions that every Elarion subsystem reads, composed from per-module source-generated JSON contexts for AOT-friendly serialization.