Getting Return Data from a sub Workflow  
Author Message
Greg_Dodd





PostPosted: Windows Workflow Foundation, Getting Return Data from a sub Workflow Top

Hello.

I am calling a sub workflow from a work flow via the "invokeWorkflow" shape. Here is my problem. The sub workflow returns a value that is displayed via a web service. How can I retrieve that value from the sub workflow in the main workflow The master web service passes a value into the main workflow, and that value is in turn passed in the sub workflow so that it can perform its work. Now I need to get the return value back.

Any help would be appreciated.

Thanks.



Software Development for Windows Vista2  
 
 
Steve Danielson





PostPosted: Windows Workflow Foundation, Getting Return Data from a sub Workflow Top

Hi Greg,

The InvokeWorkflow activity executes the target Workflow asynchronously, and there is no provision in the InvokeWorkflow activity to wait for the target workflow to complete so that you can access the output parameters that are available in the WorkflowComplete event. However, you could use a pattern like this to both get the return value and have your host workflow block until the target workflow has completed.

Create a LocalService class with a custom argument type that contains a member to hold the data that you want to get out of the target workflow. After the InvokeWorkflow Activity, place a HandleExternalEvent activity and bind to the local service interface. Register an instance of the LocalService class in an ExternalDataExchange service, and in the WorkflowCompleted event in the host look for Workflows that are completing that are not the parent workflow. Pull the parameters from the completing workflow, and raise an event to the Hosting workflow that contains the data that you need to get back from the invoked workflow.

Let me know if you would like some code examples and I would be happy to post them here.

Thanks,

Steve Danielson [Microsoft]
This posting is provided "AS IS" with no warranties, and confers no rights.


 
 
jamba8





PostPosted: Windows Workflow Foundation, Getting Return Data from a sub Workflow Top

I have a similar problem:

I have a WF1 which creates a certain amount of workflows of the type WF2. When a WF2 finishes I want to notify the WF1, so it can check if all started WF2s are finished now.

When a WF2 finished I use a "CallExternalMethod" Activity which calls a method in which a event is triggers. The WF1 has a "HandleExternalEvent" Activity which listens for that particular event. But I just cant get that working.

I would appreciate If you could explain this scenario and provide some code examples.

Thanks


 
 
Greg_Dodd





PostPosted: Windows Workflow Foundation, Getting Return Data from a sub Workflow Top

Thanks Steve.

If you wouldn't mind putting a bit of code example in there, I would appreciate it.

Thanks,

Greg


 
 
Steve Danielson





PostPosted: Windows Workflow Foundation, Getting Return Data from a sub Workflow Top

Here is some sample code below. Please let me know if this answers it for you. THis is a console sequential workflow application that invokes a workflow using InvokeWorkflowActivity, and then blocks and waits for that workflow to complete, and then it extracts a return value from the invoked workflow

// Program.cs
#region Using directives

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Workflow.Runtime;
using System.Workflow.Runtime.Hosting;
using System.Workflow.Activities;

#endregion

namespace WorkflowConsoleApplication11
{
  class Program
  {
    static Guid HostWFGuid;
    static LocalService ls;

    static void Main(string[] args)
    {
      using(WorkflowRuntime workflowRuntime = new WorkflowRuntime())
      {
        // LocalServices
        ExternalDataExchangeService dataService = new ExternalDataExchangeService();
        workflowRuntime.AddService(dataService);
        ls = new LocalService();
        dataService.AddService(ls);

        AutoResetEvent waitHandle = new AutoResetEvent(false);
        workflowRuntime.WorkflowCompleted += delegate(object sender, WorkflowCompletedEventArgs e)
        {
          // If we are going to spawn only 1 child workflow
          // Then we just need to check that the workflow that
          // is completing is not our host workflow, like this
          //if (e.WorkflowInstance.InstanceId != HostWFGuid)

          // Another way is to compare the completing InstanceId
          // with the InstanceID from the Workflow that was invoked
          if(e.WorkflowInstance.InstanceId == ls.TargetInstanceId)
          {
            // This is an Invoked Workflow Completing
            // Get out the Return value
            // Signal the "Host" Wf that it is complete
            int nRetVal = Convert.ToInt32(e.OutputParameters["RetVal"]);
            ls.WorkComplete(HostWFGuid, nRetVal);
          }
          else
          {
            // We are done, so signal
            waitHandle.Set();
          }
        };
        workflowRuntime.WorkflowTerminated += delegate(object sender, WorkflowTerminatedEventArgs e)
        {
          Console.WriteLine(e.Exception.Message);
          waitHandle.Set();
        };

        WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(WorkflowConsoleApplication11.Workflow1));
        HostWFGuid = instance.InstanceId;
        instance.Start();

        waitHandle.WaitOne();
      }
    }
  }
}

