How NSmithy works
Generated code describes the service; runtime libraries act on it. The generator emits model types, a schema for every shape, and the client and server surface. Wire formats, routing, retries, validation, and error handling live in hand-written NuGet packages that read those schemas.
| Part | Generated or runtime | Owns |
|---|---|---|
| Model types | Generated | The service’s data as C# records, unions, enums, and exceptions |
| Schemas | Generated | Each shape’s structure and traits, and serializing its values |
| Client and server surface | Generated | Typed methods, handler interfaces, and the service’s table of operations |
| Codecs | Runtime | One wire format each: JSON, CBOR, XML, Protobuf |
| Protocols | Runtime | Encoding, routing, and error representation of one Smithy protocol |
| Client runtime | Runtime | A call’s lifecycle: endpoint, auth, interceptors, retries, telemetry |
| Server runtime and host | Runtime | Dispatching a request to its handler on ASP.NET Core |
At build time
Section titled “At build time”Generation is part of dotnet build; there is no separate command. It runs in
three steps:
- MSBuild collects the model files, including those of referenced contracts projects, and writes the Smithy build configuration.
- The Smithy CLI assembles and validates the model and runs NSmithy’s generator.
- MSBuild compiles the generated
.g.csfiles underobj/with the project.
Unchanged models skip generation, and dotnet watch build regenerates on every
model edit. Handler implementations are application code and are never
overwritten. The MSBuild reference covers
configuration and the bundled toolchain.
Schemas
Section titled “Schemas”A schema such as GetCityInputSchema describes its shape’s members and traits,
and serializes values of the shape by handing each member to whatever consumes
it. A JSON codec writes the member as JSON, the validator checks its
constraints, and an HTTP binding puts it in a path label or header. Every
consumer goes through the same generated code, so a new wire format or protocol
needs no new generated code, and nothing reflects over model types.
Trait values are model data, so a schema keeps traits the generator knows
nothing about: a custom @owner("catalog-team") trait reaches the running
application without a generator change. Keeping a trait does not implement it.
A new constraint or protocol needs a runtime component that reads it, and a
trait that changes the C# representation, such as @required, needs generator
support. Both can come from a package outside NSmithy.
Protocols
Section titled “Protocols”A protocol is a runtime object, such as RestJson1Protocol. It binds an
operation’s schema to HTTP requests and responses: how input is encoded, which
route an operation answers on, and how a modeled error is represented and
recognized. Generated code never branches on a protocol, so the generated
types are the same under every protocol the service declares. A client uses one
protocol at a time and can switch it in configuration; a server can serve the
same handlers over several at once, as
Hosting multiple protocols shows.
A client call
Section titled “A client call”client.GetCityAsync(input) passes the input and the operation’s binding to
the client runtime, which runs every call the same way:
resolve the endpoint and select an auth scheme -> run interceptors -> protocol serializes the request -> sign and send the attempt (retried by the retry strategy) -> protocol deserializes the output, or throws the modeled errorAuth, retries, interceptors, and telemetry are therefore client configuration, not generated code, and apply to every operation and protocol alike. Paginators and waiters call the client’s own operation methods, so each page or poll is a normal call.
A server request
Section titled “A server request”MapWeather hands the service’s operations and the selected protocols
to the ASP.NET Core host. Each protocol reports its routes, and the host maps
them as endpoints. A request then flows:
host converts the HttpContext into a Smithy request -> protocol deserializes the input -> validator checks the model's constraints -> handler runs: GetCityAsync(input, cancellationToken) -> protocol serializes the output, or the modeled error the handler threwhost writes the responseThe handler sees only typed model values. Validation rejects a request that breaks the model before the handler runs, and the MCP host serves the same operations from the same table.
Why it matters
Section titled “Why it matters”Because behavior lives in the runtime, one model and one implementation serve several protocols, fakes and MCP reuse the same operations, and NSmithy can be extended with a new protocol or service generator from a package of its own; see Extending NSmithy.