Mike Taulty's Blog
Bits and Bytes from Microsoft UK
|
Sign in
| Join
| Help
|
I sat in on a workshop on WF yesterday that David was running.
One of the interesting discussions that came up was around the "CallExternalMethod"/"HandleExternalEvent" Activities.
The idea of these Activities is that you can build a Workflow which makes a call out to the "outside world" using the "CallExternalMethod" Activity;
You can also have a Workflow wait for something to happen in the "outside world" by using the "HandleExternalEvent" Activity;
At build time with these Activities the only thing that the Workflow needs knowledge of is the definition of a .NET interface that provides methods that it (the Workflow Instance) can call and events that it (the Workflow Instance) can handle to receive data.
This means that the Workflow definition is very much abstracted from the means by which it sends information "out" and the means by which it receives notifications "in".
I made a few videos on this stuff which you can find up at wf.netfx3.com under the screencasts section. Specifically,
MSDN UK WF Nuggets - 10 - Communications from the Workflow to the Host
MSDN UK WF Nuggets - 11 - Communications from the Host to the Workflow
MSDN UK WF Nuggets - 12 - Bidirectional Communication (Host Workflow)
However. If you were, for instance, trying to build a Workflow that waited for something and that something happens to be a call into an ASMX web service then there's already an Activity that does this;
And there's also an Activity that will call a web service for you.
So, clearly, there's an idea here that sometimes you would use the general communication mechanism to talk between the Workflow Instance and "the outside world" but there are other times when a specific Activity is preferable as in the ASMX web services case.
In David's workshop, the question that we probably spent the most time on was "How do I decide when I use this Host/Workflow generic mechanism and when to use or build a specific Activity to achieve what I want".
It's an interesting question :-) Firstly, I think when it comes right down to it whichever path you take, the same core mechanism will underpin it (using queues that communicate with a Workflow instance) but you're still left with a choice of whether to go with a very generic mechanism or whether to build a specific Activity.
Imagine you need to do something like;
In each case, what do you do? Do you build a specific Activity that does what you want or do you use the general purpose "CallExternalMethod" Activity?
Here's what I think are some of the trade-offs between the two. I'm sure there's a lot more.
Build a Specific Activity
- Your activity is easily re-usable across different Workflows. An MSMQActivity is a useful thing to have.
- Your activity is easier for the Workflow builder to use. It has a clear purpose and inputs/outputs that line up with that purpose.
- If you need to be event-driven then you need to build a service to plug in to the runtime that will deliver the right events to your Activity. This means some code to support your Activity. Most of the code you write then goes into writing your Activity and its service.
Use the General "CallExternalMethod" Mechanism
- You can re-host the Workflow in different hosting environments and implement its communication in different ways each time. That is, you're not tied to a specific implementation of "communication".
- You can make dynamic choices in the host as to how the communication is implemented (i.e. have some fancy switch statement that chooses depending on the day of the week :-))
- You do not need to write a service to plug in to the runtime - the ExternalDataService bits already do that for you. Most of the code you write goes into the host.
So it seems that we have the Activity-centric way of doing things and the Host-centric way of doing things and I don't think it's simple enough that there's a "right" answer and each time you make this decision you'll have to evaluate against the pros/cons of each approach and decide what to do.
I thought I'd try and illustrate this with a simple scenario. Say I want to build a Windows Forms application and I want to have a Workflow that traps key presses and draws shapes on the screen.
Implementing that with "CallExternalMethod" looks something like this;
and it was pretty easy to implement once the definition of the interface that sits between the Workflow and the Host was established as;
public enum Shape { Circle, Square, Triangle } [Serializable] public class KeyPressEventArgs : ExternalDataEventArgs { public KeyPressEventArgs(Guid g) : base(g) { this.WaitForIdle = true; } public KeyPressEventArgs(Guid g, char k) : base(g) { this.key = k; this.WaitForIdle = true; } public char Key { get { return key; } set { key = value; } } private char key; } [ExternalDataExchange] public interface IUICommunication { void DrawShape(Shape s); event EventHandler<KeyPressEventArgs> KeyPress; }
Here's the project file that does it - when the app's running you can press 's', 'c' or 't' and you'll get squares, circles and triangles on the screen. Note that I don't bother to keep a list of what's on the screen so if you invalidate the window your shapes will disappear.
What about doing this the alternative way with custom Activities? It's not too hard to build a DrawShapeActivity although there is the question of how it gets hold of a Graphics object to draw with. In terms of building a WaitForKeyPressActivity it's a bit trickier because it needs to be asynchronous because it could run for an indefinite period of time.
So, the WaitForKeyPressActivity needs a runtime service to support it and it's probably best if the DrawShapeActivity also uses some kind of service to get hold of a Graphics object to draw with.
Here's the project file that works this way - it should run the same way as the other one and I didn't spend too long on it but I'd say it was slightly harder than the other way of working. The Workflow ends up looking very similar;
but those two white boxes are custom Activities now rather than something that ships in the box. I also have a UIService class which helps those 2 Activities do what they need to do (i.e. draw to screen and draw shapes).
Going back to the list of 5 examples earlier - here's my thoughts on what I'd probably do unless I needed to be able to specifically achieve the following without needing to re-host the Workflow with different mechanisns;
but, as I say, to me it's not a question of right or wrong.