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
| Property | Default | Purpose |
|---|---|---|
ElarionJsonRpcGenerateSchema | false | Enables the package targets. |
ElarionJsonRpcGenerateSchemaOnBuild | same as ElarionJsonRpcGenerateSchema | Controls automatic generation during dotnet build. |
ElarionJsonRpcSchemaOutputPath | $(BaseIntermediateOutputPath)rpc-schema.json | Exact schema file path. |
ElarionJsonRpcSchemaOutputDirectory | $(BaseIntermediateOutputPath) | Used with ElarionJsonRpcSchemaFileName when no explicit path is set. |
ElarionJsonRpcSchemaFileName | rpc-schema.json | File name used with the output directory. |
ElarionJsonRpcSchemaEnvironment | Development | Value for DOTNET_ENVIRONMENT / ASPNETCORE_ENVIRONMENT while loading the app. |
ElarionJsonRpcSchemaApplicationArguments | empty | Arguments passed to the app entry point after --. |
ElarionJsonRpcSchemaGenerationOptions | empty | Extra 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=trueGuarding 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.