Hardened.Library.SourceGenerator 0.16.0-rc1000

This is a prerelease version of Hardened.Library.SourceGenerator.
There is a newer prerelease version of this package available.
See the version list below for details.
dotnet add package Hardened.Library.SourceGenerator --version 0.16.0-rc1000
                    
NuGet\Install-Package Hardened.Library.SourceGenerator -Version 0.16.0-rc1000
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Hardened.Library.SourceGenerator" Version="0.16.0-rc1000">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Hardened.Library.SourceGenerator" Version="0.16.0-rc1000" />
                    
Directory.Packages.props
<PackageReference Include="Hardened.Library.SourceGenerator">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Hardened.Library.SourceGenerator --version 0.16.0-rc1000
                    
#r "nuget: Hardened.Library.SourceGenerator, 0.16.0-rc1000"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Hardened.Library.SourceGenerator@0.16.0-rc1000
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Hardened.Library.SourceGenerator&version=0.16.0-rc1000&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Hardened.Library.SourceGenerator&version=0.16.0-rc1000&prerelease
                    
Install as a Cake Tool

Hardened

Hardened.Framework

A compile-time, source-generated .NET framework for web APIs and serverless functions. The dependency injection, routing, parameter binding, configuration and request filters are written by source generators during the build, not resolved by reflection at startup. What runs is ordinary C# you can open and read.

The core is provider-agnostic: a handler never learns what host it runs on, and swapping the runtime module is the whole migration. AWS Lambda is the function compute supported today, through Hardened.Amz.

Full documentation: ipjohnson.github.io/Hardened.Docs

Start here

dotnet new install Hardened.Templates
dotnet new hardened-web -n Greeter
cd Greeter
dotnet run --project src/Greeter.Host

That is a working API with tests, on http://localhost:5080, with a reference page at /docs.

$ curl localhost:5080/greeting/world
{"message":"Hello, world!"}

Start from a template rather than from bare packages. The runtime packages carry no analyzers, so a project that references only them compiles to an application that answers 404 to everything. The templates wire the generators, pin every version in one place, and split the projects so the host can be swapped without touching the code.

Template What you get
hardened-web An implementation library, a host, and tests. --host kestrel\|aspnet\|aws-lambda, --contract code\|openapi\|smithy
hardened-function A serverless function and tests, on AWS Lambda today. --trigger invoke\|sqs
hardened-library A reusable module an application picks up with one attribute

See the templates guide for every option, and getting started for the same project assembled by hand.

The contract is yours to choose

Hardened builds the same application from any of three contract styles. Pick with --contract code|openapi|smithy on the template, or change your mind later.

Code-first

The C# is the contract. A route is an attribute on a method of a plain class: no base type, no interface, no registration. The OpenAPI document is generated from your handlers.

[BasePath("/greeting")]
public partial class GreeterLibrary;      // the module

public class GreetingController {
    [Get("/{name}")]
    public Greeting Hello(string name) => new($"Hello, {name}!");
}

The application names its runtime and the libraries it composes, and that is the whole bootstrap:

[HardenedModule]
[KestrelRuntime]          // or [AspNetCoreRuntime], or [LambdaWebModule] from Hardened.Amz
[GreeterLibrary]
public partial class Application;

OpenAPI-first

An OpenAPI document is the contract. Add it to the project as an AdditionalFiles item and the build generates the models, a service interface per tag, the routes and the validation its constraints describe.

# contracts/greeting.yaml
paths:
  /greeting/{name}:
    get:
      tags: [Greeting]
      operationId: hello
      parameters:
        - { name: name, in: path, required: true, schema: { type: string } }
      responses:
        '200':
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Greeting' }

You implement the interface it wrote. [Handler] is the whole wiring; the verb and the path came from the document, so neither is restated in C#.

[Handler]
public class GreetingService : IGreetingService {
    public Task<Greeting> Hello(string name) =>
        Task.FromResult(new Greeting($"Hello, {name}!"));
}

