locked
Get the current workflow instance ID in a WF4 workflow service RRS feed

  • Question

  • Is it possible to somehow query the current workflow instance ID (or another unique identifier) within code executed as part of a workflow service?

    Example:

    public sealed class SomeCodeActivity : CodeActivity
    {
      public InOutArgument<List<string>> Messages { get; set; }
    
      protected override void Execute(CodeActivityContext context)
      {
        var logger = new SimulatedLogger();
        logger.CreateLog(context.GetValue(Messages));
      }
    }
    
    
    public class SimulatedLogger
    {
      public void CreateLog(IList<string> messages)
      {
        //This line throws an exception
        //var workflowInstanceId = WorkflowEnvironment.WorkflowInstanceId;
          
        var workflowInstanceId = "Workflow Instance";
    
        var message = string.Format("Log: Thread {0}, WFInstance {1}",
                      Thread.CurrentThread.ManagedThreadId, workflowInstanceId);
        messages.Add(message);
      }
    }
    
    


    In ASP.NET you can use HttpContext.Current as context for a single request, in WCF there is the OperationContext.Current available for the duration of the service request. The closest thing to that I could find was WorkflowEnvironment.WorkflowInstanceId (which could be used as a key to a custom context implementation).

    Unfortunately, a call to WorkflowEnvironment.WorkflowInstanceId throws an exception (Unable to extract Instance ID for the current batch ).

     

    Background:

    I would like to be able to write log entries with information to which workflow instance they belong - similar to logging with a thread ID. Workflow instances are (no longer?) guaranteed to run on a single thread (e.g. as soon as there is a ParallelActivity with a delay or asynchronous call in one of the branches there is a good chance that code runs on multiple threads).

    I cannot use OperationContext.Current as most of our workflow services are either one-way calls or return a response before the workflow has finished.

    Wednesday, October 6, 2010 6:07 AM

Answers

  • Hi guys,
    I haven't fully tested it, but it seems like it might work if you use this wrapper actitivty as the root of your workflow, would you mind trying it out and letting me know if it works ok for you?
    Tim

      [Serializable]
      public class ThreadStaticExecutionProperty : IExecutionProperty
      {
        [ThreadStatic]
        private static Nullable<Guid> threadStaticId;
    
        private Guid serializedId;
    
        public void CleanupWorkflowThread()
        {
          threadStaticId = null;
        }
    
        public void SetupWorkflowThread()
        {
          if (threadStaticId != null)
          {
            throw new Exception("We appear to have already set up the execution property on this thread.");
          }
          threadStaticId = this.serializedId;
        }
    
        public void SetId(Guid newId)
        {
          this.serializedId = newId;
        }
    
        public static Nullable<Guid> ExecutingActivityId
        {
          get
          {
            return threadStaticId;
          }
        }
      }
    
      public class WrapperActivity : NativeActivity
      {
        public Activity Body { get; set; }
    
        protected override void Execute(NativeActivityContext context)
        {
          var property = new ThreadStaticExecutionProperty();
          property.SetId(context.WorkflowInstanceId);
          context.Properties.Add("instancePropertyMaintainer", property);
          if (this.Body != null)
          {
            context.ScheduleActivity(this.Body);
          }
        }
      }
    
    
    
    Thursday, October 14, 2010 10:23 PM

