locked
Adding a service to workflow runtime from an activity RRS feed

  • Question

  • Hi, I have a sequential workflow activity which uses a callExternalMethodActivity to call a service. The issue I'm having is that the service must be already registered with the workflowRuntime. Since I don't have access to the workflow initialisation code (the activity is run through Project Trident), I was wondering if there is a way to register this service.

    The reason for having this service is I have several simulation runs, each of which should be run in it's own thread. Is this the right approach (external services) or is there a way to do this without calling an external method? 

    Thursday, April 22, 2010 5:27 AM

Answers

  • Thanks for the link. Upon researching, there doesn't appear to be any easy mechanism to attach a service to a workflow runtime. The method I managed to get adding the service uses reflection to edit the values.

     

    Type activityType = this.GetType();
    
      while (activityType.Name != "Activity")
      {
      activityType = activityType.BaseType;
      }
    
      object workflowExecutor =
      activityType.InvokeMember(
       "workflowCoreRuntime",
       BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance,
       null,
       this,
       null);
      object runtime = workflowExecutor.GetType().InvokeMember(
       "_runtime",
       BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance,
       null,
       workflowExecutor,
       null);
    
      // args to call WorkflowRuntime.AddService(System.Object) 
      object[] args = new object[1];
      ExternalDataExchangeService edes = new ExternalDataExchangeService();
    
      args[0] = edes;
    
      // call method AddService with setup Args
      var returnval = runtime.GetType().InvokeMember(
      "AddService",
      BindingFlags.InvokeMethod,
      null,
      runtime,
      args);
      edes.AddService(new ServiceName());

     

    This seems to produce the same result as using the 'usual' way using workflowruntime.AddService() method and I've found is best put inside the Invoke() method of the activity

    The only issue I'm having now is that the CallExternalMethodActivity isn't passing its correlationToken to the service.  Thats a separate issue, the code above does solve the issue of adding the service to the runtime.

     

    • Marked as answer by dheh Friday, April 30, 2010 12:05 AM
    • Edited by dheh Friday, April 30, 2010 12:08 AM code context
    Wednesday, April 28, 2010 5:56 AM