There are no route attributes anywhere in the project. Add an operation to the contract and the build writes the model, the route and the validation, then stops compiling until your service implements the new method. See generating from OpenAPI.

Smithy-first

The same generated output from a Smithy model instead of an OpenAPI document.

service Greeter {
    version: "2024-01-01"
    operations: [Hello]
}

@http(method: "GET", uri: "/greeting/{name}")
@readonly
operation Hello {
    input := {
        @httpLabel
        @required
        name: String
    }

    output := {
        @required
        message: String
    }
}

The implementation side is identical: implement the generated interface, mark it [Handler].

[Handler]
public class GreeterService : IGreeterService {
    public Task<HelloOutput> Hello(string name) =>
        Task.FromResult(new HelloOutput($"Hello, {name}!"));
}

Constraint traits like @required and @length become validation filters in front of the handler. Needs the Smithy CLI on PATH; the build names the version it expects if yours differs. See generating from Smithy.

Whichever you choose

The application serves its OpenAPI document at /openapi.json and a reference page at /docs. Code-first, the document is generated from the routing table; contract-first, it is your contract embedded verbatim. Hardened does not generate clients — the document is the deliverable, and Kiota or NSwag pointed at it does the rest. See the OpenAPI document.

Three return models

A handler that can answer more than one way has to say so somewhere. There are three places to say it. The choice decides what the compiler checks and what the generated document describes, and all three work side by side.

The handler says Other statuses Needs
Standard one success type thrown any SDK
Response the whole set, as Response<T1..Tn> in the return type any SDK
Union the whole set, as a C# union in the return type .NET 11, LangVersion preview

Standard is the default. The signature names the success type and every other status is thrown. Nothing in the signature says the route can answer a 404, so nothing checks that you handled it, and the document describes only the 200.

[Get("/todos/{id}")]
public Todo ById(ITodoStore store, int id) {
    var todo = store.Find(id);

    if (todo is null) {
        throw new NotFound("todo", $"No todo has id {id}.").AsException();
    }

    return todo;
}

Response puts the whole set in the return type. Response<T1..Tn> is an ordinary struct with an implicit conversion per case, so the handler returns payloads and never names the wrapper. The compiler knows the set and the document describes all of it.

[Get("/todos/{id}")]
public Response<Todo, NotFound> ById(ITodoStore store, int id) {
    var todo = store.Find(id);

    if (todo is null) {
        return new NotFound("todo", $"No todo has id {id}.");
    }

    return todo;
}

Union declares the same set as a C# language union, which adds exhaustiveness wherever you pattern-match on the result. The handler body is identical to the Response version.

public union TodoResult(Todo, NotFound);

[Get("/todos/{id}")]
public TodoResult ById(ITodoStore store, int id) { /* same body */ }

Unions need net11.0 and <LangVersion>preview</LangVersion>, which rules out AWS Lambda's net8.0 managed runtime today. Hardened matches Response and union structurally, so moving between them rewrites no handler. Cases like NotFound, Created<T> and RateLimited are built-in records that carry their status; each has a <T> form for your own error body.

Code-first, the return type alone decides. Contract-first, the statuses come from the contract and <HardenedResponseModel>Standard|Response|Union</HardenedResponseModel> decides the generated interface's shape. Declared 404s as nullable returns, and operations with two success statuses, are in declared responses.

Filters

Every request runs through the same pipeline, whatever the transport: an HTTP call, a function invocation, a queue message. A pipeline is an ordered list of filters, and the handler you wrote is the last one. A filter does its work around chain.Next(); not calling it short-circuits everything after it, which is how authorization and caching return without reaching the handler.

public class TimingFilter : IExecutionFilter {
    public async Task Execute(IExecutionChain chain) {
        var start = MachineTimestamp.Now;

        try {
            await chain.Next();
        }
        finally {
            chain.Context.RequestMetrics.Record(
                RequestMetrics.TotalRequestDuration, start.GetElapsedMilliseconds());
        }
    }
}

