Public and Private in Depth (“Custom ‘Activity Sequences’ with Private Implementation” Interlude)


[In this interlude, we’re spiritually continuing the Custom ‘Activity Sequences’ With Private Implementation series, but without referring to any code from the original problem.

Read Part 1 for the introduction. Or guess from this summary – we’re looking at how we can build custom activities compositionally in code, inside of NativeActivity, so we are seeing everything there is to see. By the end of Part 2 we ended up with some code for an activity, NativeHandleRequest. And the idea that we want to be using implementation scoping to hide all the implementation details of this activity from the users of the activity, who should just get to see the public surface area. But still we were working with a fairly fuzzy idea of what implementation scope means. Let’s try to drill down into that.]

Public, and implementation. Where do we begin?

When you create a new NativeActivity class it is empty. Your activity has no children, no variables, no arguments, and all-up it has no public surface area that anyone could observe. Also, it has no implementation logic, just a blank Execute() method. Easy.

As soon as you start adding public properties, everything above becomes false.

For example, add a property:

public Activity Body { get; set; }

As soon as we’ve done this, the behavior of base.CacheMetadata() [NativeActivity.CacheMetadata()] kicks in. We now have a public child. There’s some similar default behavior for autodeclaring public Variables and Arguments. All basic stuff.

Next we probably start writing our implementation logic inside of Execute(), which is when we have to start dealing with the rules.

The Rules

  1. Public variables declared on your activity are in public land.
  2. Implementation variables on your activity are in implementation land.
  3. Implementation variables and public variables can never reference each other in expressions, since they can’t see into the other foreign land.
  4. Arguments on your activity bridge the gap. When your argument is initialized with an expression, that expression can refer to variables visible in public land. (But those will probably be variables from elsewhere in the workflow tree – not your own variable.) Consuming the value of an argument, however, is only possible in implementation land.
  5. Inside of Execute(), you are in implementation land.
  6. All of your public child activities live in public land. All of your implementation child activities live in implementation land. Which land they are determines which variables are in scope for those activities.

 

Practically what does this mean?

You cannot access your own public variables directly Execute(), using Variable<>.Get()/Set().
You can access your own implementation variables directly in Execute(). Your activity is the only activity that sees these variables as implementation variables. To everyone else they either appear not to exist, or they appear in the scope.

Public child activities can refer to your public variables via expressions.
Implementation child activities can refer to your implementation variables via expressions.

 

One more interesting case – ActivityDelegates

ActivityDelegate, ActivityAction, and ActivityFunc are super-interesting, because they allow you to mix things up, and solve some problems which could otherwise be… unsolvable?

Notice that with all the above rules there appears to be no way to implement ForEach activity with just a straightforward child activity Body. I mean, how would you do it?

Attempt 1: Body as a public child.

Correct point: Inside Body you can refer to variables throughout the scope of your workflow.
Problem point: Inside Body you cannot get data from anywhere which is accessible to Execute(). The only thing you can see from ForEach is public variables, and they aren’t accessible from Execute(), and so there is no way for Execute() to pass the loop iterator variable to Body.

Attempt 2: Body as an implementation child.

Correct point: We can set an implementation variable in Execute(), and Body can retrieve its value, since it becomes visible in the public scope of Body.
Problem point: We can’t directly refer to any variables anywhere else in the workflow. We could refer to them indirectly by having them passed to Body as InArguments and OutArguments. But this would require us to know exactly what Body is going to do in advance.

The ActivityDelegate loophole solves this problem. We can pass data into the Arguments of any ActivityDelegates we own, inside Execute(), like this (Body is an ActivityAction<T>):

activityContext.ScheduleAction<T>(this.Body, valueToPass, OnDelegateCompleted);

 

In this case Body is declared in CacheMetadata() as a public ActivityDelegate child. And the actual loop body is a public Activity child of Body: Body.Handler. This is how every variable in the public scope of ForEach remains visible to the child.

To summarize the loophole:

ActivityDelegates are the way to cross the implementation land –> public land gap in the opposite direction to regular activity arguments.

 

(Ugly) pictorial representation:

image

That’s all for this post, and hopefully soon we can conclude the series.

Comments (5)

  1. Notre says:

    Hi Tim,

    I like the idea of a group of activities with a private implementation.  What I'd be most interested in is doing is something like this, but in a Flowchart model.  In particular, I would like to string together a custom activity with a FlowSwitch or FlowDecision.  The problem that I can't get over is how to do this from the designer perspective, given that the FlowSwitch and FlowDecision are the only node types that allow more than one ouput path (and the whole reason I use FlowSwitch and FlowDecision to begin with).

    Could the principles of this blog mini-series be applied to the scenario I just described, somehow?

    Thanks!

  2. Quintonn says:

    From your code I was able to solve a similar problem, thank you SOOOOO much

  3. tilovell09 says:

    Hi Notre,

    I guess you are asking either for a way to take an activity, which generates a value, on the flowchart, and a flowswitch or flow decision, from the same flowchart, and merge them into one blob – from the point of view of someone viewing the flowchart.

    I don't think the approach in this blog will help you achieve that, it is really only applicable to activities and FlowSwitch and FlowDecision are not activities.

    One thing that could resemble what you are asking in terms of appearance/behavior would be to embedding the value-generating activity inside of the expression in the FlowChart/FlowDecision – you can do it from xaml, but unfortunately there is no way to do this using the built-in expression editor UI.

    Tim

  4. BruceS@cornerstone-tech.com says:

    Tim,

    I have custom activity that schedules a Receive / SendReply activities along with other work that happens prior to and after the Recieve is executed.  I can't seem to get it to respect the WF instance correlation created by a built-in Receive activity that created the correlation handle for the WF instance.  I'm getting the error:

    The execution of an InstancePersistenceCommand was interrupted because the instance key '12046dfc-8594-015f-430a-921927710e48' was not associated to an instance. This can occur because the instance or key has been cleaned up, or because the key is invalid. The key may be invalid if the message it was generated from was sent at the wrong time or contained incorrect correlation data.

    Looking at your example, I see you're creating a WF instance in your Receive activity whereas I'm trying to use an existing one that is saved as WF instance variable scoped to the WF service.  My Receive accepts parameters content that passes in the WF instance correlation id used in the instance correlation handle.  Can you help me with how to setup the Receive CorrelatesWith and CorrelatesOn properties?

    Thanks.

  5. louisila says:

    I can't seem to find part 2..?..  Is the link broken?

Skip to main content