A state machine for .NET, licensed under LGPL.
I would love to know if you use NState in your application. I can be contacted on [email protected].
Example:
//arrange
var transitions = new IStateTransition<BugState, TransitionActionStatus>[]
{
new BugTransition.Open(),
new BugTransition.Assign(new BugTransitionAction.Assign()),
new BugTransition.Defer(new BugTransitionAction.Defer()),
new BugTransition.Resolve(new BugTransitionAction.Resolve()),
new BugTransition.Close(new BugTransitionAction.Close()),
};
var myStateMachine = new StateMachine<BugState, TransitionActionStatus>("example",
transitions,
initialState: new BugState.Open());
//act
var bug = new Bug("my bug name", myStateMachine); //Bug type inherits from Stateful base type
//assert
Assert.That(bug.CurrentState, Is.TypeOf<BugState.Open>()); //true
//act
bug.Assign("[email protected]", out transitionActionStatus); //triggers a transition of the state machine
//assert
Assert.That(bug.CurrentState, Is.TypeOf<BugState.Assigned>()); //true
Assert.That(transitionActionStatus, Is.EqualTo(TransitionActionStatus.Success)); //true
Assert.That(bug.AssigneeEmail, Is.EqualTo("[email protected]")); //true
//act
var json = myStateMachine.ToJson();
var myDeserializedStateMachine = new StateMachine<BugState, TransitionActionStatus>("example",
_transitions,
initialState: new BugState.Open());
myDeserializedStateMachine.InitializeFromJson(json);
//assert
Assert.That(myDeserializedStateMachine.CurrentState, Is.TypeOf<BugState.Assigned>()); //true
- easy construction of trees of interdependent or orthogonal state machines
- supports making domain objects stateful
- trivial state machine tree persistence and retrieval to/from JSON
- transition conditions, exit and entry actions
- transition actions are fully-fledged types enabling use of dependency injection to maintain clean, testable code even for complex behavior
- initial and final state specification
0. Get it
nuget install nstate
1. Create some states
public abstract class BugState : State
{
public class Assigned : BugState {}
public class Closed : BugState {}
public class Deferred : BugState {}
public class Open : BugState {}
public class Resolved : BugState {}
}
2. Define your stateful type
public class Bug : Stateful<BugState, TransitionActionStatus>
{
public Bug(string title, IStateMachine<BugState, TransitionActionStatus> stateMachine) : base(stateMachine)
{
Title = title;
}
public string Title { get; set; }
public string AssigneeEmail { get; set; }
public string ClosedByName { get; set; }
public Bug Open(out TransitionActionStatus transitionActionStatus)
{
return TriggerTransition(this, new BugState.Open(), out transitionActionStatus);
}
public Bug Assign(string assigneeEmail, out TransitionActionStatus transitionActionStatus)
{
dynamic dto = new ExpandoObject();
dto.AssigneeEmail = assigneeEmail;
return TriggerTransition(this, new BugState.Assigned(), out transitionActionStatus, dto);
}
public void Defer(out TransitionActionStatus transitionActionStatus)
{
return TriggerTransition(this, new BugState.Deferred(), out transitionActionStatus);
}
public void Resolve(out TransitionActionStatus transitionActionStatus)
{
return TriggerTransition(this, new BugState.Resolved(), out transitionActionStatus);
}
public Bug Close(string closedByName, out TransitionActionStatus transitionActionStatus)
{
dynamic dto = new ExpandoObject();
dto.ClosedByName = closedByName;
return TriggerTransition(this, new BugState.Closed(), out transitionActionStatus, dto);
}
}
3. Define your transitions
public partial class BugTransition
{
public class Resolve : StateTransition<BugState, TransitionActionStatus>
{
public Resolve(BugTransitionAction.Resolve transitionAction)
: base(transitionAction: transitionAction) { }
public override BugState[] StartStates
{
get { return new[] { new BugState.Assigned(), }; }
}
public override BugState[] EndStates
{
get { return new[] { new BugState.Resolved(), }; }
}
}
public class Assign : StateTransition<BugState, TransitionActionStatus>
{
//...
}
//etc.
}
4. Define any transition actions
public partial class BugTransitionAction
{
public class Assign : TransitionAction<BugState, TransitionActionStatus>
{
public override TransitionActionStatus Run(BugState targetState,
IStateMachine<BugState, TransitionActionStatus> stateMachine,
dynamic statefulObject, dynamic dto = null)
{
if (dto == null)
{
throw new ArgumentNullException("dto");
}
if (dto.AssigneeEmail == null)
{
throw new Exception("AssigneeEmail not supplied.");
}
statefulObject.AssigneeEmail = dto.AssigneeEmail;
return TransitionActionStatus.Success;
}
}
//etc.
}
5. Instantiate your state machine
//...
var transitions = new IStateTransition<BugState, TransitionActionStatus>[]
{
new BugTransition.Open(),
new BugTransition.Assign(new BugTransitionAction.Assign()),
new BugTransition.Defer(new BugTransitionAction.Defer()),
new BugTransition.Resolve(new BugTransitionAction.Resolve()),
new BugTransition.Close(new BugTransitionAction.Close()),
};
_stateMachine = new StateMachine<BugState, TransitionActionStatus>("example",
transitions,
initialState: new BugState.Open());
//...
6. Work with your stateful object
//arrange
var bug = new Bug("my bug name", _stateMachine);
//act
bug.Assign("[email protected]", out transitionActionStatus);
//assert
Assert.That(bug.CurrentState, Is.TypeOf(BugState.Assigned)); //true
Assert.That(transitionActionStatus, Is.EqualTo(TransitionActionStatus.Success)); //true
Assert.That(bug.AssigneeEmail, Is.EqualTo("[email protected]")); //true
7. Persist and restore your state machine
//...
var json = _myStateMachine.ToJson();
//send json to Couch or somewhere...
//later...
var myStateMachine = new StateMachine<BugState, TransitionActionStatus>("example",
_transitions,
initialState: new BugState.Open());
myStateMachine.InitializeFromJson(json);
//continue where you left off...
- Run
/build/build.bat
- Type in the desired option
- Hit return
This software is released under the GNU Lesser GPL. It is Copyright 2013, Ben Aston. I may be contacted at [email protected].
Pull requests including bug fixes, new features and improved test coverage are welcomed. Please do your best, where possible, to follow the style of code found in the existing codebase.