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>
<PackageReference Include="Elarion.JsonRpc" Version="0.1.0" />
<PackageReference Include="Elarion.AspNetCore" Version="0.1.0" />
<PackageReference Include="Elarion.Generators" Version="0.1.0" PrivateAssets="all" />
<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 ConfigureAllServices, MapAllEndpoints(endpoints, configuration),
RegisterRpcMethods(dispatcher, configuration), RegisterMcpMethods(dispatcher, configuration),
GetMcpMetadata(configuration), GetAllJsonTypeInfoResolvers, IsModuleEnabled, and GetAllModuleNames,
plus per-module transport methods (Map{Module}Http, Add{Module}JsonRpc, Add{Module}Mcp,
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();
ModuleBootstrapper.ConfigureAllServices(builder.Services, 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();
ModuleBootstrapper.MapAllEndpoints(app, 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. MapAllEndpoints does the same gating for module MapEndpoints
and generated [HttpEndpoint] routes. To expose the same handlers over MCP (an independent, equally gated
transport), pair ModuleBootstrapper.GetMcpMetadata(builder.Configuration) 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.