Elarion

Schema generation

Export rpc-schema.json automatically during dotnet build with the Elarion.AspNetCore.SchemaGeneration MSBuild package.

rpc-schema.json is the contract that drives the TypeScript client. You can export it in code with JsonRpcSchemaExporter, or — more conveniently — have it written automatically during dotnet build.

Beyond each operation's request/response shapes, the exported schema carries [Description] texts, the idempotent flag for [Idempotent] operations, and the request DTOs' DataAnnotations constraints as JSON Schema keywords (minLength/maxLength, minimum/maximum, pattern, format — see Validation), so the generated client can pre-validate requests against exactly what the server enforces. An attribute's explicit ErrorMessage rides along under x-elarion-error-messages, keyed by constraint keyword (see Error messages).

Each method also lists its declared error contract (ADR-0080) under errors: stable code → kind (the kind's default code such as not_found) and, when the error carries a typed payload, the data schema. Handlers declare errors with [ProducesError]; the framework adds validation, authorization, feature-gate and idempotency failures where they are statically known. The block is omitted for a method that declares none.

"errors": {
  "not_found": { "kind": "not_found" },
  "token.malformed": { "kind": "validation", "data": { "type": "object", "properties": { "reason": { "type": "string" } }, "required": ["reason"] } }
}

A method also says what a client may know before calling it, each member omitted when empty: requires — either anonymous, or authenticated with the permissions and roles the handler requires (all of them) and serverChecks for a policy, claim or resource requirement only the server can evaluate; readOnly for an IQuery request; and tags, the handler's [ClientTag] hints. They are computed at compile time from the same declarations the authorization decorator enforces (an [ElarionAuthorizationDefaults] in scope sets authenticated), and the generated client exposes them as rpcMethodInfo. The schema ships with the client, so it shows every method's required permission names, the same vocabulary the capabilities block lists.

"invoices.approve": {
  "params": { "…": "…" },
  "result": { "…": "…" },
  "requires": { "authenticated": true, "permissions": ["invoices.approve"], "serverChecks": true },
  "tags": { "timeout": "60s" }
}

Requiredness

One rule, in both directions (ADR-0082): a non-nullable property is required on the wire, a nullable property is optional. The C# required modifier and [JsonRequired] do not change the schema. For request types a non-nullable property with a default is also optional to send: a constructor parameter default (int Size = 10) or an initializer that yields a non-default value (List<string> Tags { get; init; } = []). An initializer equal to the CLR default (= 0, = false) is indistinguishable from none, so give such a member a constructor default or make it nullable.

For response, event and error-payload types, every non-nullable member is always written, so it is required. Result and event types must therefore not opt members out with [JsonIgnore(Condition = WhenWritingDefault)] (or WhenWritingNull on a non-nullable reference): the generator reports ELRPC004 for a handler response graph, and the exporter fails with the member name for event payloads and custom serializer options. Make the member nullable to declare it optional. The canonical serializer enforces the contract it exports (RespectNullableAnnotations and RespectRequiredConstructorParameters), so a null for a non-nullable member and a missing non-nullable constructor parameter without a default are rejected rather than silently accepted. The reverse holds too: a nullable member is optional to send everywhere — record Search(string Query, string? Note) accepts {"query":"q"} and binds Note to null, with no = null needed — including a nullable member marked required or [JsonRequired], which stays a construction-site aid for the compiler only.

Build-time export

Add the schema generation package as private build tooling in the host project:

<ItemGroup>
  <PackageReference Include="Elarion.AspNetCore.SchemaGeneration"
                    Version="0.3.1"
                    PrivateAssets="all" />
</ItemGroup>

<PropertyGroup>
  <ElarionJsonRpcGenerateSchema>true</ElarionJsonRpcGenerateSchema>
  <ElarionJsonRpcSchemaOutputPath>$(MSBuildProjectDirectory)/../../rpc-schema.json</ElarionJsonRpcSchemaOutputPath>
</PropertyGroup>

The package imports MSBuild targets that run after the host compiles. The target launches the built application, captures the host immediately after builder.Build(), resolves the registered JsonRpcDispatcher, and writes the schema using the dispatcher's runtime serializer options. Application code after builder.Build() does not run during generation.

Properties

PropertyDefaultPurpose
ElarionJsonRpcGenerateSchemafalseEnables the package targets.
ElarionJsonRpcGenerateSchemaOnBuildsame as ElarionJsonRpcGenerateSchemaControls automatic generation during dotnet build.
ElarionJsonRpcSchemaOutputPath$(BaseIntermediateOutputPath)rpc-schema.jsonExact schema file path.
ElarionJsonRpcSchemaOutputDirectory$(BaseIntermediateOutputPath)Used with ElarionJsonRpcSchemaFileName when no explicit path is set.
ElarionJsonRpcSchemaFileNamerpc-schema.jsonFile name used with the output directory.
ElarionJsonRpcSchemaEnvironmentDevelopmentValue for DOTNET_ENVIRONMENT / ASPNETCORE_ENVIRONMENT while loading the app.
ElarionJsonRpcSchemaApplicationArgumentsemptyArguments passed to the app entry point after --.
ElarionJsonRpcSchemaGenerationOptionsemptyExtra tool arguments for advanced scenarios.

Manual generation

The same target can be invoked directly:

dotnet msbuild src/MyApp.Api/MyApp.Api.csproj \
  -t:GenerateElarionJsonRpcSchema \
  -p:ElarionJsonRpcGenerateSchema=true

Guarding startup work

Because the application is loaded during the build, guard expensive or external startup work. JsonRpcSchemaGeneration.IsRunning checks the Elarion marker environment variable and the schema generation tool's entry assembly name:

var app = builder.Build();

if (!JsonRpcSchemaGeneration.IsRunning) {
    await app.Services.GetRequiredService<IStartupTask>().RunAsync();
}

Requirements

Schema generation requires the host to register a frozen JsonRpcDispatcher before builder.Build(). If the dispatcher is missing, unfrozen, or empty, the build target fails with an actionable error instead of writing a stale schema. See Hosting for the registration.

On this page