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:
- What is HATEOAS? — the concept and vocabulary in five minutes: links, relations, affordances, and why responses that carry them make clients simpler.
- Getting started — a ten-minute walkthrough: install, declare, opt in, and watch a
cancelaction appear and disappear with order state. Includes troubleshooting. - 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
- Wire formats & negotiation — the Default shape, HAL, and HAL-FORMS, selected by
Accept. - Affordances & HAL-FORMS — actions, methods, and derived form fields.
- Pagination — offset and cursor paging with
self/prev/nextlinks. - Embedded resources —
_embedded, link arrays, and CURIEs. - Custom wire formats — plugging in Siren or a house format.
- Error responses — problem details with links and actions.
Running in production
- API versioning — composing with
Asp.Versioning. - Link URL policy — absolute vs path-relative links,
PublicBaseUri, the per-requestResolvePublicBaseUrifor multi-tenant hosts, andTransformUrl. - Conditional requests, OPTIONS & deprecation —
WithETag, preconditions,Allow, deprecation headers. - Diagnostics & observability — warnings, metrics, and tracing.
Beyond minimal APIs
- Controllers (MVC) — the same model with
[CairnLinks]. - The typed client —
CairnClient, following links, invoking actions, submitting forms. - Consuming with Ketting (JavaScript) — Cairn APIs work with the standard JS hypermedia client out of the box.
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 (CAIRN001–002), 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.