This is the local services class:
// LocalServices.cs
using System;
using System.Workflow.Activities;
namespace WorkflowConsoleApplication11
{
[Serializable]
public class MyExternalDataEventArgs : ExternalDataEventArgs
{
public MyExternalDataEventArgs(Guid InstanceID, int nRetValue)
: base(InstanceID)
{
this.RetValue = nRetValue;
}
        private int m_nRetValue;
        public int RetValue
{
get { return m_nRetValue; }
set { m_nRetValue = value; }
}
}
    [ExternalDataExchange]
internal interface ILocalService
{
event EventHandler<MyExternalDataEventArgs> InvokedWorkflowComplete;
void SetTargetId(Guid InstanceId);
void WorkComplete(Guid HostWFGuid, int nRetValue);
}
    internal class LocalService : ILocalService
{
public event EventHandler<MyExternalDataEventArgs> InvokedWorkflowComplete;
public void WorkComplete(Guid HostWFGuid, int nRetValue)
{
if (InvokedWorkflowComplete != null)
{
InvokedWorkflowComplete(null, new MyExternalDataEventArgs(HostWFGuid, nRetValue));
}
}
        public void SetTargetId(Guid InstanceId)
{
TargetInstanceId = InstanceId;
}
        public Guid TargetInstanceId;
}
}
Here is the designer.cs and code beside cs for the Host workflow:
// Workflow1.designer.cs
using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Collections;
using System.Drawing;
using System.Reflection;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.Runtime;
using System.Workflow.Activities;
using System.Workflow.Activities.Rules;
namespace WorkflowConsoleApplication11
{
partial class Workflow1
{
#region Designer generated code

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
[System.Diagnostics.De****NonUserCode]
private void InitializeComponent()
{
this.CanModifyActivities = true;
System.Workflow.ComponentModel.ActivityBind activitybind1 = new System.Workflow.ComponentModel.ActivityBind();
System.Workflow.ComponentModel.WorkflowParameterBinding workflowparameterbinding1 = new System.Workflow.ComponentModel.WorkflowParameterBinding();
System.Workflow.ComponentModel.ActivityBind activitybind2 = new System.Workflow.ComponentModel.ActivityBind();
System.Workflow.ComponentModel.WorkflowParameterBinding workflowparameterbinding2 = new System.Workflow.ComponentModel.WorkflowParameterBinding();
this.codeActivity2 = new System.Workflow.Activities.CodeActivity();
this.handleExternalEventActivity1 = new System.Workflow.Activities.HandleExternalEventActivity();
this.callExternalMethodActivity1 = new System.Workflow.Activities.CallExternalMethodActivity();
this.invokeWorkflowActivity1 = new System.Workflow.Activities.InvokeWorkflowActivity();
this.codeActivity1 = new System.Workflow.Activities.CodeActivity();
//
// codeActivity2
//
this.codeActivity2.Name = "codeActivity2";
this.codeActivity2.ExecuteCode += new System.EventHandler(this.codeActivity2_ExecuteCode);
//
// handleExternalEventActivity1
//
this.handleExternalEventActivity1.EventName = "InvokedWorkflowComplete";
this.handleExternalEventActivity1.InterfaceType = typeof(WorkflowConsoleApplication11.ILocalService);
this.handleExternalEventActivity1.Name = "handleExternalEventActivity1";
activitybind1.Name = "Workflow1";
activitybind1.Path = "MyArgs";
workflowparameterbinding1.ParameterName = "e";
workflowparameterbinding1.SetBinding(System.Workflow.ComponentModel.WorkflowParameterBinding.ValueProperty, ((System.Workflow.ComponentModel.ActivityBind)(activitybind1)));
this.handleExternalEventActivity1.ParameterBindings.Add(workflowparameterbinding1);
//
// callExternalMethodActivity1
//
this.callExternalMethodActivity1.InterfaceType = typeof(WorkflowConsoleApplication11.ILocalService);
this.callExternalMethodActivity1.MethodName = "SetTargetId";
this.callExternalMethodActivity1.Name = "callExternalMethodActivity1";
activitybind2.Name = "invokeWorkflowActivity1";
activitybind2.Path = "InstanceId";
workflowparameterbinding2.ParameterName = "InstanceId";
workflowparameterbinding2.SetBinding(System.Workflow.ComponentModel.WorkflowParameterBinding.ValueProperty, ((System.Workflow.ComponentModel.ActivityBind)(activitybind2)));
this.callExternalMethodActivity1.ParameterBindings.Add(workflowparameterbinding2);
//
// invokeWorkflowActivity1
//
this.invokeWorkflowActivity1.Name = "invokeWorkflowActivity1";
this.invokeWorkflowActivity1.TargetWorkflow = typeof(WorkflowConsoleApplication11.SubWorkflow2);
this.invokeWorkflowActivity1.Invoking += new System.EventHandler(this.TargetWfInvoking);
//
// codeActivity1
//
this.codeActivity1.Name = "codeActivity1";
this.codeActivity1.ExecuteCode += new System.EventHandler(this.codeActivity1_ExecuteCode);
//
// Workflow1
//
this.Activities.Add(this.codeActivity1);
this.Activities.Add(this.invokeWorkflowActivity1);
this.Activities.Add(this.callExternalMethodActivity1);
this.Activities.Add(this.handleExternalEventActivity1);
this.Activities.Add(this.codeActivity2);
this.Name = "Workflow1";
this.CanModifyActivities = false;
        }
        #endregion
        private CodeActivity codeActivity1;
private CodeActivity codeActivity2;
private HandleExternalEventActivity handleExternalEventActivity1;
private CallExternalMethodActivity callExternalMethodActivity1;
private InvokeWorkflowActivity invokeWorkflowActivity1;
 
 
 

}
}
// workflow1.cs
using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Collections;
using System.Drawing;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.Runtime;
using System.Workflow.Activities;
using System.Workflow.Activities.Rules;
namespace WorkflowConsoleApplication11
{
public sealed partial class Workflow1: SequentialWorkflowActivity
{
private MyExternalDataEventArgs myArgs;
        public MyExternalDataEventArgs MyArgs
{
get { return myArgs; }
set { myArgs = value; }
}

public Workflow1()
{
InitializeComponent();
}
        private void codeActivity1_ExecuteCode(object sender, EventArgs e)
{
Console.WriteLine("Host Workflow Executing");
}
        private void codeActivity2_ExecuteCode(object sender, EventArgs e)
{
Console.WriteLine("Return Value from Invoked Workflow {0}", MyArgs.RetValue);
}
        private void TargetWfInvoking(object sender, EventArgs e)
{
}
}
}
Here is the child workflow designer and code beside:
// Subworkflow2.Designer.cs
using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Collections;
using System.Drawing;
using System.Reflection;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.Runtime;
using System.Workflow.Activities;
using System.Workflow.Activities.Rules;
namespace WorkflowConsoleApplication11
{
partial class SubWorkflow2
{
#region Designer generated code

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
[System.Diagnostics.De****NonUserCode]
private void InitializeComponent()
{
this.CanModifyActivities = true;
this.codeActivity1 = new System.Workflow.Activities.CodeActivity();
//
// codeActivity1
//
this.codeActivity1.Name = "codeActivity1";
this.codeActivity1.ExecuteCode += new System.EventHandler(this.codeActivity1_ExecuteCode);
//
// SubWorkflow2
//
this.Activities.Add(this.codeActivity1);
this.Name = "SubWorkflow2";
this.CanModifyActivities = false;
        }
        #endregion
        private CodeActivity codeActivity1;
}
}
// SubWorkflow2.cs
using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Collections;
using System.Drawing;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.Runtime;
using System.Workflow.Activities;
using System.Workflow.Activities.Rules;
namespace WorkflowConsoleApplication11
{
public sealed partial class SubWorkflow2: SequentialWorkflowActivity
{
private int x;
        public int RetVal
{
get { return x;}
set { x = value;}
}
        public SubWorkflow2()
{
InitializeComponent();
}
        private void codeActivity1_ExecuteCode(object sender, EventArgs e)
{
x = 10;
Console.WriteLine("Target Workflow Executing");
}
}
}
Steve Danielson [Microsoft]
This posting is provided "AS IS" with no warranties, and confers no rights.
Use of included script samples are subject to the terms specified at http://www.hide-link.com/


 
 