All replies

  • To add a custom workflow runtime service to the runtime, you need to have access to the runtime (which you can get if you have a custom service registered) but you can't get it through the activity execution context that you get in your custom activity's execute override.

    I am not familiar with Project Trident, is there any way to get a custom service registered? Or is there any sort of generic communication activities generated that you could use to talk with the host. "Long running" work, such as running simulations, are best done on another thread in the host and not the workflow thread and a custom activity/service, or communication activities are the way to go, but you have to be able to have access to the host to register the service(s).

    I can provide some sample code for these approaches, but it depends on what is available through your host.

    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

     

    Thursday, April 22, 2010 10:52 PM
    Moderator
  • With Trident there is no way (to my knowledge) of accessing the runtime. Trident is supplied with a composer front end which allows the workflow to be made graphically (similar to the way in visual studio). This means all that can be developed are the activities which only have access to an AEC and that is about all I can access to. There also are no generic communication activities to talk to the host either (is this something that I could write?). 

    This leaves me thinking that this may be a Trident issue since it's essentially a scientific workflow front end which doesn't allow "long running" work.

    Thursday, April 22, 2010 11:15 PM
  • Thanks for providing the link to Trident.

    On the download page for Trident it says:

    http://research.microsoft.com/en-us/downloads/f8d37ecb-dfed-4a3d-840a-7d1ccc6b60d4/default.aspx

    "Project Trident also provides a framework to add runtime services and comes with services such as provenance, workflow monitoring, and workflow-versioning support."

    This appears to be exactly what you need, registering a custom WorkflowRuntime service would allow you to do exactly what you describe above. Here is a sample of a custom activity paired with a custom service that shows how to perform long running work in the host without blocking the workflow thread.

    http://blogs.msdn.com/pandrew/archive/2007/11/13/patterns-for-long-running-activities-in-windows-workflow-foundation.aspx

    Unfortunately I am not familiar with Trident to know how to register this custom runtime service, but I will see if I can find out anything.

    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

     

     

    Friday, April 23, 2010 5:09 PM
    Moderator
  • Thanks for the link. Upon researching, there doesn't appear to be any easy mechanism to attach a service to a workflow runtime. The method I managed to get adding the service uses reflection to edit the values.

     

    Type activityType = this.GetType();
    
      while (activityType.Name != "Activity")
      {
      activityType = activityType.BaseType;
      }
    
      object workflowExecutor =
      activityType.InvokeMember(
       "workflowCoreRuntime",
       BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance,
       null,
       this,
       null);
      object runtime = workflowExecutor.GetType().InvokeMember(
       "_runtime",
       BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance,
       null,
       workflowExecutor,
       null);
    
      // args to call WorkflowRuntime.AddService(System.Object) 
      object[] args = new object[1];
      ExternalDataExchangeService edes = new ExternalDataExchangeService();
    
      args[0] = edes;
    
      // call method AddService with setup Args
      var returnval = runtime.GetType().InvokeMember(
      "AddService",
      BindingFlags.InvokeMethod,
      null,
      runtime,
      args);
      edes.AddService(new ServiceName());

     

    This seems to produce the same result as using the 'usual' way using workflowruntime.AddService() method and I've found is best put inside the Invoke() method of the activity

    The only issue I'm having now is that the CallExternalMethodActivity isn't passing its correlationToken to the service.  Thats a separate issue, the code above does solve the issue of adding the service to the runtime.

     

    • Marked as answer by dheh Friday, April 30, 2010 12:05 AM
    • Edited by dheh Friday, April 30, 2010 12:08 AM code context
    Wednesday, April 28, 2010 5:56 AM
  • Correlation in WF3 has two parts. The CorrelationToken is used to bind the CallExternalMethod (CEM) that initializes the correlation with any HandleExternalEvent (HEE) activities that follow this correlation; that is are the ones that any events raised by the code that is executed by the CEM that started it all. That's one part.

    In addition to those tokens, you must also have a unique piece of data that is passed along as one of the parameters of the CallExternalMethod. In your ExternalDataExchange interface, you indicate this piece of data with the CorrelationParameter attribute which is used to decorate the external data exchange interface. (For example, OrderId). This OrderId must be passed along as one of the parameters of the method invoked by the CallExternalMethod activity. The custom event args that you pass back when you raise the event back to the workflow must also have a parameter of OrderId, that is set to the value of the OrderId that is passed in by the CEM. (if this value is in the args but with a different name, you can map it using the CorrelationAlias attribute). finally, the method (or methods) in the interface that can be used to start up a correlated exchange must be marked with the CorrelationInitializer attribute.

    The Correlated Local Service Sample shows how to use these attributes.

    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

     

     

    Wednesday, April 28, 2010 5:22 PM
    Moderator
  • This is possible in WF3 (and thankfully a fully supported feature in WF4). Better and cleaner code would be this->

    internal static WorkflowRuntime AcquireRuntime(this Activity activity)

    {

      Debug.Assert(activity != null);

     

      var workflowExecutor = typeof(Activity).InvokeMember(

                    "workflowCoreRuntime",

                    BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance,

                    null,

                    activity,

                    null);

     

      var runtime = workflowExecutor.GetType().InvokeMember(

                    "_runtime",

                    BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance,

                    null,

                    workflowExecutor,

                    null);

     

      Debug.Assert(runtime != null);

      Debug.Assert(runtime is WorkflowRuntime);

     

      return runtime as WorkflowRuntime;

    }

    Which would then be used like this in an Activity or workflow

    protectedoverridevoid Initialize(IServiceProvider provider) {

      var servicePresent = provider.GetService(typeof(MyService)) != null;   var runtime = this.AcquireRuntime();

      if (!servicePresent) runtime.AddService(newMyService());

      base.Initialize(provider); }

    Note that you must perform this initialization BEFORE calling the base method!

    So there’s an additional concern which will be needed to be handled in case the service instance subclasses WorkflowRuntimeService. Internally when added to a runtime, the runtime will provide a reference to itself to the service which will then sink all the appropriate events. However the runtime will already be running and therefore the Started event will not be fired (from the perspective of the service) and it will never move into the started state. You can handle this with another little but of reflection to call into the HandleStarted even sink and everything else should just work.

    internal static void StartService(this WorkflowRuntimeService service){
       var method = typeof(WorkflowRuntimeService).GetMethod("HandleStarted"BindingFlags.NonPublic | BindingFlags.Instance);
       var c = typeof(WorkflowRuntimeEventArgs).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic).First();
       var args = c.Invoke(new Object[] { true });

       method.Invoke(service, new [] { service.AcquireRuntime(), args });}

    
    
    internal static WorkflowRuntime AcquireRuntime(this WorkflowRuntimeService service){
      var property = typeof(WorkflowRuntimeService).GetProperty("Runtime"BindingFlags.NonPublic | BindingFlags.Instance);
      Debug.Assert(property != null);
      var runtime = property.GetValue(service, null);
      Debug.Assert(runtime != null);
      Debug.Assert(runtime is WorkflowRuntime);
      return runtime as WorkflowRuntime;}
    
    
    • Edited by Jimmy Zimms Thursday, October 11, 2012 4:22 PM
    Wednesday, October 10, 2012 4:39 PM