Generate diagrams from your code
This guide shows you how to generate Beck diagrams from your C# code so they stay in sync with the real system instead of drifting away from it.
Why generate
A hand-drawn diagram is a snapshot. The moment you add a service, rename a queue, or move a database, the picture lies — and nobody notices until it misleads someone. A diagram walked from your real model can't drift: you build it from the same source of truth the system runs on — an Aspire app graph, an EF model, a service registry — and regenerate it whenever that source changes.
Beck.Authoring is the C# half of the package. It is dependency-free, lives in the Beck namespace, and emits Beck YAML from a fluent DiagramBuilder. Walk your model into the builder once, wire the regeneration into your build, and the diagram updates itself.
Note
This is the how-to for wiring generation into a build. If you're learning the builder for the first time, start with the C# tutorial.
Build it with DiagramBuilder
To produce a diagram from code, chain DiagramBuilder: set the direction, add nodes and edges, group what belongs together. Drive the calls from a loop over your real model rather than typing each node by hand.
using Beck;
var builder = new DiagramBuilder("Web Platform")
.Direction(Direction.TB)
.Node("web", n => n.Title("Web App").Kind(NodeKind.User))
.Node("gw", n => n.Title("API Gateway").Kind(NodeKind.Gateway))
.Node("orders", "Orders Service")
.Node("db", n => n.Title("Postgres").Kind(NodeKind.Db))
.Group("services", g => g.Label("Services").Members("gw", "orders").Accent(AccentToken.Primary))
.Edge("web", "gw")
.Edge("gw", "orders")
.Edge("orders", "db", e => e.Label("query"));
Node, Group, and Edge all take an id plus an optional configuration callback, so foreach (var service in registry) builder.Node(service.Id, ...) is the whole trick. The full builder surface — every NodeBuilder, GroupBuilder, EdgeBuilder, and FlowBuilder method — is in the API reference.
Tip
ToYaml() throws if an edge references an id you never declared, so a malformed walk fails loudly in C# rather than blanking the diagram in the browser.
Emit it
The builder has two terminal methods. Pick by where the diagram is going.
To write a standalone file your site can fetch, call .ToYaml() and save it:
File.WriteAllText("wwwroot/diagrams/generated.beck.yaml", builder.ToYaml());
To get a ready-to-paste Markdown block, call .ToFence() — it returns the YAML already wrapped in a ```beck fence:
File.WriteAllText("docs/architecture.md", builder.ToFence());
Render the result
A .beck.yaml file renders in Markdown with a ```beck:symbol fence pointing at it. The Pennington preprocessor runs the C# engine at build time and inlines a static, self-animating <svg> — no script tag, no client runtime.
A fence from .ToFence() is simpler still: drop it straight into any Markdown page and its ```beck block renders to an inline <svg> the same way. In an ASP.NET app, BeckSvg.Render(File.ReadAllText(path)) from the Beck package returns that same self-contained <svg> string for you to write into a page.
Here is the kind of diagram the snippet above produces:
type: architecture
meta: { title: Web Platform, direction: TB }
nodes:
- { id: web, title: Web App, kind: user }
- { id: gw, title: API Gateway, kind: gateway }
- { id: orders, title: Orders Service }
- { id: db, title: Postgres, kind: db }
groups:
- { id: services, label: Services, members: [gw, orders], accent: primary }
edges:
- { from: web, to: gw }
- { from: gw, to: orders }
- { from: orders, to: db, label: query }
The other diagram types
Every diagram type has a builder. SequenceDiagramBuilder scripts an interaction
(Participant / Message / Reply / Section), StateDiagramBuilder a machine
(State / Transition / Initial / Final), and ClassDiagramBuilder a UML model:
string fence = new SequenceDiagramBuilder("Checkout")
.Participant("web", "Web App", NodeKind.User)
.Participant("api", "Orders API")
.Message("web", "api", "POST /orders")
.Reply("api", "web", "201 Created")
.ToFence();
The class builder has the strongest generation story of all: ClassDiagramBuilder.FromTypes
reflects real CLR types into cards and infers the relations among them — base types become
inherits, interfaces implements, property types labelled associations (collections get a *
multiplicity), enums «enum» cards. A domain-model diagram that literally cannot drift:
string fence = ClassDiagramBuilder
.FromTypes(typeof(Entity), typeof(Order), typeof(OrderLine), typeof(Customer), typeof(OrderStatus))
.Title("Order Model")
.ToFence();
Only the types you pass are drawn — references to anything outside the set are ignored, so the diagram stays scoped on purpose. See Draw a class diagram for the output.
Regenerate in CI
To keep the committed diagram honest, regenerate it on every build and fail when it drifts. Put the generation in a small console target (or a test) that writes the file, then guard with git diff --exit-code — a non-empty diff means someone changed the model without committing the new diagram.
# Walk the model and rewrite the diagram
dotnet run --project tools/GenerateDiagram -c Release
# Fail the build if the committed file is now stale
git diff --exit-code wwwroot/diagrams/generated.beck.yaml
If you'd rather assert inside the test suite, write the file in a test and run dotnet test, then keep the same git diff --exit-code guard as the final CI step. The contributor's fix is the same in both cases: rerun the generator and commit the regenerated YAML.
Tip
Beck.Sample is a runnable end-to-end example of generating diagrams from code. Run dotnet run --project src/Beck.Sample -c Release to print one to stdout — pass sequence, state, class, or reflection to see the other builders in use.
Next steps
- The API reference — the complete
Beck.Authoringbuilder API. - Author a diagram in C# — the hands-on introduction to the builder.
- Flow & animation reference — the vocabulary for scripting
.Flow(...)once your generated diagram should move.