다음을 통해 공유


Working with ModelItem inside IActivityTemplateFactory in PU1

 

Note: this one I meant to talk about this for a long time. After this, I actually ran out of immediate topics for WF. So any suggestions are welcomed.

Back in July, we received a customer request that IActivityTemplateFactory does not work anymore after he installed .Microsoft .NET Framework 4 Platform Update 1 (KB2478063).  It was the first issue we saw after the Platform Update shipped, and I was more nervous than others, because I was the primary dev for the WF activity/designer at that time and fixed the majority of the bugs.  I wasn’t sure if I just introduced a recall-scale bug.  I was on the servicing loop at the time so after some initial investigation, I was tasked to find out the root cause.

In the customer’s scenario, he has a rehoster with a Flowchart as a root.  The rehost app has a Flowchart as the root activity.  He also created an IActivitytemplateFactory which would place two connected flownodes in the FlowchartDesigner:

    1: Activity IActivityTemplateFactory.Create(DependencyObject target)
    2: {
    3:   ActivityDesigner designer = target as ActivityDesigner;
    4:   FlowStep step = new FlowStep();
    5:   Assign assign = new Assign();
    6:   step.Action = assign;
    7:   FlowDecision decision = new FlowDecision();
    8:   step.Next = decision;
    9:   designer.ModelItem.Properties["Nodes"].Collection.Add(step);
   10:   designer.ModelItem.Properties["Nodes"].Collection.Add(decision);
   11:   ModelItem decisionItem = designer.ModelItem.Properties["Nodes"].Collection[1];
   12:   ModelItem stepItem = designer.ModelItem.Properties["Nodes"].Collection[0];
   13:   return null;
   14: }

So far so good right?  And you wouldn’t expect any problem when you drag this Factory to the Flowchart right?  Try this on your own!

If you have PU1, you would discover that the drag operation would throw an ugly ArgumentOutOfRangeException at line 3:

    1: designer.ModelItem.Properties["Nodes"].Collection.Add(step);
    2: designer.ModelItem.Properties["Nodes"].Collection.Add(decision);
    3: ModelItem decisionItem = designer.ModelItem.Properties["Nodes"].Collection[1];

This was surprising – you would expect the “1” index would return the FlowDecision ModelItem but it throws an Exception instead.  Then we tried to look at the Collection.Count at Line 3 and we discovered that FlowDecision node was not inserted:

    1: designer.ModelItem.Properties["Nodes"].Collection.Add(step);
    2: designer.ModelItem.Properties["Nodes"].Collection.Add(decision);
    3: Debug.Assert(designer.ModelItem.Properties["Nodes"].Collection.Count == 2);

Guess what the Collection.Count is: 2? 1? No… it’s ZERO!  So it appeared as if nothing was inserted into the collection!  As we debugged the return value of Collection.Add, it was not null and was a ModelItem that wrapped around the FlowNode that was inserted to the FlowchartDesigner.  As this moment, I realized that the it was actually caused by a fix we did for PU1.  But before that, let’s give you some background.

The ModelItem object model provides an API which merges multiple ModelItem operations into a single unit of work – you can consider EditingScope as a transaction, where all the changes are not committed until you declare EditingScope.Complete().  Inside an EditingScope though, any ModelItem operation would not change the state of the object model, until Complete is called.

However, if you try to drag this composite FlowNodes to a Dev10 RTM Flowchart and then undo, you will observe that only the FlowDecision is undone, but the Assign remains in the Flowchart.  That’s because the Drag operation is not wrapped inside an EditingScope.  So each ModelItem operation is independent.  Even though it appeared as if you dragged one item to the Flowchart, there were multiple operations (what we called UndoUnit).

Normally, this is not a blocking issue.  However, we found another problem later – if you drag an Activityto the edge of a Flowchart, this drop operation would automatically expand the Flowchart to be big enough to contain the dropped Activity.  Now, if you try to undo it, you will realize that the drag operation is undone, but Flowchart still has the expanded size.  This problem eventually became more annoying with StateMachine, and we eventually decided to fix it by wrapping the drap-drop operation inside an EditingScope.

So far so good… now there is only one UndoUnit for drag-drop operation and the size would be restored properly.  What we didn’t realize was that there were customers who had dependency on ModelItem state, and this fix would break their rehost app.

Wrapping the drag-drop operation into a single EditingScope is still the right thing to do.  And if you are a WF ModelItem developer, you might already see that the customer’s code is not the only way to solve the problem.  Eventually, we recommended the customer to workaround this problem by not depending on Count of Indexes on the collection, but operate on the return value of the Add operation instead:

    1: ModelItem decisionMI = designer.ModelItem.Properties["Nodes"].Collection.Add(decision);
    2: ModelItem stepMI = designer.ModelItem.Properties["Nodes"].Collection.Add(step);

And a single advisory for all WF designer developer:

In IActivityTemplateFactory, do not rely any property or index values of any ModelItemCollection or ModelitemDictionary, because the caller might be in an EditingScope.

However, this behavior is unacceptable, and in Dev11 we have enough new WF functionalities such that the drag-drop can still be in ONE EditingScope, but all the Add/Remove would change the state of the Collection!  Isn’t that wonderful?  A very smart dev called Tony Tang has completed this work and you will see this in Dev11!

Anyway, it is a small fix, but it was interesting (I think) nevertheless, so I wanted to write this story down.

Now, I really need to think hard what I should write next…