Hosting
Wire the module bootstrapper, RPC method map, 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.1.0" />
<PackageReference Include="Elarion.JsonRpc" Version="0.1.0" />
<PackageReference Include="Elarion.AspNetCore" Version="0.1.0" />
<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;
namespace MyApp.Api.Hosting;
[GenerateModuleBootstrapper]
public static partial class ModuleBootstrapper;ModuleBootstrapper gains extension methods on their natural receivers so the host wires modules
fluently: services.AddElarion(configuration), endpoints.MapElarion(configuration),
dispatcher.RegisterRpcMethods(configuration), dispatcher.RegisterMcpMethods(configuration),
configuration.GetMcpMetadata(), configuration.GetAllJsonTypeInfoResolvers(), and
configuration.IsModuleEnabled(name) — plus the parameterless ModuleBootstrapper.GetAllModuleNames()
and per-module transport extension methods (Map{Module}Http, Add{Module}JsonRpc, Add{Module}Mcp)
and the parameterless Get{Module}McpMetadata for every module that owns [HttpEndpoint] / [RpcMethod]
handlers.
All transports are module-scoped and feature-flag-gated through the bootstrapper: a module disabled
with Modules:{Name}:Enabled = false has its services, validators, MapEndpoints, [HttpEndpoint] routes,
[RpcMethod] 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.AddInMemoryScheduler(builder.Configuration);
builder.AddMyAppPlatformCapabilities();
builder.Services.AddElarion(builder.Configuration);
var serializerOptions = CreateSerializerOptions(builder.Configuration);
builder.Services.AddSingleton(serializerOptions);
// Registers JSON-RPC options and the frozen JsonRpcDispatcher singleton, gated per module.
builder.Services.AddJsonRpc(serializerOptions, ModuleBootstrapper.RegisterRpcMethods);
var app = builder.Build();
app.MapElarion(app.Configuration);
app.MapJsonRpc();
app.Run();See Serialization for CreateSerializerOptions.
AddJsonRpc(serializerOptions, ModuleBootstrapper.RegisterRpcMethods) is the config-aware overload: it
resolves IConfiguration when the dispatcher singleton is built and registers only the enabled modules'
JSON-RPC-surfaced [RpcMethod] handlers. MapElarion does the same gating for module MapEndpoints
and generated [HttpEndpoint] routes. To expose the same handlers over MCP (an independent, equally gated
transport), pair builder.Configuration.GetMcpMetadata() with
ModuleBootstrapper.RegisterMcpMethods 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, validators, 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.
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.