Elarion

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.

On this page