Elarion
JSON-RPC

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.

On this page