Contents
- Microsoft Windows Communication Foundation (WCF) and Windows Workflow Foundation (WF) Technologies
- No custom activity to receive an event message
- Based on mapping the interface contracts between the WCF and
Workflow
technologies - Transaction support
- Declaratively programming
- No requirement to build a Local Service
- Null
Workflow
Local Service for correlated events - Null WCF Service
- Capability of pre/post processing in the operation invoker
- Working side by side with a
Workflow
Local Service (ExternalDataExchangeService
)
The Microsoft WF Technology makes it possible to receive external (host) events via a tightly coupled Local Service layer. This layer simplifies the connectivity between the workflow
and the host application based on the delegate/event pattern. All this magic plumbing is hidden by ExternalDataExchangeService
class that intercepts a Local Service and generates an event message to the specific Workflow
queue.
In order to receive an event message by event driven Activity, the ExternalDataExchange
interface contract must be declared with an application specific event handler.
The event delegate has a standard .NET signature shown in the following line:
public void EventHandler(object sender, ExternalDataEventArgs eventArgs);
where the eventArgs
represents a Workflow
event interest such as InstanceId, Identity, application specific data, etc.
The interface contract metadata is used to create a unique workflow
queue based on the interface type, event name and collection of the properties for correlation pattern as an additional option. Note that the workflow
queue messaging is the only way in which the host application can talk to the workflow
.
From the connectivity point of the view, the Workflow
represents an event sink and the WCF client's event source (subscriber interest). The following picture shows this logical pattern:
On the event source side, we can create many kinds of patterns (such as Remoting, WSE, WCF, etc.) to generate an event message and send it to the local or remote workflow
queue.
This article describes how this connectivity can be achieved using the WCF paradigm based on the interface contracts defined differently at each end side. You will see how event source can be logically (declaratively) connected with the event sink metadata.
The following picture shows a representation of the loosely coupled connectivity between the WCF event source and WF event sink:
Note, the Workflow
side doesn't need to have the Local Service layer in order to generate an event message. For this WCF/WF plumbing we need only metadata of the interface contracts and of course a generic decorator of the operation in order to make all magic work.
The service operation contract can be mapped to the specific WF interface contract explicitly using the WorkflowFireEventAttribute
. Behind this attribute is a plumbing code to generate an event message and that is sent to the workflow
queue.
Using the WCF connectivity to deliver an event message to the Workflow
event sink (such as HandleExternalEventActivity
) enables building an application model with a capability of the WCF paradigm based on the Address, Binding and Contract. For example: the netMsmgBinding
binding for delivery event message in the async and disconnected manner or the webHttpBinding
binding to enable a connectivity for delivery an event message from the browser driven by AJAX/Json (Orcas CTP version).
Let's look at in detail, how the WCF/WF plumbing is done for dispatching an event message to the workflow
queue. I will assume that you are familiar with these technologies. In addition, I mentioned earlier, the Workflow
side doesn't use any custom activity for this plumbing, so it is a standard workflow definition (sequential or state machine) using a HandleExternalEventActivity
. Therefore I will focus on the plumbing concept and implementation.
The concept of the loosely coupled WCF/WF connectivity for sending a workflow
event message is based on the interceptor (WorkflowFireEventAttribute
) on the service operation contract, which will map it to the specific ExternalDataExchange
interface contract (see the above picture).
The WCF service is hosted in the same appDomain with a WorkflowRuntime
. Basically we need to create a Null
Service derived from our WCF Interface contract. The following is an example of the simple Null
service and Interface contracts:
[ServiceContract]
public interface IFireEventTest
{
[OperationContract(IsOneWay = true)]
void Approve(object sender, ExternalDataEventArgs e);
}
public class ServiceTest1 : IFireEventTest
{
[WorkflowFireEvent(EventType = typeof(IWorkflowSimpleEvents))]
public void Approve(object sender, ExternalDataEventArgs e) { }
}
[ExternalDataExchange]
public interface IWorkflowSimpleEvents
{
event EventHandler<ExternalDataEventArgs> Approve;
}
During the start up appDomain, the WorkflowFireEventAttribute
will insert a WorkflowFireEventOperationInvoker
to the OperationInvoker
to intercept a call before invoking a service method. The following code snippet shows a method, where it is going to dispatch a workflow
event message:
object IOperationInvoker.Invoke(object instance, object[] inputs,
out object[] outputs)
{
object retVal = null;
outputs = new object[0];
if (inputs.Length < 2 ||
!typeof(ExternalDataEventArgs).IsInstanceOfType(inputs[1]))
throw new InvalidOperationException(string.Format(" ... ");
object sender = inputs[0];
ExternalDataEventArgs eventArgs = inputs[1] as ExternalDataEventArgs;
if (Transaction.Current != null &&
Transaction.Current.TransactionInformation.Status ==
TransactionStatus.Active)
{
WorkflowInstance workflowInstance = null;
eventArgs.WaitForIdle = false;
#region action - transactional delivery to the workflow queue
using(TransactionScope ts =
new TransactionScope(TransactionScopeOption.RequiresNew))
{
if (this.Option == WorkflowFireEventOption.After)
{
retVal = this._innerOperationInvoker.Invoke(instance, inputs,
out outputs);
}
workflowInstance =
WorkflowHelper.DispatchEvent(this.EventType,this.EventName,
sender,eventArgs,true);
workflowInstance.Unload();
if (this.Option == WorkflowFireEventOption.Before)
{
retVal = this._innerOperationInvoker.Invoke(instance, inputs,
out outputs);
}
ts.Complete();
}
try
{
workflowInstance.Start();
}
catch (Exception ex)
{
Trace.WriteLine(ex.Message);
}
#endregion
}
else
{
#region action - non-transactional delivery to the workflow queue
if (this.Option == WorkflowFireEventOption.After)
{
retVal = this._innerOperationInvoker.Invoke(instance, inputs, out outputs);
}
WorkflowHelper.DispatchEvent(this.EventType, this.EventName,
sender, eventArgs);
if (this.Option == WorkflowFireEventOption.Before)
{
retVal = this._innerOperationInvoker.Invoke(instance, inputs, out outputs);
}
#endregion
}
return retVal;
}
Basically, the method is divided into 3 parts such as pre-processing, dispatching and post-processing calls based on the option and transaction context. The option can be useful for application specific logic, for example; creating a workflow
, populating eventArg
s properties, etc. The default option is None
. Other extensions in the call use additional method parameters behind the mandatory parameters such as inputs[0]
and inputs[1]
or outputs. These extensions are for application specific features.
Firing Event in the Transaction Context
Delivering an event message to the workflow
instance in the transactional manner is little bit different from other resources driven by transactional context. Presently, we don't have a transactional memory resource for workflow instance, therefore the ACID transaction has to be done indirectly via the SqlWorkflowPersistenceService
. The concept is based on sending the event message to the workflow
instance in the synchronously manner (WaitForIdle = false
) and dehydrate the workflow
within the transaction scope and then after the transaction has been committed, the workflow
is returned back to the memory (rehydrating). For this reason, we have to create a new transaction context to avoid a deadlock. Note, the pre/post processing run under the new transaction context and their logic can participate in the ACID transaction.
Let's continue with a DispatchEvent
call. This method is responsible for creating and starting a workflow
instance based on the eventArgs.InstanceId
value, and calling the helper SendEventMessage
method to generate a workflow
event message and sending it to the instance queue. The following is the code snippet for DispatchEvent
method:
public static WorkflowInstance DispatchEvent(Type interfaceType,
string eventName, object sender, ExternalDataEventArgs eventArgs)
{
if (interfaceType == null)
throw new ArgumentNullException("interfaceType");
if (string.IsNullOrEmpty(eventName))
throw new ArgumentException("eventName");
if (eventArgs == null)
throw new ArgumentNullException("eventArgs");
WFServiceHostExtension extension =
OperationContext.Current.Host.Extensions.Find<WFServiceHostExtension>();
WorkflowRuntime workflowRuntime = extension.WorkflowRuntime;
WorkflowInstance instance = workflowRuntime.GetWorkflow(eventArgs.InstanceId);
if (!bTransactional)
{
try
{
instance.Start();
}
catch (Exception ex)
{
Trace.WriteLine(ex.Message);
}
}
SendEventMessage(interfaceType, eventName, sender, eventArgs, instance);
return instance;
}
Note, it would be nice having a public WorkflowMessageEventHandler
class from the System.Workflow.Activities namespace, which would significantly simplify this implementation. I created one for this purpose - see the helpers.cs file, but it doesn't work within the transaction scope (I will figure out this issue and update it). For now, we can use a reflection to invoke a public method on the internal class as it is shown in the following code snippet:
private static void SendEventMessage(Type interfaceType, string eventName,
object sender, ExternalDataEventArgs eventArgs, WorkflowInstance instance)
{
Type type =
Type.GetType("System.Workflow.Activities.WorkflowMessageEventHandler, ...");
object[] args1 =
new object[3] {eventType, eventType.GetEvent(eventName), instance.WorkflowRuntime};
object handler = Activator.CreateInstance(type, bindingAttr1, null, args1, cultureInfo);
object args2 = new object[2] { sender, eventArgs };
type.InvokeMember("EventHandler", bindingAttr2, null, handler, args2);
}
WorkflowFireEventAttribute
This is a plumbing class to insert a custom invoker in the operation contract to perform dispatching an event message from the WCF into the WF paradigms in a loosely coupled manner.
The WorkflowFireEventAttribute
class can be initiated by the following properties:
EventType
- type of the Workflow
Interface attributed by ExternalDataExchange
(mandatory property) EventName
- name of the Workflow
event (default event name is the name of the operation contract) WorkflowFireEventOption
- option of the operation invoker to pre or post process service method
- None - no service method is invoking (default option)
- Before - the
workflow
event is fired before the service method is invoked - After - the
workflow
event is firing after the service method has been invoked
The usage of the above properties is shown in the following examples, where the IFireEventTest
interface is mapped to two Workflow
interfaces with the same event name:
[ServiceBehavior]
public class ServiceTest1 : IFireEventTest
{
[WorkflowFireEvent(EventType = typeof(IWorkflowSimpleEvents))]
public void Approve(object sender, ExternalDataEventArgs e) { }
[WorkflowFireEvent(EventType = typeof(IWorkflowEvents))]
public void Reject(object sender, MyEventArgs e) { }
[WorkflowFireEvent(EventName = "Reject", EventType =
typeof(IMyWorkflowEvents))]
public void MyReject(object sender, MyEventArgs e) { }
}
or as shown in the next sample, how we can handle dispatching an event message within the operation method or pre-processing a firing workflow
event:
[ServiceBehavior]
public class ServiceTest2 : IFireEventTest
{
public void Approve(object sender, ExternalDataEventArgs e,
string workflowType)
{
MyObject mo = e.WorkItem as MyObject;
Console.WriteLine("Operation Approve - {0}", mo.Name);
WorkflowHelper.DispatchEvent(typeof(IWorkflowSimpleEvents),
"Approve", sender, e);
}
[WorkflowFireEvent(Option = WorkflowFireEventOption.After,
EventName = "Reject", EventType = typeof(IWorkflowEvents))]
public void Reject(object sender, MyEventArgs e)
{
Console.WriteLine("Operation Reject - {0}", e.TaskName);
}
}
As I mentioned earlier, the WCF connectivity enables the usage of any binding between the event source (client) and service. The following code snippet shows an example of the service endpoint configuration by config file:
<service name="ServiceHost.ServiceTest1">
<endpoint
address="net.tcp://localhost:11111/Workflow"
binding="netTcpBinding"
contract="InterfaceContract.IFireEventTest"/>
</service>
<service name="ServiceHost.ServiceTest3">
<endpoint
address="net.msmq://localhost/private/rk.workflow_test"
binding="netMsmqBinding"
bindingConfiguration="PoisonBinding"
contract="InterfaceContract.IFireEventTest"/>
</service>
Correlated events
In the case of using the multiple workflow
event sinks driven by the same interface contract, the event(s) must be correlated with a specific parameter value to create a unique workflow
queue name in the same workflow
instance. The solution described in this article supports this feature and it is fully transparent to the workflow
event sink HandleExternalEventActivity
(HEEA). This feature is also implemented also in the WorkflowMessageEventHandler2
class.
The following code snippet shows a simple event Reject
correlated on the parameter taskName
:
[ExternalDataExchange]
[CorrelationParameter("taskName")]
public interface IWorkflowEvents
{
[CorrelationInitializer]
void Initializer(string taskName);
[CorrelationAlias("taskName", "TaskName")]
event EventHandler<MyEventArgs> Reject;
}
The Correlated event requires having the Local Service layer to perform a method attributed as CorrelationInitializer
. This method must be called in prior of the HEEA within the specific logical correlation token scope to inform the Local Service about the correlation value.
public class MyLocalService : IWorkflowEvents
{
public void Initializer(string taskName)
{
Console.WriteLine("Initializer - {0}", taskName);
}
public event EventHandler<MyEventArgs> Reject;
}
Note, in our loosely coupled connectivity, the correlation value can be sent by the event source or it can be pre-processed in the operation method on the WCF Service.
Attaching WorkflowRuntime
The WorkflowRuntime
can be attached to the WCF Service host via its extension. For this case, I have created a WSServiceHostExtension
class with two constructors. The first one, will store an existing WorkflowRuntime
reference (for instance: created by Orcas WorkflowServices
) and the second one will create a WorkflowRuntime
from the config file.
The following code snippet shows an example of the creating WorkflowRuntime
based on the config file and attaching it to the WCF host:
using (System.ServiceModel.ServiceHost host =
new System.ServiceModel.ServiceHost(typeof(ServiceTest3)))
{
WFServiceHostExtension extension =
new WFServiceHostExtension("WorkflowRuntimeConfig");
host.Extensions.Add(extension);
host.Open();
Console.WriteLine("Press any key to stop server...");
Console.ReadLine();
host.Close();
}
Example of the workflow
runtime configuration:
Note, the SqlWorkflowPersistenceService
must be included in the config file.
The FireWorkflowEvent
solution is divided into two folders such as WorkflowEventService
- the hosting implementation and WorkflowFireEventAttribute
. This assembly must be included in the application appDomain where the WCF and WF are hosted. The other folder is for test purpose only in order to demonstrate the usage of the WorkflowFireEventAttribute
. This folder contains few projects such as ConsoleApplications
for generating an event, InterfaceContracts
for WCF and WF, Logger
for simple logging of WCF messages on the console screen, ServiceHost
project to host WCF services and WorkflowRuntime
and the last project - WorkflowLibrary
of the 3 workflows (state machine, sequential and xoml activated).
Let's start testing. I assume that your environment has the netfx 3.0 and SqlExpress installed, included a workflow
stuff.
Test 1. Disconnectable Transactional Event
This test demonstrates the connectivity between the event source and event sink (WCF - Workflow
) via transactional MSMQ. Delivering an event message from end to end in transactional (disconnectable) manner enables building robust event driven architectures.
The following are major steps how to plumb WCF and WF models together:
Step 1. Workflow
In this step, we need to create an Interface contract for ExternalDataExchange
with EventHandler
method(s). In our example test, there is simple event Approve
with a workflow
ExternalDataEventArgs
. Once we have this contract, the event sink such as HandleExternalEventActivity
(HEEA) can be assigned. Note, it is not necessary to create a Local Service implementation.
The following picture shows these steps - interface contract, HEEA properties and test workflow
:
That's all for the Workflow
. The following steps are WCF only.
Step 2. WCF Service
First of all, we need to create a Service
(Interface) contract to consume a Workflow
event(s). The following code snippet shows a simple contract for consuming two events - Reject
and Approve
. In this test, only Approve
event will be fire it.
[ServiceContract]
public interface IFireEventTest
{
[OperationContract(IsOneWay = true)]
void Reject(object sender, MyEventArgs e);
[OperationContract(IsOneWay = true)]
void Approve(object sender, ExternalDataEventArgs e);
}
Now, we can create a service and decorate a specific event for its integration with the Workflow
. In this test, we ask the operation invoker for an event post-processing (the event is fired Before
, see the option) and plumbing with the IWorkflowSimpleEvents
contract. The operation is setup for transactional processing with auto committing.
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class ServiceTest3 : IFireEventTest
{
[WorkflowFireEvent(Option = WorkflowFireEventOption.Before,
EventType = typeof(IWorkflowSimpleEvents))]
[OperationBehavior(TransactionAutoComplete =
true, TransactionScopeRequired = true)]
public void Approve(object sender, ExternalDataEventArgs e)
{
}
[WorkflowFireEvent(EventType = typeof(IWorkflowEvents))]
public void Reject(object sender, MyEventArgs e) { }
}
Before hosting this service in the appDomain, we have to create a service configuration section. The following code snippet shows part of the ServiceHost.exe.config file for endpoint
and netMsmqBinding
.
<services>
<service name="ServiceHost.ServiceTest3">
<endpoint
address="net.msmq://localhost/private/rk.workflow_test"
binding="netMsmqBinding"
bindingConfiguration="PoisonBinding"
contract="InterfaceContract.IFireEventTest"/>
</service>
</services>
<bindings>
<netMsmqBinding>
<binding name="PoisonBinding"
receiveErrorHandling="Drop"
receiveRetryCount="0"
exactlyOnce="true"
useSourceJournal="true"
useActiveDirectory="false">
<security mode="None" />
</binding>
</netMsmqBinding>
</bindings>
Now, we can host the above service in the appDomain - see the ServiceHost
project, program.cs file. After that, our Workflow
has been exposed via WCF service to receive a message for the Approve
event.
Step 3. WCF Client
This step shows an example of how the client can generate an Approve
event in the transactional manner. For test purposes, the workflow2.xoml is created and persisted in advance of the firing event. The rest of the work is straightforward such as creating the channel with FireWorkflowEvent
configuration, event argument with application specific event interest and sending an Approve
message. Note, the message will be sent to the MSMQ queue, when the transaction scope is committed.
using (TransactionScope ts =
new TransactionScope(TransactionScopeOption.RequiresNew))
{
using(XmlReader xmlReader=
XmlReader.Create(@"..\..\..\WorkflowLibrary\Workflow2.xoml"))
{
wi = wr.CreateWorkflow(xmlReader);
}
wi.Unload();
using (ChannelFactory<IFireEventTest> factory =
new ChannelFactory<IFireEventTest>("FireWorkflowEvent"))
{
IFireEventTest channel = factory.CreateChannel();
ExternalDataEventArgs eventArgs =
new ExternalDataEventArgs(wi.InstanceId);
eventArgs.WaitForIdle = false;
eventArgs.WorkItem = MyObject.Create(sender, ii);
channel.Approve(sender, eventArgs);
factory.Close();
}
ts.Complete();
}
Now, we are ready to make an actual test.
Step 4. Run Test
Launch the ConsoleApplication2
program. The following screen snippet shows a process of creating and unloading Workflow2
. Using the included Logger
, the console screen will show an event message envelope sent to the WCF service (and forwarding to the Workflow
).
Before launching the ServiceHost
program, the message can be checked in the private rk.workflow_test queue. We don't have a binding setup for timeout delivery and receiving a message by service, therefore we can launch the service at any time.
The following picture shows the console screen snippet for ServiceHost
program. We can see how many endpoints
are in this host, receive event message and also how this message goes through the Workflow2
. Based on the workflow definition, the workflow
will be completed after receiving this message.
The test can be repeated for another workflow
instance by pressing any key on the ConsoleApplication2
screen.
One more thing. In the Step 2, we have commented a post-processing of the firing event. Let's uncomment it and run the service host one more time. Each Approve
event call will be interrupted by the following dialog within the transaction scope:
Based on your choice, we can simulate an Abort
scenario. Note, the message is dropped from the MSMQ queue, but if you are running on Vista, the netMsmqBinding
can be re-configured for using a poison queue.
That's all for this test and let's look at another examples of the WorkflowFireEventAttribute
.
Test 2. Firing Correlated Events
This test demonstrates a correlated Reject
event where the correlated parameter is taskName
. Each workflow
correlation scope has a responsibility to initialize this parameter before waiting for Reject
event. For this purpose, the workflow
is invoking the CallExternalMethodActivity
and passing an initialize
value of the correlated parameter to the host application, therefore the Local Service must be created and register in the WorkflowRuntime
.
To test this scenario, the ServiceHost
program must be launched prior to the ConsoleApplication1
program. Note, this test uses an application specific MyEventArgs
derived from the ExternalDataEventArgs
in the event message, therefore we have to add this type into the <system.runtime.serialization>
section for both config files (service and client):
<system.runtime.serialization>
<dataContractSerializer>
<declaredTypes>
<add type="System.Workflow.Activities.ExternalDataEventArgs,
System.Workflow.Activities,
Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<knownType type ="InterfaceContract.MyObject, InterfaceContract"/>
</add>
<add type="InterfaceContract.MyEventArgs, InterfaceContract">
<knownType type ="InterfaceContract.MyObject, InterfaceContract"/>
</add>
</declaredTypes>
</dataContractSerializer>
</system.runtime.serialization>
Let's do one more test for StateMachine
Workflow
.
Test 3. Firing Simple State Machine Event
The following picture shows a simple example of the State Machine with one state waiting for the Approve
event within the Timeout
limit. The incoming Approve
event will recycle a state and re-trigger a timeout limit.
The interface contracts are very simple and require having only a Null
(no body) WCF Service.
To test this scenario, the ServiceHost
program must be launched prior to the ConsoleApplication
program.
Note, the test ServiceHost
program is configured to run all test client console programs, therefore it can be run for an endless loop test.
Generic WCF Service
The purpose of the Generic
service is to host a universal service for simple events with a typical event signature such as sender
and ExternalDataEventArgs
parameters. The application specific object can be passed via the WorkItem
property of the ExternalDataEventArgs
object to the Workflow
.
The following code snippet shows complete implementation of the generic service for exposing Workflow
via WCF:
[ServiceContract]
public interface IFireWorkflowEvent
{
[OperationContract(Action = "*", IsOneWay = true)]
void FireWorkflowEvent(Message message);
}
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class WorkflowEventService : IFireWorkflowEvent
{
public void FireWorkflowEvent(Message message)
{
WorkflowHelper.FireEvent(message);
}
}
As you can see, there is no operation interceptor and WorkflowFireEventAttribute
for mapping the interface contracts. The concept and the limitation of this solution is based on implicitly mapping a soap message to the Workflow
interface contract defined by name of the event and interface type. The name of the operation contract can be easy mapped to the workflow
event name. The second mapping requirements such as an event type is not straightforward. I decided (for now) to use a namespace of the WCF interface contract.
Ok, now we can look at how the WorkflowHelper.FireEvent
will map an incoming message to the workflow
:
public static void FireEvent(Message message)
{
System.Xml.XmlDictionaryReader reader = message.GetReaderAtBodyContents();
Type interfaceType = Type.GetType(reader.NamespaceURI);
string eventName = reader.Name;
reader.ReadStartElement();
string sender = reader.ReadElementContentAsString("sender",
reader.NamespaceURI);
DataContractSerializer xs =
new DataContractSerializer(typeof(ExternalDataEventArgs),
"e", reader.NamespaceURI);
ExternalDataEventArgs eventArgs =
(ExternalDataEventArgs)xs.ReadObject(reader);
while (reader.NodeType != XmlNodeType.EndElement) { reader.Read(); }
reader.ReadEndElement();
DispatchEvent(interfaceType, eventName, sender, eventArgs);
}
The FireEvent
method deserializes a message body to obtain the first two eventhandler arguments such as sender
and e
. The event contract, as I mentioned above, is obtained from the method name and namespace. Having these parameters, the DispatchEvent
is called to dispatch an event message to the Workflow
queue the same way as it has been described earlier. The following code snippet shows an example of the WCF interface contract for this generic WCF service:
[ServiceContract(Namespace="InterfaceContract.IWorkflowSimpleEvents,
InterfaceContract")]
public interface IFireEventTest2
{
[OperationContract(IsOneWay = true)]
void Approve(object sender, ExternalDataEventArgs e);
}
How is it working?
Well, after the WorkflowEventService
has been hosted in the appDomain with a WorkflowRuntime
, we are ready to receive an event message via the endpoint. The following config snippet shows this generic configuration. Of course, we can create many kind of bindings:
<service name="RKiss.WorkflowEventService.WorkflowEventService">
<endpoint
address="net.tcp://localhost:11110/Workflow"
binding="netTcpBinding"
contract="RKiss.WorkflowEventService.IFireWorkflowEvent"/>
</service>
Therefore, the client (eg. ConsoleApplication
) will send an event message based on the WCF interface contract (eg. IFireEventTest2
) to the above endpoint, forwarding to the generic service ( Action = "*"
) operation FireWorkflowEvent
. Once we have a message in the method, we process it as an event message, otherwise it will throw an exception.
Correlated Events in the State Machine
In the case when the multiple event driven activities (HEEA) are waiting for the same event in the same State, the correlation feature can help it. The following picture shows how the correlation parameter can be initialized in the StateInitializationActivity
. I didn't include this test, therefore try to modify the Workflow1
for this case and test it with a ConsoleApplication1
program.
This article described a WCF/WF integration for firing workflow
events. The concept is based on the loosely coupled connectivity between the WCF and WF interface contracts. It is a straightforward and transparent integration without requirements to modify a workflow
and building a custom Local Service for no correlated events.