Elarion
Tutorial

Build a fullstack app

A complete, end-to-end walkthrough that builds a small billing application with every opinionated Elarion feature, plus a typed React frontend.

The Quickstart gets one handler onto the wire. This tutorial goes the whole way: a small but realistic billing application with a .NET backend and a typed React frontend, exercising every opinionated Elarion feature on its happy path.

You will build Billing — a single-tenant-per-user app where a signed-in account manages its clients and issues invoices. Each feature below earns its place in that story; nothing is bolted on just to demo an attribute.

What you will build

A typed backend

Two feature modules and a core module, handlers exposed over JSON-RPC, FluentValidation, a transactional decorator pipeline, and per-user result caching.

Reliable background work

Invoice emails sent through a resilient, retrying background job, plus a nightly cron job that chases overdue invoices.

An AI-ready surface

The same handlers exposed to AI agents as MCP tools, with OpenTelemetry traces across RPC, caching, scheduling, and resilience.

A typed React frontend

A Vite + React app using shadcn/ui and TanStack Query, calling a generated, Zod-validated client with full request cancellation via AbortSignal.

How each feature maps to the app

Every Elarion capability shows up exactly where it pulls its weight in a real billing flow:

App concernElarion featureWhere it appears
Feature boundaries (Clients, Invoicing) and a shared foundationModules (feature + core)Model the domain
Reads and writes as named use casesHandlers + ResultsWrite the features
Data access without a repository layerEF Core IAppDbContextModel the domain
Rejecting bad inputValidatorsWrite the features
Transactions + logging around every commandDecorator pipelinesWrite the features
"Who is calling?" for scoping and auditCurrent userWrite the features
Fast, per-user reads that stay fresh on writesCachingWrite the features
Flaky SMTP that must not drop an invoiceResilienceBackground work
Send-in-the-background + nightly remindersSchedulingBackground work
After-commit reactions that must not be lostEvents (durable outbox)Background work
A number generator, an email portServicesBackground work
A typed, optional transportJSON-RPCHost the API
The same handlers as AI toolsMCP serverHost the API
Traces and metrics, no SDK lock-inTelemetryHost the API
A frontend that calls handlers like functionsTypeScript clientBuild the frontend

The shape of the solution

Billing follows the recommended layout: the application project declares intent, infrastructure provides concrete capabilities, and the host wires platform concerns.

billing/
├─ Billing.sln
├─ src/
│  ├─ Billing.Application/      # modules, handlers, services, validators, jobs, policies  ← Elarion + generators
│  ├─ Billing.Infrastructure/   # BillingDbContext (PostgreSQL), SMTP email sender
│  └─ Billing.Api/              # ASP.NET Core host  ← Elarion.JsonRpc + Elarion.AspNetCore (+ MCP)
└─ web/                         # Vite + React + TanStack Query frontend

The dependency direction is Api → Infrastructure → Application. The application project never references the host or concrete infrastructure — that boundary is what lets the generators wire modules without a central registration list.

Prerequisites

  • .NET 10 SDK or later (see Installation).
  • Node.js 20+ and npm for the frontend and client generator.
  • Docker (or a local PostgreSQL) for the database.
  • The EF Core CLI for migrations: dotnet tool install --global dotnet-ef.

Scaffold the solution

Create the projects

mkdir billing && cd billing
dotnet new sln -n Billing

dotnet new classlib -n Billing.Application   -o src/Billing.Application
dotnet new classlib -n Billing.Infrastructure -o src/Billing.Infrastructure
dotnet new web      -n Billing.Api            -o src/Billing.Api

dotnet sln add src/Billing.Application src/Billing.Infrastructure src/Billing.Api

Wire project references

The onion points inward — infrastructure and the host depend on the application, never the reverse.

dotnet add src/Billing.Infrastructure reference src/Billing.Application
dotnet add src/Billing.Api            reference src/Billing.Infrastructure src/Billing.Application

Add the Elarion packages

The application project holds your modules and needs the runtime plus the generators. It also uses EF Core for data access and FluentValidation for input rules.

cd src/Billing.Application
dotnet add package Elarion
dotnet add package Elarion.Generators
dotnet add package Elarion.EntityFrameworkCore
dotnet add package Elarion.EntityFrameworkCore.Generators
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package FluentValidation
cd ../..

Mark the two generator packages as analyzer-only so they stay out of published output. Edit src/Billing.Application/Billing.Application.csproj:

src/Billing.Application/Billing.Application.csproj
<ItemGroup>
  <PackageReference Include="Elarion.Generators" PrivateAssets="all" />
  <PackageReference Include="Elarion.EntityFrameworkCore.Generators" PrivateAssets="all" />
</ItemGroup>

The infrastructure project owns the concrete DbContext and the PostgreSQL provider, plus the EF Core integration-event outbox:

cd src/Billing.Infrastructure
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
dotnet add package Elarion.Messaging.Outbox
cd ../..

The host project references the JSON-RPC transport, the ASP.NET Core integration, the MCP server, and the generators (it emits the RPC map and module bootstrapper). It also registers the scheduler, the Microsoft resilience runtime, and EF Core design-time tooling.

cd src/Billing.Api
dotnet add package Elarion.JsonRpc
dotnet add package Elarion.AspNetCore
dotnet add package Elarion.AspNetCore.Mcp
dotnet add package Elarion.Generators
dotnet add package Microsoft.Extensions.Resilience
dotnet add package Microsoft.EntityFrameworkCore.Design
cd ../..

Mark Elarion.Generators as analyzer-only in the host too:

src/Billing.Api/Billing.Api.csproj
<ItemGroup>
  <PackageReference Include="Elarion.Generators" PrivateAssets="all" />
</ItemGroup>

Turn on the generators

Opt the application assembly into Elarion generation once. We will add the pipeline attribute to this file in Write the features.

src/Billing.Application/ElarionAssembly.cs
using Elarion.Abstractions;

[assembly: UseElarion]

[assembly: UseElarion] enables generation for module handlers, services, validators, scheduled jobs, and resilience policies across the assembly.

Start PostgreSQL

docker run --name billing-db -e POSTGRES_PASSWORD=postgres \
  -e POSTGRES_DB=billing -p 5432:5432 -d postgres:17

Add the connection string to the host configuration:

src/Billing.Api/appsettings.Development.json
{
  "ConnectionStrings": {
    "Billing": "Host=localhost;Database=billing;Username=postgres;Password=postgres"
  }
}

The skeleton is in place. Next, model the domain and define the modules that own it.

On this page