/// Application framework for .NET

Write the handler.
The build wires the rest.

One [Handler] class becomes a JSON-RPC method, a REST endpoint, and an MCP tool for AI agents — authorized, validated, and traced by a decorator pipeline that Roslyn source generators emit as ordinary C#. No reflection scanning, no startup discovery, no registration lists to drift.

Modules/Clients/GetClient.csthe class you write
[Handler("clients.get")][HttpEndpoint("clients/{id}")][RequirePermission("clients", "read")]public sealed class GetClient(AppDbContext db) : IHandler<GetClient.Query, Result<GetClient.Response>> { public sealed record Query(Guid Id) : IQuery; public sealed record Response(Guid Id, string Name); public async ValueTask<Result<Response>> HandleAsync( Query q, CancellationToken ct) { var client = await db.Clients .Where(c => c.Id == q.Id) .Select(c => new Response(c.Id, c.Name)) .FirstOrDefaultAsync(ct); return client is null ? AppError.NotFound($"Client {q.Id} was not found.") : client; }}

what the build does with it → 01

  • .NET 10 · C# 14
  • NativeAOT paths
  • Zero runtime reflection
  • 40+ focused packages
  • 1,500+ tests
  • Apache-2.0
/// building with AI agents?The case for Elarion in an AI-first team — in business terms
01 / Generated output

Attributes in. Ordinary code out.

The generators run inside dotnet build and emit the wiring you would otherwise write and keep in sync by hand. It is code, not container magic — open it, read it, step through it.

  • DI registrations with the full decorator pipeline, composed in source.
  • Minimal-API route maps and the shared JSON-RPC + MCP operation registry.
  • A schema export that becomes a typed TypeScript client.

The DI registration — the decorator pipeline composed in source.

// <auto-generated/> — Elarion.Generators, abridged for readingpublic static IServiceCollection AddGetClient(this IServiceCollection services) { services.AddScoped<GetClient>(); services.AddScoped<IHandler<GetClient.Query, Result<GetClient.Response>>>(BuildPipeline); return services;} private static IHandler<GetClient.Query, Result<GetClient.Response>> BuildPipeline( IServiceProvider sp) { IHandler<GetClient.Query, Result<GetClient.Response>> handler = sp.GetRequiredService<GetClient>(); handler = new AuthorizationDecorator<GetClient.Query, Result<GetClient.Response>>(handler, …); handler = new ObservabilityDecorator<GetClient.Query, Result<GetClient.Response>>(handler, …); return handler;}

a.Deterministic startup

The host calls generated registrations. Nothing scans assemblies at run time, so boot behavior is fixed at build time.

b.NativeAOT-ready paths

Emitted code is concrete and statically typed, and JSON serialization is source-generated — supported AOT paths avoid runtime discovery.

c.Reviewable like your own code

Flip EmitCompilerGeneratedFiles and the wiring shows up in code review as plain C# diffs.

02 / Capability index

Everything an application needs. Nothing you didn't reference.

Each capability is a focused package over seams in Elarion.Abstractions. The core depends on Microsoft.Extensions abstractions and nothing else — Polly, HybridCache, and OpenFeature enter your build the day a handler asks for them, not before.

Realtime & coordination
Client eventsAt-most-once hints tell browsers to re-query; reconnect greetings make views converge.Ordered streamsSequenced hot broadcasts with bounded replay, visible gaps, and resumable SSE.Client connectionsBidirectional WebSocket and TCP links for devices, interactive input, and server-to-client RPC.Device identitySingle-use pairing, key rotation, and constant-time HMAC handshakes for device links.ActorsMailbox-serialized live state with generated typed facades; use only when a row is not the answer.Role leasesElect one instance for a coarse role and proxy holder-owned HTTP routes when needed.Data-rate shapingBatch loss-tolerant writes and conflate latest-wins values before they hit storage or the UI.
03 / Diagnostics

Wrong wiring doesn't ship.

What the generators wire, they also validate — every mistake becomes a precise diagnostic with a fix direction. Production never finds out.

  • A reach into another module is flagged as you type (ELMOD002).
  • A route with no inferable verb fails the build, not the demo.
  • An authorization gate that can't fail closed is an error, not a hope.