jamba8





PostPosted: Windows Workflow Foundation, Getting Return Data from a sub Workflow Top

Thanks Steve for posting your code. I have a quite different setup (Host: Windows Service, Consumer: Asp.Net web application, State Machine Workflow) but I think I have a clue why my events dont get delivered.

Could you explain the AutoResetEvent (WaitHandle) functionality. I never used this in my Workflows. I guess I need that. But I dont know where to put it, or what it does.

--------------------------

EDIT: I tried to set the AutoResetEvent after I spawn the subworkflows (also StateMachineWorkflows). But then the workflow doesnt get persisted and it doesnt show in the Workflow Tracking Monitor.

I'm quite desperate, because I cannot deliver an event from my sub state machine workflow to my parent state machine workflow. No matter what I tried, I always get an exception, that the event for the instance (subworkflow instance) cannot be delivered.

--------------------------

EDIT:

Would you mind providing an example with 2 state machine workflows, instead of the Sequential workflows I would really appreciate your effort.

--------------------------

Thanks


 
 
Steve Danielson





PostPosted: Windows Workflow Foundation, Getting Return Data from a sub Workflow Top

Hi,

The AutoReset event is just something that is used so that the host can wait for the hosted workflow to complete execution.

I will build a small sample that uses 2 state machine workflows and signal an event from the hosted workflow to the parent workflow. I will post back later today with this.

