Repository navigation
5. Behaviors
Core concept of Stateflows framework is Behavior: piece of executable logic which is addressable, has its own persistent state and can be interacted with by Events sent to it and received from it.
Note that Behavior is the equivalent of Actor in Actor model. Stateflows derives its core concepts from the UML, hence the name "Behavior". This term applies to all kinds of Actor-like executable logic units in Stateflows framework, even if not all of them are derived from UML.
Behaviors differ in the way they accept and process incoming Events; they can use different notations and have different structure, which is also executed differently.
Stateflows provides four core Behavior Types:
- Action (designed for simple tasks that do not require structured workflows),
- State Machine (designed to manage processes),
- Activity (more suited for data processing),
- Entity (designed for persistent domain data and operations).
Stateflows State Machine and Activity models are based on UML behavioral diagrams.
Definition of Behavior (f.e. State Machine structure) is called Behavior Class.
For each Behavior Class there can be multiple addressable Behaviors, distinguished by string-based Instance ID.
Every host must configure a runtime to execute Behaviors, deliver events, and schedule time events. AddStateflows registers services and definitions but does not select or start a runtime. The core package currently includes a transitional in-process Legacy runtime, scheduled for removal; new applications should use Orleans, which is intended to be the remaining runtime. See Runtimes for setup.
Each Behavior has its own lifecycle:
- Before any interaction with it, it has status
NotInitialized.
Actually, before any interaction with Behavior, it exist only virtually. Behavior represents virtual actor in Stateflows framework.
- By default, first interaction (first Event sent to it) initializes Behavior and enables it to consume next incoming Events.
Initialization can be customized, so developers can control when it happens.
- Behavior can also finish its work and be marked as
Finalized, which means that no further Events will be consumed by it (with the exception of some system events).
Each Behavior context also exposes a per-instance Values key-value collection for internal implementation data. State Machines provide additional State-scoped collections. See Behavior Values and State-Scoped Values.
To define a core Behavior, define a State Machine, Activity, Action, or Entity.
State Machines are suited to manage interactive processes; Activities model token-processing flows. Entities represent persistent domain data and operations. Quick comparison:
| Behavior | Primary model | Typical use |
|---|---|---|
| Action | A code operation | A discrete task that does not need a structured workflow |
| State Machine | States, transitions, and events | An event-driven lifecycle or interactive process |
| Activity | Nodes, flows, and tokens | A data-processing graph or workflow |
| Entity | Fields, mutations, and projections | Persistent domain data with controlled updates and queryable views |
- You have a business object which should behave differently in different states,
- You want to integrate actions on a business object that are run by different clients,
- You want to build your UI the way that enables only actions that are available for your business object,
- You want to build UI wizard/creator process.
- You have heavy calculations to run,
- You want to integrate data input from different clients into single processing flow,
- You want to add interactivity to your data processing.
- You have a simple, but heavy calculations to run.
- You need persistent domain data with event-based mutations,
- You want to expose read-only projections and optionally publish their updates.
A State Machine can embed submachines, Activities, and Actions. Related behaviors can share domain data through owned Entities. See Behavioral Groups for state-lifetime and invocation-scoped embedding, parent/owner communication, and shared Entities.
Typed runtime elements can inject application services and the context appropriate to their execution site. This includes State Machine states and transitions, Activity nodes and flows, Action implementations, and supported Entity middleware. Definition-time Build methods are different from runtime activation. See Dependency Injection for the context availability tables, reusable handlers, token injection, and lifetime rules.
Complete host examples include startup and service registration. A fragment beginning with .Add... belongs after a builder variable inside the appropriate definition callback or static Build method; it is not a standalone C# compilation unit. Type declarations and startup statements are shown separately.
State Machine fragments that add ordinary states, composite states, or pseudostates assume an initial configuration has already been declared on the containing machine builder. The builder's available methods change after AddInitialState (or its composite/orthogonal equivalent); use the returned initialized builder for additional elements. Register definitions under the family methods AddStateMachines, AddActivities, AddActions, and AddEntities, not directly on IStateflowsBuilder.
Unless a page supplies them, application-specific services (orders, inventory, logger, and similar), IDs, event/DTO types, and typed model elements must be supplied by the application. Typed State Machine examples use IState classes for ordinary states, and the appropriate dedicated interface for composite/orthogonal states. Reusable State Machine and Activity definitions implement static Build methods. Imports typically include Stateflows, the behavior namespace, Stateflows.Common, System.Linq, and System.Threading.Tasks.
Definitions are built during registration, not mutated in response to runtime events. A State Machine definition can reuse another typed definition and adjust it with UseStateMachine<TBase>:
using Stateflows.StateMachines;
public sealed record Confirm;
public sealed class BaseDocument : IStateMachine
{
public static void Build(IStateMachineBuilder machine) => machine
.AddInitialState("New")
.AddState("Confirmed");
}
public sealed class ConfirmableDocument : IStateMachine
{
public static void Build(IStateMachineBuilder machine) => machine
.UseStateMachine<BaseDocument>(definition => definition
.UseState("New", state => state
.AddTransition<Confirm>("Confirmed")));
}UseState selects an existing state; AddState adds a new one. Activities have the corresponding UseActivity<TBase> and node/flow override builders. This is definition composition, not state-lifetime embedding: use Behavioral Groups to run a separate child Behavior.
Register versions under the same behavior class name to evolve a definition while retaining the earlier definition. For example, in startup code:
services.AddStateflows(stateflows => stateflows
.AddStateMachines(machines => machines
.AddStateMachine<BaseDocument>("Document", version: 1)
.AddStateMachine<ConfirmableDocument>("Document", version: 2)));The newest registered version is selected for a new instance; persisted instances retain their selected definition version. Activities, Actions, and Entities also accept versioned registrations. A new version is not an automatic migration of existing persisted data or active configurations. Keep every version required by live instances deployed, preserve compatible serialized contracts, and plan data/state migrations explicitly. Do not silently replace code under an existing version number or remove a live version. Resetting an instance is a lifecycle operation, not a data migration strategy. See Orleans Runtime for version-aware placement during deployments.
Behavior can be interacted with by sending Events to it. In order to do that, Behavior handle must be located using IBehaviorLocator:
using Stateflows;
namespace Example
{
public class DocumentService(IBehaviorLocator locator) // IBehaviorLocator is available via Dependency Injection
{
public async Task ConfirmAsync(string documentId)
{
if (locator.TryLocateBehavior(new BehaviorId(BehaviorType.StateMachine, "Document", documentId), out var document))
{
await document.SendAsync(new Confirm());
}
else
{
throw new InvalidOperationException($"Document '{documentId}' could not be located.");
}
}
}
}Each Behavior Type has its own, specialized types of locator and Behavior handle interfaces:
IStateMachineLocatorandIStateMachineBehaviorfor State Machines,IActivityLocatorandIActivityBehaviorfor Activities,IActionLocatorandIActionBehaviorfor Actions,IEntityLocatorandIEntityBehaviorfor Entities.For details on interacting with each behavior type, see State Machines, Activities, Actions, and Entities.
Home page Support Code licensed under an MIT-style License. Documentation licensed under CC BY 4.0. © by Mikołaj Milewski, 2026