Cairn

Opt-in HATEOAS for ASP.NET Core — hypermedia links and actions, added only where they help.

Cairn adds _links and actions to your API responses without touching your models. DTOs stay plain record types — no base class, no marker interface, no attributes. Hypermedia is declared separately in a LinkConfig<T> and injected at serialization time through a System.Text.Json contract modifier. Endpoints you don't opt in serialize exactly as before, which makes Cairn safe to adopt one endpoint at a time in an existing API.

Runs on .NET 8, 9, and 10. Source on GitHub · MIT licensed.

The 30-second example

dotnet add package Cairn.AspNetCore

A plain DTO — Cairn never touches it:

public record OrderDto(int Id, string Status);

The link rules live outside the DTO, in a LinkConfig<T>:

using Cairn;

public sealed class OrderLinks : LinkConfig<OrderDto>
{
    public override void Configure(ILinkBuilder<OrderDto> b) =>
        b.Self(o => LinkTarget.Route("GetOrderById", new { id = o.Id }));
}

Register Cairn and the config, then opt the endpoint in. The route's .WithName(...) supplies the name LinkTarget.Route resolves against:

using Cairn.AspNetCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCairn(o => o.AddLinks(new OrderLinks()));

var app = builder.Build();

app.MapGet("/orders/{id:int}", (int id) => TypedResults.Ok(new OrderDto(id, "Pending")))
   .WithName("GetOrderById")
   .WithLinks();

app.Run();

A request to /orders/42 now returns the DTO with a _links object projected in:

{
  "id": 42,
  "status": "Pending",
  "_links": {
    "self": { "href": "http://localhost:5000/orders/42" }
  }
}

That's the whole model. Everything else in these docs — conditional actions, authorization gates, HAL and HAL-FORMS, pagination, forms — is the same LinkConfig<T> builder doing more.

Learn the library

Read these three in order — they build on each other:

  1. What is HATEOAS? — the concept and vocabulary in five minutes: links, relations, affordances, and why responses that carry them make clients simpler.
  2. Getting started — a ten-minute walkthrough: install, declare, opt in, and watch a cancel action appear and disappear with order state. Includes troubleshooting.
  3. Link configurations — the full builder: conditions, service-aware and async targets, authorization, titles.

Weighing hypermedia against other API styles? Cairn vs GraphQL vs OData compares the three contract models honestly — including where Cairn is the wrong tool.

Guide by topic

Shaping responses

Running in production

Beyond minimal APIs

Tooling & quality

  • Route safety — analyzers CAIRN001–003 and the generated Routes.* catalog.
  • OpenAPI & Swagger — documenting hypermedia responses.
  • Testing — asserting on links and affordances; snapshots.

The package family

Package Purpose
Cairn.Core Transport-agnostic hypermedia model: Link, LinkRelation, Affordance, LinkTarget, and the LinkConfig<T> builder. No ASP.NET Core dependency.
Cairn.AspNetCore ASP.NET Core integration: AddCairn, minimal-API .WithLinks(), MVC [CairnLinks], pagination, wire formats, problem details. Bundles the route-safety analyzers (CAIRN001002), code fixes, and the Routes.* source generator (CAIRN003).
Cairn.Client Typed hypermedia client: CairnClient, Resource<T>, link following, affordance invocation.
Cairn.Testing HypermediaResponse, .Should() assertions, and HypermediaSnapshot.
Cairn.OpenApi AddCairnHypermedia() for the built-in OpenAPI generator (.NET 10 only).
Cairn.Swashbuckle AddCairnHypermedia() for Swashbuckle/Swagger.
Cairn.Mcp WithCairnAffordances() — state/auth-gated affordances as Model Context Protocol tools for AI agents.

Most apps start with just Cairn.AspNetCore (it references Cairn.Core). Details and framework targets are in Packages.