All replies

  • Hi Te Aro,

    Take care about Paralle activity. As many WF users you think it use paralleles thread but it isn't true. Your Workflow will always work in a single Thread. Parallele is only really usable with async activities. But in asynch activity we use the IAsyncResult Pattern that ensure we use the single thread at begining and at end of activity.

    In workflow services you can use the web context as in simple WCf services. It could help you to your log ;)


    Jérémy Jeanson MVP, MCP, MCTS http://blogs.codes-sources.com/JeremyJeanson/ (French or English spoken)
    Wednesday, October 6, 2010 9:21 AM
  • WorkflowEnvironment.WorkflowInstanceId is part of the WorkflowRuntime that was introduced with .NET 3.0, (System.Workflow.*) and while this method of hosting workflows is still included in .NET 4, it is not used when .NET 4 workflows (System.Activities.*)

    In your code activity above, you can get the workflow instance id by querying the activity context (context.WorkflowInstanceID) from your CodeActivity.Execute override, and then you could pass than along to your logger component.

    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

     

    • Marked as answer by Andrew_Zhu Wednesday, October 13, 2010 7:09 AM
    • Unmarked as answer by Te Aro Thursday, October 14, 2010 6:00 AM
    Wednesday, October 6, 2010 4:07 PM
  • I have read a number of time that a workflow instance always works on a single thread. That is not true if there is for example a Delay activity (even with a few milliseconds) in the sequence to allow the execution to run "parallel". I put together a small spike and after the Delay there are definitely more than one thread running.

    The workflows run as Workflow Services, however there is no access to the web context once the WCF call has returned, i.e. the SendResponse activity has been executed. After that our workflows continue to run - only the client request has been completed. Calls to e.g. OperationContext.Current after the SendResponse results in exceptions.

    Friday, October 8, 2010 12:24 PM
  • Hi Te Aro,

    Delay is a specila feature as Receive activities. Workflow is idle and is reload in a diferent thread than invokation thread but it use only one thread at same time.


    Jérémy Jeanson MVP, MCP, MCTS http://blogs.codes-sources.com/JeremyJeanson/ (French or English spoken)
    • Marked as answer by Andrew_Zhu Wednesday, October 13, 2010 7:09 AM
    • Unmarked as answer by Te Aro Thursday, October 14, 2010 5:59 AM
    Friday, October 8, 2010 12:27 PM
  • WorkflowEnvironment.WorkflowInstanceId is part of the WorkflowRuntime that was introduced with .NET 3.0, (System.Workflow.*) and while this method of hosting workflows is still included in .NET 4, it is not used when .NET 4 workflows (System.Activities.*)

    Thank you for clarifying this. While I'm sure there are good reasons for it, it nevertheless is disappointing for us.

     

    Yes, I know I could get the instance ID within the context of activities and then pass it on, but that has one fundamental flaw: It would have to be passed through to any code executed by the workflow. We have quite a number of code activities that use libraries. In these libraries we of course want to be able to log. To achieve this the way you described, all public methods of library classes would have to include a parameter for the instance ID so they can pass it on to the logger.

    We use the UnityContainer to inject amongst others the Logger. I am now trying to implement a LifetimeManager for a workflow instance so that context information (like e.g. the instance ID) can be accessed through Unity. This leaves our class design free from clutter.

    I hope you now better understand what we are trying to achieve. The idea is that such a LifetimeManager can use the workflow instance ID as key in a dictionary of values - similar as described here for the PerThreadLifetimeManager: http://tavaresstudios.com/Blog/post/Writing-Custom-Lifetime-Managers.aspx

    Friday, October 8, 2010 12:34 PM
  • Let me see if I can find another way to get equivalent functionality of WorkflowEnvironment.WorkflowInstanceId in WF4, that way you wouldn't have to get it an pass it every single time, like you mention.

    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, October 8, 2010 4:24 PM
  • Let me see if I can find another way to get equivalent functionality of WorkflowEnvironment.WorkflowInstanceId in WF4, that way you wouldn't have to get it an pass it every single time, like you mention.

     

    That would be fantastic!

    Monday, October 11, 2010 11:21 AM
  • Idem : it would de really fantastic if someone could find this Id
    Jérémy Jeanson MVP, MCP, MCTS http://blogs.codes-sources.com/JeremyJeanson/ (French or English spoken)
    Monday, October 11, 2010 12:02 PM
  • Steve, have you found anything?

    I am right now attempting to upgrade our workflows to 4.0 and found this page because of the problems I am having right now. We are running a modified version of Enterprise Library that logs to database on the WorkflowEnvironment.WorkflowInstanceId as ActivityId; if it happends to be running in a workflow. I.e. no possible way with Policy Injection to get the context.WorkflowInstanceId into that loop and send it along every activity/function.

    Since the possibility to follow the logs of an entire workflow is crucial this is quite a showstopper for us.

    Best regards
    Hakan


    // Håkan Andersson
    Thursday, October 14, 2010 6:24 PM
  • Hi guys,
    I haven't fully tested it, but it seems like it might work if you use this wrapper actitivty as the root of your workflow, would you mind trying it out and letting me know if it works ok for you?
    Tim

      [Serializable]
      public class ThreadStaticExecutionProperty : IExecutionProperty
      {
        [ThreadStatic]
        private static Nullable<Guid> threadStaticId;
    
        private Guid serializedId;
    
        public void CleanupWorkflowThread()
        {
          threadStaticId = null;
        }
    
        public void SetupWorkflowThread()
        {
          if (threadStaticId != null)
          {
            throw new Exception("We appear to have already set up the execution property on this thread.");
          }
          threadStaticId = this.serializedId;
        }
    
        public void SetId(Guid newId)
        {
          this.serializedId = newId;
        }
    
        public static Nullable<Guid> ExecutingActivityId
        {
          get
          {
            return threadStaticId;
          }
        }
      }
    
      public class WrapperActivity : NativeActivity
      {
        public Activity Body { get; set; }
    
        protected override void Execute(NativeActivityContext context)
        {
          var property = new ThreadStaticExecutionProperty();
          property.SetId(context.WorkflowInstanceId);
          context.Properties.Add("instancePropertyMaintainer", property);
          if (this.Body != null)
          {
            context.ScheduleActivity(this.Body);
          }
        }
      }
    
    
    
    Thursday, October 14, 2010 10:23 PM
  • Ironically, there is a way to do this if you are using workflow tracking. The default tracking database WorkflowInstanceEvent table has fields for WorkflowInstanceId and WorkflowActivityDefinition - which actually contains the workflow name. It is pretty simple to set up a query on the database to return the workflow name given the workflow instance id. And since the workflow will have been started prior to the activity executing, there should be an event record in the table that holds this association. The code to do this looks something like this:

        public static string GetWorkflowName(Guid workflowInstanceId)
        {
          List<WorkflowInstanceView> list = DataAccess.GetAll<WorkflowInstanceView>().ToList();
          WorkflowInstanceView workflowInstanceView = 
            list.Single<WorkflowInstanceView>(l => l.WorkflowInstanceId == workflowInstanceId);
          return workflowInstanceView.WorkflowActivityDefinition;
        }
    
    
    The DataAccess class referenced here is doing some mapping from Linq to SQL, but this should give you some idea of how to extract this field.
    Ram
    Wednesday, November 24, 2010 9:07 PM
  • Go here for GetWorkflowInstanceId class: http://msdn.microsoft.com/en-us/library/ee943758.aspx

    Then call the class like this: (New GetWorkflowInstanceId()).toString()

    Oops,  (New GetWorkflowInstanceId()).toString() probably will not work, but if you add the GetWorkflowInstanceId class to your cs file, then the  GetWorkflowInstanceId statement should appear (after recompile) as a tool in your xaml toolbox, add the tool to your xaml then you can enter a variable into the result box (properties) of that tool, the variable will have the WF InstanceId Guid.

        public sealed class GetWorkflowInstanceId : CodeActivity<Guid>
        {
            protected override Guid Execute(CodeActivityContext context)
            {
                return context.WorkflowInstanceId;
            }
        }
    Figured it out by looking at the WF_WCF_Samples\WF\Scenario\ActivityLibrary\GetWorkflowInstanceId sample.

    • Edited by rogerperkins Tuesday, December 11, 2012 11:34 PM added after recompile
    Tuesday, November 20, 2012 10:26 PM