Repository navigation
Dependency Injection
Stateflows runtime elements can receive application services and the context of their current execution through constructor injection. This applies to State Machine states, guards and effects, Activity nodes and flows, Action implementations, and supported typed middleware. A context parameter does not need a special attribute:
public sealed class Working(
IStateContext state,
IBehaviorContext behavior,
ILogger<Working> logger) : IStateEntry
{
public Task OnEntryAsync()
{
logger.LogInformation("Entering {State} in {Behavior}", state.Name, behavior.Id);
return Task.CompletedTask;
}
}This example uses System.Threading.Tasks, Microsoft.Extensions.Logging, Stateflows.Common, and Stateflows.StateMachines. Register the state through a typed builder overload such as AddState<Working>(). Application services must be registered with the host's IServiceCollection; Stateflows supplies the execution contexts when it activates the element.
Stateflows activates typed runtime elements through StateflowsActivator. It selects the public constructor with the most parameters, resolves its arguments, and constructs the element. Prefer one public constructor. Ordinary arguments are resolved as required DI services; missing registrations fail activation.
Model-element types referenced by typed builders do not need a separate AddTransient<TElement>() registration. Their application dependencies do. Stateflows-specific parameter attributes are processed on the model element's constructor, not recursively on constructors of arbitrary application services resolved by Microsoft DI.
The static Build methods of IStateMachine, IActivity, IStateDefinition, and other definition contracts run while definitions are registered. They are not per-instance execution callbacks and have no current behavior context to inject. An Entity's IEntity<TTemplate>.Build similarly configures a definition, not an executing Entity.
Do not treat runtime element objects as persistent behavior instances. Keep durable data in Behavior Values or Entities, not in element instance fields.
The common interfaces are in Stateflows.Common:
| Injectable service | Meaning |
|---|---|
IBehaviorContext |
The currently executing Behavior: identity, Values, messaging, subscriptions, and associated Entity operations. |
IExecutionContext |
The current trigger, trigger ID, event headers, and cancellation token. |
IParentBehaviorContext |
Messaging and Entity operations directed at the immediate containing Behavior, when one exists. |
IOwnerBehaviorContext |
Messaging and Entity operations directed at the behavioral-group owner, when one exists. |
These are execution-dependent services, not arbitrary services that can be resolved at startup or from an unrelated HTTP request. The runtime keeps their current values in AsyncLocal holders and exposes them through transient DI factories. Transient registration does not create a new Behavior or extend a context's lifetime.
If a context is unavailable, its factory throws InvalidOperationException with a message such as No service for type '...' is available in this context. Registering all behavior families does not make all their contexts available everywhere. A nullable constructor parameter does not bypass this throwing factory.
Inject parent/owner contexts directly when embedding is a requirement of the element. If the element must also work as a standalone Behavior, use the execution callback's TryGetParentBehaviorContext / TryGetOwnerBehaviorContext methods and handle absence deliberately. See Behavioral Groups.
In addition to the common contexts, typed State Machine runtime elements can inject IStateMachineContext and Stateflows.StateMachines.IExecutionContext. The latter extends the common execution context with recorded ExecutionSteps. Use fully qualified names or aliases when importing both namespaces.
| Activation site | Element-specific context | Not available at this site |
|---|---|---|
| State entry/exit, composite/orthogonal initialization/finalization | IStateContext |
ITransitionContext |
| State deferral guard | IStateContext |
ITransitionContext |
| Transition guard/effect, including default transitions | ITransitionContext |
IStateContext |
| Machine initializer/finalizer | Machine and execution contexts only |
IStateContext, ITransitionContext
|
For transition endpoints, use ITransitionContext.Source and .Target; the target is absent for an internal transition. Do not inject a current IStateContext into a transition to represent either endpoint. The registered service is the non-generic ITransitionContext; receive the typed event through GuardAsync(TEvent) / EffectAsync(TEvent), not by assuming the callback's generic context interface is a DI service.
IStateMachineInspection is also registered for runtime inspection. Use Observability for observation hooks rather than retaining the inspection model beyond execution.
An inherited IStateEntry implementation can use the registered state identity, eliminating repeated entry lambdas and duplicated name literals:
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Stateflows.Common;
using Stateflows.StateMachines;
public abstract class LoggedState(
IStateContext state,
IBehaviorContext behavior,
ILogger<LoggedState> logger) : IStateEntry
{
public Task OnEntryAsync()
{
logger.LogInformation("Entering {State} in {Behavior}", state.Name, behavior.Id);
return Task.CompletedTask;
}
}
public sealed class Waiting(
IStateContext state,
IBehaviorContext behavior,
ILogger<LoggedState> logger) : LoggedState(state, behavior, logger);
public sealed class Processing(
IStateContext state,
IBehaviorContext behavior,
ILogger<LoggedState> logger) : LoggedState(state, behavior, logger);Definition fragment:
.AddInitialState<Waiting>(state => state
.AddTransition<StartProcessing, Processing>())
.AddState<Processing>()Supply the application's StartProcessing event. The base can instead await a domain operation, such as updating the group's Entity with the current stage. Only states that should perform that operation should inherit it. Composite and orthogonal implementations must additionally implement their appropriate marker interfaces.
Use typed registration and target overloads consistently. Where an API requires a string, use State<TState>.Name rather than a hardcoded copy of a type-derived name. An explicitly overridden registration name must also be used consistently; State<TState>.Name resolves naming metadata/settings, not an arbitrary custom registration alias. See State identity.
ITransitionGuard<in TEvent> and ITransitionEffect<in TEvent> are contravariant. One handler can consume an interface implemented by several concrete event types:
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Stateflows.Common;
using Stateflows.StateMachines;
public interface IReasonable
{
string Reason { get; }
}
public sealed record Cancel(string Reason) : IReasonable;
public sealed record Reject(string Reason) : IReasonable;
public sealed class RecordReason(
IBehaviorContext behavior,
ILogger<RecordReason> logger) : ITransitionGuard<IReasonable>, ITransitionEffect<IReasonable>
{
public Task<bool> GuardAsync(IReasonable @event) =>
Task.FromResult(!string.IsNullOrWhiteSpace(@event.Reason));
public Task EffectAsync(IReasonable @event)
{
logger.LogInformation("Reason for {Behavior}: {Reason}", behavior.Id, @event.Reason);
return Task.CompletedTask;
}
}One class can implement both contracts. The typed transition overload automatically
attaches its guard and effect, so there is no need to repeat AddGuard and
AddEffect callbacks at each use:
.AddTransition<Cancel, RecordReason, Stopped>()
.AddTransition<Reject, RecordReason, Stopped>()Stopped must be a registered IState type. The transitions still declare concrete trigger types; sharing an interface-based handler does not itself register an interface-triggered transition. Do not infer event-dispatch behavior from handler contravariance.
The type-parameter order is trigger, transition, target, omitting the transition type when only a target is needed, and omitting parameters that do not apply:
| Transition kind | Typed form |
|---|---|
| Regular |
AddTransition<TEvent, TTarget>() or AddTransition<TEvent, TTransition, TTarget>()
|
| Internal |
AddInternalTransition<TEvent, TTransition>() (no target) |
| Default |
AddDefaultTransition<TTarget>() or AddDefaultTransition<TTransition, TTarget>() (no trigger) |
| Choice/junction completion branch |
AddTransition<TTarget>() or AddTransition<TTransition, TTarget>() (no trigger) |
| Else regular |
AddElseTransition<TEvent, TEffect, TTarget>() (effect only) |
| Else internal |
AddElseInternalTransition<TEvent, TEffect>() (no target, effect only) |
| Else default |
AddElseDefaultTransition<TEffect, TTarget>() (no trigger, effect only) |
| Else choice/junction branch |
AddElseTransition<TEffect, TTarget>() (no trigger, effect only) |
Use the corresponding contracts: event-triggered transitions implement
ITransitionGuard<TEvent> and/or ITransitionEffect<TEvent>; default transitions
use IDefaultTransitionGuard and/or IDefaultTransitionEffect. Else paths have no
guard. The optional builder callback can still add site-specific behavior, such
as an embedded invoice Activity after the transition's typed effect.
Separate guard/effect classes remain useful when they are independently reusable.
When the same combination always belongs together, use one transition class,
optionally inheriting shared logic and fixing operation-specific settings in
small derived types. Guard and effect hooks are activated separately; do not use
instance fields to pass data from GuardAsync to EffectAsync.
For per-use settings, implement IConfigurable<TConfiguration> (Stateflows.Common) and supply element => element.AddConfiguration(value) in the typed registration callback. The AddConfiguration extension requires Stateflows.Common.Interfaces. Configuration is applied after construction, before the handler method runs; do not read it in the constructor. This allows one effect to distinguish cancellation from failure without duplicating mutation logic, or one guard to match a configured operation.
Event-independent logic can implement ITransitionGuard / ITransitionEffect; completion-only logic can use IDefaultTransitionGuard / IDefaultTransitionEffect.
Typed Activity nodes and flows can inject IActivityContext, common behavior/execution contexts, and contexts appropriate to their activation site:
| Activation site | Element-specific contexts |
|---|---|
| Executable node, accept-event node, or structured node lifecycle | INodeContext |
| Flow guard/transformation |
IFlowContext, not INodeContext
|
| Graph exception-handler node |
INodeContext and IExceptionContext
|
| Activity initializer/finalizer | Activity and execution contexts, not node/flow contexts |
IFlowContext.SourceNode and .TargetNode expose the flow endpoints. A flow does not have one current node to inject. IExceptionContext describes the protected node and node of origin; it is supplied for graph exception-handler node activation, not as a general context for every callback or middleware exception handler. IActivityInspection (in Stateflows.Activities.Inspection.Interfaces) is registered for runtime inspection.
Token interfaces, in Stateflows.Activities, are also injectable into runtime workers:
| Interface family | Purpose |
|---|---|
IInputTokens<T>, IInputToken<T>
|
Read inputs of the requested type as a collection or a single token. |
IOptionalInputTokens<T>, IOptionalInputToken<T>
|
Declare optional inputs; the single-token form exposes IsAvailable and TokenOrDefault. |
IInputOutputTokens<T>, IInputOutputToken<T> and optional counterparts |
Read and pass through tokens using the combined input/output contracts. |
IOutputTokens<T> |
Add typed output tokens for the current execution. |
For example:
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Stateflows.Activities;
public sealed class SumPrices(
INodeContext node,
IInputTokens<decimal> prices,
IOutputTokens<decimal> totals) : IActionNode
{
public Task ExecuteAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
totals.Add(prices.Sum());
return Task.CompletedTask;
}
}Register it with AddAction<SumPrices>(...) and route incoming/outgoing decimal flows. The injected node.Name identifies this node's registered identity when shared code needs it. IInputToken<T>.Token selects the first matching token and fails if none exists; it is not an implicit exactly-one validation. Activity activation requirements and token routing still apply. See Tokens.
Flow logic can similarly implement IFlowGuard<TToken> or IFlowTransformation<TToken, TResult>, inject IFlowContext and application services, and receive the token in GuardAsync / TransformAsync. These contracts are not State Machine transition contracts.
A class implementing Stateflows.Actions.IAction can inject Stateflows.Actions.IActionContext, common behavior/execution contexts, and the same token-interface families listed above. The token interfaces remain in the Stateflows.Activities namespace even when consumed by an Action:
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Stateflows.Actions;
using Stateflows.Activities;
public sealed class CalculateTotal(
Stateflows.Actions.IActionContext action,
IInputTokens<decimal> prices,
IOutputTokens<decimal> totals) : IAction
{
public Task ExecuteAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
totals.Add(prices.Sum());
return Task.CompletedTask;
}
}Register with AddAction<CalculateTotal>() under AddActions. action.Id supplies the current Action identity. An Action has no Activity node/flow or State Machine state/transition context. Its parent/owner contexts address related Behaviors; they do not inject the parent's current state or node into the worker. ExecuteAsync receives cancellation directly; alternatively inject Stateflows.Common.IExecutionContext to inspect cancellation and event headers.
An Entity template is an interface implemented by a runtime proxy, not a constructor-injected model-element class. Its default-interface initializers, mutations, computations, and projections use the template's fields and their declared arguments. Do not add arbitrary service parameters to these methods expecting automatic DI, or try to inject the template as a live Entity service. Use events, projections, and associated Entity operations.
Typed Entity observers, interceptors, and exception handlers are activated separately. That activation supplies common IBehaviorContext / IExecutionContext and available parent/owner contexts, alongside registered application services. It does not provide an IEntityContext DI service or State Machine/Activity-specific element contexts. Use each hook's supplied context for its Entity template, mutation, field, or projection information. See Entities and Observability.
Do not generalize this middleware activation to every hook in every behavior family. Observers/interceptors may be constructed before a particular state, node, or transition is selected, and some registration paths use ordinary DI activation. Prefer the hook's context argument for operation-specific information.
Special constructor-parameter attributes provide another kind of injection:
| Mechanism | Resolves |
|---|---|
[GlobalValue] |
A Behavior-wide Value or IValue<T> accessor. |
[StateValue], [ParentStateValue]
|
State Machine state-scoped Values at a compatible activation site. |
[SourceStateValue], [TargetStateValue]
|
Transition endpoint Values; an internal transition has no target. |
[FromField<TTemplate>] |
A directly readable field of an associated Entity. |
[FromProjection<TTemplate>] |
A projection of an associated Entity. |
Value and Entity attributes are in Stateflows.Common.Attributes, except state-specific Value attributes, which are in Stateflows.StateMachines.Attributes. Entity attributes select EntityScope.Self (default), Parent, or Owner; see Behavioral Groups.
A plain injected Value, field, or projection is read during element activation. It is a snapshot, not a live property binding or a subscription. An IValue<T> accessor instead performs Value operations when called. For current Entity data, use explicit checked Entity reads through the appropriate behavior/parent/owner context. Field injection requires direct Get access; missing required data fails activation. Making a data value optional does not create a missing parent/owner relationship.
For observers, interceptors, and exception handlers activated through the specialized model-element resolver, plain Value injection is explicitly rejected; use an IValue<T> accessor and read it in an appropriate hook instead. Registration paths using ordinary DI activation do not process these Stateflows parameter attributes.
See Behavior Values, Entity Fields, and Entity Projections for their detailed contracts.
Keep injected contexts and token accessors inside the execution that supplies them. Do not capture them in singletons, static fields, caches, or detached background tasks. They are not persistent state or a cross-Behavior shared container. Normal DI lifetimes still govern application services; a service that captures a current context must not outlive that execution.
An asynchronous child Activity/Action is a separate Behavior with its own execution context, even when it shares a behavioral-group owner. A typed inline transition effect is awaited; replacing it with AddEffectAction / AddEffectActivity changes it into embedded behavior dispatch, not merely a different injection syntax. See Behavioral Groups.
Lambda callbacks already receive their appropriate execution context. Some APIs also expose a Delegate overload that resolves parameters through Stateflows; use that only where the overload explicitly supports it. Constructor-activation features are not a promise that every lambda, delegate, middleware registration, or Entity method supports the same parameter attributes.
The registration factories and their holders establish which service types exist; the activation sites establish which contexts have meaningful values:
- Common DI registration, CommonContextHolder, and StateflowsActivator.
- State Machine DI registration, StateMachinesContextHolder, and Executor.
- Activity DI registration, ActivitiesContextHolder, and NodeScope.
- Action DI registration, ActionsContextHolder, and ActionsRegister.
- Entity DI registration and EntitiesRegister.
Home page Support Code licensed under an MIT-style License. Documentation licensed under CC BY 4.0. © by Mikołaj Milewski, 2026
Overview
Installation
Runtimes
Orleans runtime
Concepts
Events and Structural Events
Messaging Patterns
Validation
Scheduling
Observability
Behaviors
Behavior Values
Dependency Injection
Behavioral Groups
State Machines
Concepts
State Identity
In-State Event Deferrals
Evaluation of Transitions
Exception Handling
Building blocks
Regions
States
State
Composite State
Orthogonal State
Final State
Pseudostates
Choice
Junction
Fork
Join
History
Transitions
Transition
Default Transition
Internal Transition
Activities
Concepts
Exception Handling
Implicit Fork and Join
Tokens
Building blocks
Nodes
Action Node
Decision Node
Merge Node
Initial Node
Final Node
Input Node
Output Node
Fork Node
Join Node
Accept Event Action Node
Send Event Action Node
Publish Event Action Node
Time Event Action Node
Data Store Node
Structured Activity Node
Iterative Activity Node
Parallel Activity Node
Flows
Data Flow
Control Flow
Actions
Entities
Fields
Mutations
Projections
Extensions
ClearScript
FluentValidation
JSON definitions
Minimal APIs
OneOf
OpenTelemetry
PlantUML
Scheduling