Repository navigation
6.7. PlantUML
Stateflows.Extensions.PlantUml generates PlantUML text for registered State Machine and Activity definitions. The package adds inspection interceptors and handles PlantUmlInfoRequest events.
Register the extension with Stateflows:
services.AddStateflows(stateflows => stateflows
.AddPlantUml()
.AddStateMachines(machines => machines
.AddStateMachine<Order>()));Send a PlantUmlInfoRequest to a State Machine or Activity instance that can be inspected. Its response is a PlantUmlInfo containing the generated PlantUML string and convenience PNGUrl / SVGUrl links.
Those image links point to the public plantuml.com server and include the encoded diagram in the URL. Avoid sharing those links for diagrams containing sensitive information; render the returned PlantUML with a trusted local or privately hosted PlantUML server when needed. The extension source is available here.
For a located IStateMachineBehavior or IActivityBehavior named behavior:
using Stateflows.Common;
using Stateflows.Extensions.PlantUml.Events;
var result = await behavior.RequestAsync<PlantUmlInfo>(new PlantUmlInfoRequest());
if (result.Status != EventStatus.Consumed || result.Response is null)
{
throw new InvalidOperationException($"Diagram request failed: {result.Status}.");
}
await File.WriteAllTextAsync("behavior.puml", result.Response.PlantUml);PlantUmlInfoRequest does not implicitly initialize a Behavior. Initialize the instance through its normal lifecycle before requesting a diagram when required. Reading PlantUml and writing it locally does not contact the public image server; do not fetch PNGUrl or SVGUrl for private models.
Structured, parallel and iterative containers configured with .Interleave()
(NodeOptions.Interleave) include <<interleave>> in their label. Parallel
containers with a finite maxConcurrency additionally show the configured
limit, for example:
partition "ParallelActivity jobs <<interleave>> [maxConcurrency=4]" {
}These annotations describe execution settings, not additional Activity edges
or an immediate boundary-output policy. maxConcurrency counts active Activity
partitions per invocation, not CPU threads. Existing pin aliases, output
aggregation, crossing-flow rules, token counts and status colors are unchanged.
Custom inspection implementations that do not originate from the core model
retain their existing labels.
Activity diagrams include each AddInputPin<TToken> and AddOutputPin<TToken>
boundary explicitly, labeled Input pin or Output pin with its declared token
type. Unconnected pins are included, so the diagram also shows supported boundary
types that currently have no connected flow.
.AddInputPin<Order>(pin => pin.AddFlow<ValidateOrder>())
.AddAction<ValidateOrder>(node => node.AddFlow<OrderResult, OutputPin<OrderResult>>())
.AddOutputPin<OrderResult>()Pin flows connect to distinct diagram aliases rather than using shortened generic node names as identity. The same token type can therefore appear in root, nested, and sibling scopes without merging their pins. Nested pins remain inside the existing structured, parallel, or iterative partitions. Flow labels retain token types, weights, runtime token counts, and execution-status coloring.
The extension uses PlantUML's legacy Activity syntax: pins are labeled boundary activities, not native UML pin glyphs. Hidden self-links declare them independently of real data flows, including isolated pins; those links are not Activity edges and are not shown by the renderer.
INodeInspection.PinTokenType exposes the declared CLR token type to inspection
consumers. It is null for nodes without a declared token type; custom inspection
implementations inherit a null default for compatibility.
Home page Support Code licensed under an MIT-style License. Documentation licensed under CC BY 4.0. © by Mikołaj Milewski, 2026