Parallel Event Handling in Windows Workflow

I've been using Windows Work Flow (WF) for a while, and I faced a lot of problems that I could solve, and this article is about one of them,
I am working on a project that faced the situation which I need to raise the same event to more than one HandleExternalEventActivity activities working in parallel, and I wanted just one of the HandleExternalEventActivity's to handle the coming event according to an identifier that I'm going to send with the EventArgs.
The problem comes, that by default, if you have a ParallelActivity and inside each branch you have a HandleExternalEventActivity activity, when you raise the event from the host, you can't control which HandleExternalEventActivity gets the event, so the most left HandleExternalEventActivity gets and handles the event before the other even if I wasn't targeting it in the first place.
So how to solve this,
after some searching and reading some working or sometimes old & not-working samples, I found out something called Correlation.
so as I could understand, Correlation is how you specify a parameter in the ExternalDataExchange that is going to identify who is going to handle the Event, and only the HandleExternalEventActivity that has the same parameter in its CorrelationToken will handle the event.
so it first sounds like a big blah blah blah blah.
but after some trials I managed to write some steps of how you do that.
1- Write your custom ExternalDataEventArgs by inheriting from ExternalDataEventArgs

[Serializable]
internal class TaskEventArgs : ExternalDataEventArgs
{
private string _identifier;

public string Id
{
get { return _identifier; }
set { _identifier = value; }
}

public TaskEventArgs(Guid instanceId): base(instanceId)
{

}
}

2- Write your ExternalDataExchange interface like this

[ExternalDataExchange]
[CorrelationParameter("Identifier")]
internal interface IService
{
[CorrelationInitializer]

void Initialize(string Identifier);

[CorrelationAlias("Identifier", "e.Id")]

event EventHandler<TaskEventArgs> TaskFinished;
}

as you can see, we used three different Attributes
CorrelationParameter: this is where you specify the name of the parameter that will hold the value that Identifies the event destination
CorrelationInitializer: this is where you are going to specify the correlation parameter value, the CorrelationParameter should be a parameter to this method, and take care to write it exactly as you wrote it in the CorrelationParameter attribute with the same casing.
CorrelationAlias: this is where you specify the location of the parameter in the event arguments, because as we said before, the event argument will hold the destination identifier.

3- Implement the IService Interface

 internal class Service:IService
{
   #region IService Members

   public void Initialize(string Identifier)
   {
       // Actually you don't need to do anything here;
   }

   public event EventHandler<TaskEventArgs> TaskFinished;

   #endregion

   public void RaiseEvent(Guid instanceId, string identifier)
   {
       //initializing the TaskEventArgs.
       TaskEventArgs args = new TaskEventArgs(instanceId);
       args.Id = identifier; // setting the specified identifier to the Id property in the TaskEventArgs.
       if (TaskFinished != null)
          TaskFinished(null, args);   // Firing the event
   }

}

4- Now go to your Workflow designer, and add in every Parallel branch a CallExternalMethodActivity before the HandleExternalEventActivity you already have.
your workflow should look similar to this

as you can see in every branch you have a CallExternalMethodActivity, and HandelExternalEventActivity.
forget about the other two code activities right now.
5- Now configure the first CallExternalMethodActivity, in our case InitializeFirst
5.1 set the interface type to your ExternalDataExchange interface, in our case the IService interface
5.2 set the Method Name to the method that has the CorrelationInitializer attribute, in our case the Initialized Method in IService.
5.3 now you will notice that you have another option in the properties of the CallExternalMethodActivities called CorrelationToken, write a name inside this box "C1" for example and then expand the node by clicking on the + sign beside the CorrelationToken text and set the OwnerActivityName property with the name of Activity that holds the CallExternalMethodActivity, in our case it is sequenceActivity1.
5.4 also you will find the parameter of the Method you are calling shown in the property grid, in our case it is Identifier, set it to the identifier value you want, or bind it to a member of property, in our case we will bind it to the string "First"
5.4 repeat the same settings for the second CallExternalMethodActivity, in our case InitializeSecond but use different name for the CorrelationToken "C2" with OwnerActivityName to sequenceActivity2
the property grid should look similar to this

6- Configure the HandleExternalEventActivity, in our case HandleFirst
6.1 Choose the interface type as same as the one in the CallExternalMethod, in our case IService interface
6.2 Choose the Event Name, in our case TaskFinished
6.3 You will find a property CorrelationToken appeared, choose "C1" from the list you have
6.4 Repeat this settings for all other HandleExternalEventActivities you have, in our case HandleSecond but use the C2 CorrelationToken for this one.
The Property Grid of the HandleExternalEventActivity should look similar to this

7 now you are almost done, just go to the workflow creation code, usually in the Program.cs file and add an ExternalDataExchangeService to the runtime and add an instance of the IService service to it like this

 ExternalDataExchangeService exchange = new ExternalDataExchangeService();
workflowRuntime.AddService(exchange);
Service service = new Service();
exchange.AddService(service);

Then after you start the instance and the runtime, you can start raising the event by using (in our case) RaiseEvent function and specify the Identifier you are targeting as a parameter to the function like this

 service.RaiseEvent(instance.InstanceId, "First");

also for a complete example, I attached the complete version of the last code with this blog post. with enough documentation in the code.
Have fun coding.
kick it on DotNetKicks.com