Reflection scanningElarion
Wiring discovered by scanning assemblies at startupWiring emitted as inspectable C# at build time
A missing registration surfaces as a runtime exceptionMissing wiring is a compile error, caught in CI
Reflection undermines trimming and native AOTTrim- and AOT-friendly by construction
A parallel registration list drifts from the codeIntent declared on the type — one source of truth

Every diagnostic is documented, from ELRPC001 to ELMOD002 see the full list.

dotnet buildexit code 1
$ dotnet build Billing.Application → bin/Release/net10.0/Billing.Application.dll Modules/Sales/CreateOrder.cs(12,34): warning ELMOD002: Type 'Invoice' belongs to module 'Billing'; module 'Sales' must not depend on another module's internals — reach it through a [ModuleContract], or move the shared type out of the module Modules/Billing/ExportInvoices.cs(8,14): error ELAUTH001: Handler 'ExportInvoices' declares an authorization requirement but its response type 'string' does not implement IResultFailureFactory<T>, so the authorization check cannot short-circuit; return Result<T> or Result Build FAILED. 1 Warning(s) 1 Error(s) $
04 / Field report

The first migration deleted 16,223 lines.

Elarion wasn't designed on a whiteboard — it was extracted from a production application that had grown its own foundation: handler pipeline, transports, wiring, caching, the lot. The pull request that moved that application onto the released packages added 391 lines and removed 16,223. The same application came out the other side, minus its plumbing.

git diff --shortstat home-grown..elarionone pull request
insertions(+)+391
deletions(-)−16,223
net −15,832 lines · 41 deleted for every line added

a.What the 16,223 were

Bespoke infrastructure — dispatch, registration, caching, retries, auth glue. Code every product rewrites and no product differentiates itself by.

b.What the 391 are

Package references, attributes on the handlers that already existed, and a few registration calls — the declarations the build expands into wiring.

c.Why net −15,832 matters

Every deleted line is one your team no longer reviews, tests, or patches — and one your AI assistants never read, or bill you for, again.

///why it's built this wayThe philosophy, for engineers — one maxim, the pipeline, the batteries
05 / Start

A working handler in three steps.

Step 1 — add the packages

The generators ride along as analyzers — nothing extra to install.

your project3 files · 1 project
MyApp/
├─ MyApp.csprojstep 1
├─ Program.csstep 3
└─ SystemFeature.csstep 2

Step 2 — declare one feature

SystemFeature.cs
using System.Text.Json.Serialization;using System.Text.Json.Serialization.Metadata;using Elarion.Abstractions;using Elarion.Abstractions.Modules;using Elarion.AspNetCore; [assembly: UseElarion][assembly: GenerateModuleBootstrapper] namespace MyApp.System; [AppModule("System", Kind = AppModuleKind.Core)]public static partial class SystemModule { public static IJsonTypeInfoResolver GetJsonTypeInfoResolver() => SystemJsonContext.Default;} [Handler("system.ping")][HttpEndpoint("ping")]public sealed class Ping : IHandler<Ping.Query, Result<Ping.Response>> { public sealed record Query : IQuery; public sealed record Response(string Message); public ValueTask<Result<Response>> HandleAsync( Query query, CancellationToken ct) => ValueTask.FromResult<Result<Response>>(new Response("pong"));} [JsonSerializable(typeof(Ping.Query))][JsonSerializable(typeof(Ping.Response))]public sealed partial class SystemJsonContext : JsonSerializerContext;

The build now emits

  • DI registration + observability pipeline
  • GET /ping — minimal API
  • system.ping — operation metadata
  • source-generated JSON metadata

Step 3 — wire the host

Program.csthe complete host
using Elarion.AspNetCore;using MyApp; var builder = WebApplication.CreateSlimBuilder(args); builder.Services.AddElarion(builder.Configuration);builder.Services.AddElarionHttpJson(); var app = builder.Build(); app.MapElarionEndpoints(app.Configuration); app.Run();

This sample has no hidden database or undeclared dependency: build it and call GET /ping. The quickstart adds JSON-RPC, while the MCP guide exposes the same operation registry to AI agents; the handler and its policy stay unchanged.

The tutorial builds a billing app end to end — modules, authorization, events, and a typed React client.