Attach a filter to one handler with an attribute ([Retry] is the shipped example), or to every handler through IGlobalFilterRegistry. Serialization is itself a filter: the response carries the handler's return value, so a filter that changes the payload changes the value rather than the bytes. The ordering, the context and the shipped positions are in the execution pipeline.

What else the build writes

The same generate-don't-reflect treatment runs through the rest of the framework:

  • Parameter binding — path, query, header, body and injected services bind through code emitted for each handler's exact signature; a binding that cannot work is a build error.
  • Configuration — a configuration model is a partial class of private fields; the generator writes the interface, the implementation and the environment-variable reads.
  • Authorization — a handler says what it needs; the pipeline decides whether the caller has it.
  • Streaming responses — return IAsyncEnumerable<T> and the response streams.
  • Content negotiation and System.Text.Json configuration follow the same shape.

Everything lands as readable source: EmitCompilerGeneratedFiles is on in the templates, so the routing table, the handlers and the binding sit under obj/<configuration>/<tfm>/generated/.

Testing

A test method declares what it needs as parameters. The framework boots the real application around the test, injects them, and substitutes a mock wherever a parameter is marked [Mock]. There is no socket, port or running host: ITestWebApp sends the request through the actual pipeline — routing, filters, binding, the handler and serialization.

Two assembly attributes are the whole wiring: the harness, and the module under test.

[assembly: WebTesting]
[assembly: HardenedTestEntryPoint(typeof(TodoLibrary))]

public class TodoTests {
    [HardenedTest]
    public async Task GetTodo_ReturnsTheTodo(ITestWebApp app) {
        var response = await app.Get("/todos/1");

        response.Assert.Ok();
        Assert.Equal(1, response.Deserialize<TodoResponse>().Id);
    }

    [HardenedTest]
    public async Task GetTodo_UnknownId_IsNotFound(ITestWebApp app) {
        (await app.Get("/todos/9999")).Assert.NotFound();
    }
}

See testing and testing web apps.

Packages

Everything ships to nuget.org as Hardened.*, and the templates reference the right set for each project shape. Assembling by hand, the source generators are not optional and do not flow transitively: the project that owns the application references them directly. The full list is in the package reference.

  • Hardened.Amz — the AWS provider: Lambda runtimes, test harnesses, DynamoDB client, CDK constructs
  • Hardened.Docs — the documentation site
There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

NuGet packages (3)

Showing the top 3 NuGet packages that depend on Hardened.Library.SourceGenerator:

Package Downloads
LambdaWidgets.Runtime

The widget host for a Hardened application: routing and binding from the widget event, Razor helpers that emit cwdb-action from generated links, describe, and the typed widget context.

LambdaWidgets.Charts

Charts for CloudWatch custom widgets: inline SVG rendered on the server, because the console strips JavaScript. Validated colours for both console themes, and a table beside every chart.

LambdaWidgets.Dashboard

The CloudWatch custom widget contract as a library: events, cwdb-action, forms, the sanitizer and the default styles. No host, no HTTP, no UI.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.33.0-rc1000 0 9/10/2026
0.32.0-rc1000 42 9/9/2026
0.31.0-rc1000 35 9/9/2026
0.30.0-rc1000 133 9/8/2026
0.22.0-rc1000 57 9/6/2026
0.21.0-rc1000 72 9/5/2026
0.20.0-rc1000 55 9/4/2026
0.19.0-rc1000 52 9/3/2026
0.18.0-rc1000 59 9/2/2026
0.17.0-rc1000 57 8/31/2026
0.16.0-rc1000 64 8/31/2026
0.15.0-rc1000 58 8/29/2026
0.14.0-rc1000 61 8/26/2026
0.13.0-rc1000 59 8/25/2026
0.12.0-rc1000 70 8/21/2026
0.11.0-rc1000 75 8/20/2026
0.10.0-rc1000 63 8/19/2026
0.9.0-rc1000 68 8/19/2026
0.8.0-rc1000 79 8/18/2026
0.6.0-rc1000 82 8/18/2026
Loading failed