Jon Flanders, Pluralsight
May 2008
Windows Workflow Foundation (WF) is a programming model, set
of tools, and runtime environment which allows you to write declarative and
reactive programs on the Windows platform. WF is part of the .NET Runtime, and first
appeared in .NET 3.0.
Windows Communication Foundation (WCF) is also a programming
model, set of tools, and a runtime that first appeared in .NET 3.0. It is a
framework for building applications that can communicate with each other over
varied network protocols. It centers on the idea of building services as
loosely coupled endpoints to add functionality and value to your applications.
Although WCF and WF both shipped in .NET 3.0, in their
initial releases there wasn’t an out-of-the-box way to integrate the two
technologies. There weren’t any built-in Activities in WF to communicate using
WCF, and there weren’t any facilities built into either WCF or WF to allow WF
workflows to easily implement WCF services. With the release of .NET 3.5 and
Visual Studio 2008 however, the runtimes have been melded together to allow
easy integration, including allowing WF instances to use WCF to communicate to
remote endpoints, as well as to allowing WF instances to become the service
implementation for WCF endpoints. This is accomplished by two new Activities:
ReceiveActivity and SendActivity, as well as a new hosting infrastructure for
service endpoints. In this article I’ll look at both sides of this integration
to give you an overview of how to use it in your WF/WCF applications.
Communicating using WCF
The new .NET 3.5 SendActivity enables a workflow instance to
easily communicate with a service endpoint using WCF. It basically puts the
workflow instance in the role of being a WCF client.
The way to use the SendActivity is similar to the way WCF is
used from any .NET application. The first step is to use the “Add Service
Reference” functionality built into Visual Studio 2008 to generate a client
proxy and configuration (this could also be done with the WCF svcutil.exe
utility). Once the proxy and configuration are added to the workflow project, the
SendActivity may be configured to use the particular endpoint, and at runtime,
the SendActivity will use WCF to communicate with the endpoint.
In Visual Studio 2008, when in a workflow project (for this
example I created a new “Sequential Workflow Console Application,” but any
project type may be used), right click on the project node, and select “Add
Service Reference”. See Figure 1.
.jpg)
Figure 1: Add Service
Reference context menu
This will cause the “Add Service Reference” dialog to appear.
In this dialog, I put in the address of the service metadata I want to use to
generate my proxy and configuration. See Figure 2.
.jpg)
Figure 2: Add Service
Reference dialog
In this case, I’m adding a reference to a service that
implements a contract named IService, which has one operation – Add, a simple
calculator service (using a calculator as an example of a service is required
by the Web Service Developers Union).
Once I press OK, the “Add Service Reference” functionality
in Visual Studio will add a code-generated proxy class and it adds to (or
creates) the app.config file with the appropriate endpoint configuration. Again,
this functionality is the exact same functionality as when we use “Add Service
Reference” in any other project type inside of Visual Studio 2008.
Next, from the workflow designer view of my workflow, I’m
going to drag and drop the SendActivity from the toolbox onto my design surface.
The SendActivity is under the “Windows Workflow v3.5” tab group in the toolbox.
See Figure 3.
.jpg)
Figure 3:
SendActivity in the toolbox.
Once I drop the SendActivity onto my design surface, I have
to configure it. I can do this by double-clicking on the SendActivity itself in
the designer, which will bring up the “Choose Operation” dialog. The first
thing I need to do here is to click the Import button, which will show me the
list of Service Contract types that are available in my project.
.jpg)
Figure 4: The Choose
Operation dialog
Once I’ve associated an operation with my SendActivity, I
need to do at least two additional things to configure it properly to use WCF
to communicate with the remote endpoint: Bind the parameters, and configure the
Channel.
Binding parameters to the SendActivity is just like Activity
data-binding in general, there isn’t anything new or different for the
SendActivity. In this particular case, I’ve bound the three parameters (the two
input parameters and the one output parameter) to fields on my workflow
instance using the Activity bind dialog. You can see the final property
configuration in Figure 5.
.jpg)
Figure 5: Finished
Activity binding
Next, I am going to configure the ChannelToken property of
the SendActivity. The ChannelToken property (which is also the name of the
property’s Type) is a new type for .NET 3.5 that represents all the information
that the SendActivity needs in order to use the WCF infrastructure to send a
message. You can see my configuration in Figure 6.
.jpg)
Figure 6:
SendActivity configuration
The first thing I set is the Name property of the
ChannelToken, which I did by typing the string “MyToken” in the first property
grid box (the one to the right of the ChannelToken entry in the property list).
This is an arbitrary string which is used to identify the channel. If the
OwnerActivityName property is also set (it is optional), it is also used to
identify and scope the channel. This is useful in looping Activity scenarios
like using the ReplicatorActivity, so that every one of its iterations causes a
new channel to be created.
More than one SendActivity can use the same ChannelToken by
using the same values for Name and OwnerActivityName. This enables the scenario
wherein you want more than one SendActivity instance to use the same channel,
rather than each instance to create and open and close its own channel. Which
you choose will depend on the service you are communicating with and its
requirements, as well as how you want the workflow to manage things like
sessions. For example, in a scenario where you were using an endpoint that
supported WS-ReliableMessaging, you could use the same ChannelToken for
multiple SendActivity components, which would allow the messages sent by all the
activities to be part of the same session. In code terms, you can think of the
ChannelToken as representing a particular object reference (a reference to the
channel object, to be precise). When you are using code to call services,
sometimes you want to use the same channel object, and sometimes you want to
use a new channel object per call (note that in most projects using WCF, the
channel object is wrapped by the client proxy object, so you can use the same
analogy with the client proxy object).
The last piece of configuration the ChannelToken needs is
the name of the endpoint. This is again used to identify the channel, but this
also sets the default way for the SendActivity to determine the address and
binding for the channel it will use to communicate with the remote service.
The SendActivity will ask the .NET configuration system for
a configured endpoint with that particular name. This can easily be retrieved
by looking at the app.config file generated (or modified) when I executed “Add Service
Reference”. In this case, the name of the client endpoint in my configuration
file is “WSHttpBinding_IService,” which is the common default used by WCF for
endpoint names, the name of the binding combined with the name of the contract.
You can change this of course, but if you do, remember to change your
SendActivity configuration.
At this point, I have everything set up to work except the actual
sending of values to the service and doing something with the return. For this
simple example, I added an EventHandler for the BeforeSend event of the
SendActivity and added some code to fill in some values for the input
parameters. Then I added a CodeActivity, and in its EventHandler, I’m writing
the value returned from the service to the console. You can see the code in
Figure 7, and the output in Figure 8.
.jpg)
Figure 7: Workflow
code-behind
.jpg)
Figure 8: Execution
results
We’ve now covered the basics of configuring and using the SendActivity.
The nice thing about it is that it follows the same programming model as WCF,
but brings that model into WF in a very explicit and visible way.
ChannelManagerService
Many WCF applications use configuration files to configure
their endpoints, and both client and service endpoints can be configured this
way. As I stated before, the SendActivity finds its configuration by asking the
.NET configuration system for a named client endpoint. However, sometimes the configuration
file system is too limited for the task at hand, or may not even be accessible
(depending on where your workflow is being hosted).
One simple thing you can do with the SendActivity is
override the endpoint address found in the configuration file. The
CustomAddress property on SendActivity allows you to change the address from
the current configured address.
But what if you want to use code to create your client
endpoints and not rely on the configuration system? This may be necessary if
your workflow can’t access the configuration file (or easily change it), or
when you have endpoints that you can’t easily configure, or if you just want to
use code to avoid configuration file changes from breaking your application. This
is where the ChannelManagerService can be used.
The ChannelManagerService is a WorkflowRuntime-level service
that will be used by SendActivity instances to retrieve channels. Since the
ChannelManagerService is used only if configured, if a ChannelManagerService
isn’t registered with the WorkflowRuntime, SendActivity will continue to
execute without errors. You can rest assured that the SendActivity
infrastructure still caches channels regardless.
The purpose of the ChannelManagerService is to allow the
code in the WF host application (the code that creates the WorkflowRuntime object)
to pre-cache endpoints which can then be used by the SendActivity when it
executes, instead of the SendActivity having to go to the configuration file to
get the endpoint information.
Back to my example, I can add a ChannelManagerService to my
WorkflowRuntime (in the program.cs file in this particular project), and
pre-configure the endpoints I want to use. You can see this code in Figure 9.
.jpg)
Figure 9:
ChannelManagerService code
I can re-run my workflow, and I get the same result, even if
I remove the client endpoint configuration from the app.config file. I found it
pretty interesting the first time I did this and I didn’t change the
SendActivity configuration – the EndpointName was still
“WSHttpBinding_IService” and that still magically worked. Turns out the
ServiceEndpoint object also chooses the convention of
“{BindingName}_{ContractName}” as the value of the name property, which is why
this works without any changes to the workflow.
I could set the name of the ServiceEndpoint explicitly in
the ChannelManagerService code, in which case I’d also have to configure my
SendActivity with the new name. This is probably a good practice to avoid any
potential name conflicts that could occur.
Implementing a WCF service using WF
The other Activity added in .NET 3.5 is the ReceiveActivity.
Where SendActivity is essentially a WCF client, ReceiveActivity is the
equivalent of a WCF service, or at least part of a service.
The basic idea is that ReceiveActivity enables you to use a
workflow instance as the implementation of a service. Let’s take a step back
from WF and think about how WCF works on the service-side.
WCF is many things, but one of the things WCF does is allow
developers on the .NET platform to implement services. The dispatching layer on
the WCF service-side allows WCF to listen for incoming messages using some
network protocol (TCP, Named Pipes, HTTP, etc.) and route those messages to
.NET objects and methods on those .NET objects.
The typical way we do this with WCF is to start with an
interface marked with a special attribute that becomes the definition of our
service. For example, to build the service I was calling in the first part of
this article, I created an interface for the IService service. You can see this
in Figure 10.
.jpg)
Figure 10: Contract
definition
I can then implement this interface using a concrete class. After
the implementation, I need to hook the class up to the WCF infrastructure, and
there are a couple of ways to do that. In the sample code associated with this
article, I’m using the hosting infrastructure for WCF that integrates with
Internet Information Service (IIS), which uses .svc files as the way of linking
a particular endpoint address with a particular class and contract. In Figure
11, you can see the class that I created to implement the Add functionality and
the method that gets called every time a client calls the Add operation on the
service.
.jpg)
Figure 11: Add method
This is a pretty high-level overview of how WCF dispatches
messages; refer to the WCF documentation for more detail. The main point to
understand is that the WCF dispatching layer I’m describing is totally extensible.
The basic functionality built into WCF in .NET 3.0 was to be able to route
incoming messages to methods on .NET classes. In .NET 3.5, the ReceiveActivity
makes it possible to easily route incoming messages to workflow instances. Workflows
become our service implementations.
To start with, I’m going to implement the IService contract
using WF and the ReceiveActivity, and then I’ll progress into a more natural
application of workflow as a service, where the workflow is a long-running
process (not a short-lived process like adding two numbers).
For this workflow-as-a-service implementation, I’m going to
pick the SequentialWorkflow model as the composite Activity to implement my
service. To create my service, I’m going to drag the ReceiveActivity onto the
design surface of the SequentialWorkflow, and then configure it. I’ll configure
the ReceiveActivity’s ServiceOperationInfo the same way as I configured the
SendActivity: I’ll just double click on the ReceiveActivity and pick the
contract/operation I want this Activity to represent, which in this case is
IService.Add.
I can then bind both the input and return parameters to
variables. In this case, I’ve created a simple custom Activity named
AddActivity to actually perform the logic of my operation. The last important
property left for me to set on the ReceiveActivity is the CanCreateInstance
property. This property is set to false by default, but in my case I want to
set it to true, so that whenever a new message comes into the WCF messaging
layer for the Add operation, the new WCF/WF 3.5 hosting infrastructure will create
a new workflow instance and pass the incoming message data into the workflow. You
can see my configured workflow in Figure 12.
.jpg)
Figure 12:
ReceiveActivity configured.
Now I can take the assembly that contains this workflow and
deploy it as a service. WCF has a very flexible hosting model; in this case,
I’m deploying to a Web Application using the file-based Web Project in Visual
Studio 2008, and for production, this would get deployed to IIS.
For IIS deployment, I need two things (besides having my
assembly in the bin directory): a svc file with the right information, and a
configuration entry for my service. When a service is implemented by a .NET
type, this svc file and configuration look like the entries in Figure 13.
.jpg)
Figure 13: .NET type-based
svc and configuration
The WCF hosting infrastructure inside of IIS makes a connection
between the value of the Service attribute in the svc file and the name
attribute in the service element inside of the System.ServiceModel
configuration element, and uses the setting found there to host the endpoint
(the address is blank because the address is already set by the host/virtual
directory and svc file).
To create the hosting infrastructure necessary for hosting
my workflow as a service, I need to modify the svc file in an interesting way. The
value of the Service attribute is still linked to a type name, but in this case
the type refers to the workflow type, and I need to add a new attribute:
Factory. The Factory attribute must point to a type that derives from
ServiceHostFactoryBase. In the .NET hosting case, the default ServiceHostFactory
type is used implicitly, and it creates a ServiceHost which actually hosts the
service. For the workflow hosting case, I need to specify the
WorkflowServiceHostFactory. This is the key to allow workflows to implement
services. Instead of the typical ServiceHost, the WorkflowServiceHostFactory
creates a WorkflowServiceHost, which takes the messages coming in from the WCF
channel layer and either creates a workflow instance (when the
ReceiveActivity.CanCreateInstance is true) or sends the data to an existing
workflow instance (more on this later). You can see the svc and configuration
for my workflow service in Figure 14.
.jpg)
Figure 14: Workflow
as a service svc and configuration files.
I can now invoke this service using my workflow that uses
the SendActivity by changing its CustomAddress property (this works because the
contract is exactly the same between the two services).
Sessions and Context
To get my simple workflow service to work, I actually had to
make a change to my service contract definition. I waited until now to discuss
it so I could give it the full discussion it deserves. In Figure 15, you can
see both the IService service contract and the IAccumService service contract –
the new interface I am adding to build a long-running workflow service.
.jpg)
Figure 15: IService
and IAccumService service contracts
The arrows are pointing to the ServiceContract attribute,
specifically the SessionMode property. If I don’t set this value to SessionMode.NotAllowed
on the IService definition, the endpoint throws an exception when loaded. You
can see this in Figure 16.
.jpg)
Figure 16: Session
error with workflow services
The error is referencing two related concepts: Session and
context. In this case, I do want to set the SessionMode to NotAllowed, because
I am not trying to create a long-running session-full service. The reason that
this error is happening at all is because the SessionMode default is
SessionMode.Allowed, which means there may be a session associated with this
endpoint when run. Because the binding (WsHttpBinding) doesn’t support the
concept known as “Context”, if the runtime allowed this to happen, the
execution result would be incorrect.
You can think about “Context” as providing “Session”
capabilities for workflow services. The concept of “Session” is a way to
associate a particular client with a particular piece of shared state on the
server. In this case the piece of shared data is the workflow instance.
Think about a simple abstract example where we want the
first message from a client to start a workflow, and the next message from that
same client to be routed to the same workflow instance. Abstractly, this
concept is known as correlation. We need to correlate the second message to the
workflow instance. With workflow services, this is the concept of “Context”.
The way workflow services solve this problem is by sending “context”
data back to the client on the first message, which is the GUID that uniquely
represents the workflow instance. When the client makes a call back to the
service, it presents this “context” data, enabling the workflow services
infrastructure to route the incoming message to the right workflow instance. Although
there isn’t explicit mechanism built into workflow services for multi-user
workflows, the GUID can be passed out-of-band to a different client, and the
workflow services infrastructure will be fine. Out-of-the box .NET 3.5 includes
three new bindings that support context by including the new context channel as
part of the binding configuration. The list of these bindings is in Table 1.
Binding | Description |
WsHttpContextBinding | A binding that supports the WS-* protocols as well
as context. Context can be sent via custom Soap Headers (the default) or HttpCookies. |
BasicHttpContextBinding | A binding that supports the base WS-I web services
profile. Context sent via HttpCookies. |
NetTcpContextBinding | A binding that supports the NetTcp protocol. Context
is sent via custom Soap Headers. |
Table 1: Context bindings
For my particular scenario, I picked the
WsHttpContextBinding to configure my workflow endpoint. See Figure 17.
.jpg)
Figure 17: Context
binding configuration
The workflow I modeled is using a StateMachineWorkflow. The
possible execution path of this service is that the client will first call the
Start operation with the initial value to accumulate. It can then call the Add
operation n times, and then finally call the Result operation to get the
accumulated value. To model this interaction, I created three states, each one
with an EventDrivenActivity that allows my workflow to listen for a particular
operation. I also used the concept of recursive states, so that I can listen
for the Result operation while the workflow also listens for the Add operation.
See Figure 18.
.jpg)
Figure 18:
AccumWorkflow – StateMachineWorkflow
Inside of each EventDrivenActivity is a ReceiveActivity,
configured for the particular operation. See Figure 19 for the Start operation
(which has the ReceiveActivity with the CanCreateInstance set to true).
.jpg)
Figure 19: Start
operation EventDrivenActivity
For the client, I also use the WsHttpContextBinding and,
using the same proxy object, the channel layer in the client takes care of
receiving, storing, and passing the context information back to the service on
each call. Again, the point of using the context is to allow the same workflow
instance to receive multiple messages after the first message activated the
workflow instance.
Conversation Context
Another piece of functionality supported in .NET 3.5 is the
concept of conversations. You can think of conversations as extending the
concept of instance context to the next level. It allows you to create
“conversations” with other endpoints, which will enable you to correlate multiple
messages that use the same operation to the same workflow instance. See the
conversations sample in the SDK for more information.
Summary
WF provides a programming model, set of tools, and runtime
environment which allows you to easily write declarative and reactive programs.
WCF provides a programming model, set of tools, and runtime environment which
allows you to write programs that communicate with each other over various
network and application protocols.
.NET 3.5 includes a layer known as workflow services, which integrates
these two frameworks to allow easy access to the WCF framework from within WF. This
layer allows you to use the SendActivity to call endpoints from within a
workflow. The ReceiveActivity enables you to take a workflow, and stand it up
as a Service, callable by any service client.
About the author
Jon
Flanders is an independent consultant, speaker, and trainer for Pluralsight. He
specializes in BizTalk Server, Windows Workflow Foundation, and Windows
Communication Foundation. You can read his blog at http://www.masteringbiztalk.com/.