Defining Composite State in WF4 StateMachine

 

Earlier last week, Tim sent me a forum post about asking why State is sealed.  However, I searched around the web and did not find an answer for this.  It is strange because sealing (“shipped”) activity is almost like the first thing that I learned.  So I dug up some internal docs and found the answer:

42.

Sorry it is a cold joke; there is indeed a less cold reason for sealing activity thought.  It is really a philosophical decision: We expect that Composition is much better extension mechanism than inheritance.  However, there are also some very valid reason for not allowing State to be extensible.  For one, State is only an artifact that is relevant to StateMachine, so we want to make sure that only StateMachine can consume State.  Also, unlike other activity, State scheduling is tightly integrated with State (that’s why it’s not an Activity).  It would not make sense to allow State to be extensible, because StateMachine would not know how to schedule anything in State other than Entry and Exit.

(Aside of being philosophical, the one valid reason that I can think of where this might make sense, is when an ISV provides a set of out-of-box “composite” states to a StateMachine rehoster.  However, the ISV wants to “seal” the users from changing the State or hide them from the complex logic in this composite state.  In this case, making State “inheritable” would make sense - Microsoft can’t seal it because ISVs needs to seal it.  A little bit ironic, but nevertheless, this would be valid.)

The recommended approach, however, is still to use a composite activity and make it available to user via IActivityTemplateFactroy. There is a catch though.  The IActivityTemplateFactory.Create return an Activity instance.  Normally, that would not be a problem, since most control flow activities (i.e. Sequence, Flowchart) accept an activity as a part of its children activities.  However, StateMachine activity is composed by State, and State is not an Activity.  So you cannot really returns an Activity in IActivityTemplateFactory.

Even if you create an “empty” Activity in IActivityTemplateFactory, you would not be able to drop it into a StateMachine, because StateMachine designer would accept only State.

Does it mean that you cannot create an out-of-box composite State for our ISV customer? --–screaming!---

Well the answer is NO, so don’t panic yet.  What it means, is that you cannot drop a custom composite state from Toolbox to the StateMachine (and I guarantee you that it will be fixed in VS vNext).  However, you can still add a composite state via. the ModelItem level though.

Assume that you’re creating your own rehost, when you are initializing the StateMachine, cache the StateModel modelItem.

 private void AddDesigner()
{ 
    this.workflowDesigner = new WorkflowDesigner();
    Grid.SetColumn(this.workflowDesigner.View, 1);

    ActivityBuilder builder = new ActivityBuilder();
    builder.Implementation = new StateMachine(); // 
    this.workflowDesigner.Load(builder);
    ModelItem builderRoot = this.workflowDesigner.Context.Services.GetService<ModelService>().Root;
    
    // NOTE: you need cache the StateMachine modelItem for later use
    stateMachineModelItem = builderRoot.Properties["Implementation"].Value;
}

Then we would add a button to the rehost.  The button is used for adding the composition state to the cached StateMachine.

 private void Button_Click(object sender, RoutedEventArgs e)
{
    stateMachineModelItem.Properties["InitialState"].SetValue(null);

    State s = new State()
        {
            DisplayName = "Custom State",
            Entry = new WriteLine()
            {
                    Text = new InArgument<string>("Hello world")
            }
        };
    stateMachineModelItem.Properties["States"].Collection.Add(s);
    stateMachineModelItem.Properties["InitialState"].SetValue(s);
}

In this case if you press the button, you can see the “custom state” in the rehost.

image

Let me know if it helps Smile