Steve Danielson [Microsoft]
This posting is provided "AS IS" with no warranties, and confers no rights.


 
 
Steve Danielson





PostPosted: Windows Workflow Foundation, Getting Return Data from a sub Workflow Top

Hi,

You said:

I'm quite desperate, because I cannot deliver an event from my sub state machine workflow to my parent state machine workflow. No matter what I tried, I always get an exception, that the event for the instance (subworkflow instance) cannot be delivered.

One thing to be sure, that the instanceId that you use to construct your ExternalDataEventArgs class is the InstanceId of the Workflow that is waiting for the event. Note in my sample code I had stored the InstanceId of the parent workflow in the HostWFGuid variable, and I used that when calling into my localservices instance.

My sample with a State Machine that uses InvokeWorkflow activity to spawn another state machine and then waits until it is complete is complete... I am trying to figure a way to post the sample for download so I don't have to paste all the code here, but it should be very soon.

Steve Danielson [Microsoft]
This posting is provided "AS IS" with no warranties, and confers no rights.
Use of included script samples are subject to the terms specified at http://www.microsoft.com/info/cpyright.htm



 
 
jamba8





PostPosted: Windows Workflow Foundation, Getting Return Data from a sub Workflow Top

Hi Steve

I am absolutely clear about how to send the events now. Just as you wrote, I used to id of the wrong workflow in my EventArgs. After changing that everything worked fine. Now I know that I always have to use the id of the workflow which waits for the event, and not the signaling workflow. That was my mistake.

Thanks for your postings.

Klaus


 
 
Steve Danielson





PostPosted: Windows Workflow Foundation, Getting Return Data from a sub Workflow Top

I'm glad you got it working!

Steve Danielson [Microsoft]
This posting is provided "AS IS" with no warranties, and confers no rights.


 
 
BrianBT





PostPosted: Windows Workflow Foundation, Getting Return Data from a sub Workflow Top

Steve,

Could you let me know where I can download your sample with a State Machine that uses InvokeWorkflow activity for host Asp.net I  

Thanks

 

Brian


 
 
Steve Danielson





PostPosted: Windows Workflow Foundation, Getting Return Data from a sub Workflow Top

Hi Brian,

My sample does not use ASP.NET as the host, it is a console application, but the techniques are the same. The main difference is that in my console application I have a running stateful host application which simplifies the communication between the workflow and the host. I am going to post my sample in the next 30 minutes or so, and please take a look and see if you can get what you need from there. If not, then let me know what is missing, and I can create a small ASP.NET sample that does what you wish.

Thanks,

Steve Danielson [Microsoft]
This posting is provided "AS IS" with no warranties, and confers no rights.


 
 
Steve Danielson





PostPosted: Windows Workflow Foundation, Getting Return Data from a sub Workflow Top

Here is the sample:

http://blogs.msdn.com/sdanie/archive/2006/06/23/644366.aspx

Thanks,

Steve Danielson [Microsoft]
This posting is provided "AS IS" with no warranties, and confers no rights.
Use of included script samples are subject to the terms specified at http://www.microsoft.com/info/cpyright.htm


 
 
BrianBT





PostPosted: Windows Workflow Foundation, Getting Return Data from a sub Workflow Top

Steve,

Thanks for your help and I appreciate it.

have a great weekend,

Brian


 
 
John Portnov





PostPosted: Windows Workflow Foundation, Getting Return Data from a sub Workflow Top

Steve,

This example is great, but I have some questions.  I need to call a child workflow from a parent workflow and then tell the parent workflow what event to call.  Inside the WorkflowCompleted event (instead of calling Completed event), how do you pass event property name to the parent workflow from the child workflow (i.e., how do you pass a property from a child workflow to the host of the parent workflow )

BTW:  How can I make the InvokeWorkflowActivity call a child workflow that is XOML only state workflow

Currently I call an xoml workflow with Createworkflow(xomlreader, rulesreader, paramsCollection).  The TargetWorkflow property of the InvokeWorkflowActivity does not give me this flexibility.  Any suggestions

Also, what is the best way to raise a parent workflow event from a childworkflow completion

Thanks in advance,

John Portnov