me

Syndication:


Google
Web manuelfoerster.net

Categories:

  • Blogroll

  • Calendar

    November 2008
    M T W T F S S
    « Jul    
     12
    3456789
    10111213141516
    17181920212223
    24252627282930

    Archives:

    Custom WorkflowPersistenceServices

    March 10th, 2006 by manu

    WWF Beta 2 comes along with a SqlWorkflowPersistenceService for persisting the state of longrunning workflows. As this service only works with Microsoft SQL Server or the Express Edition, you might think about creating your own persistence service which could target any other storage medium, like other relational databases, files, or - in this case - datasets.

    For a short introduction to the WWF persistence services see [1], a comprehensive overview of a workflow’s lifecycle (and the persistence points) is described in [2].

    As a first simple approach, i tried storing the workflows in a dataset with one table, which consits of the following columns:

    Blocked : boolean
    Info : string
    InstanceID : Guid
    DateTime : DateTime
    Activities : byte[]
    StateID : Guid
    Status : int
    Type : int
    Unlocked : boolean

    To implement a custom persistence service for your workflows, you just have to inherit from WorkflowPersistenceService (in System.Workflow.Hosting) and override the following methods:

    protected override void SaveWorkflowInstanceState(Activity rootActivity, bool unlock);
    protected override Activity LoadWorkflowInstanceState(Guid instanceId);
    protected override void UnlockWorkflowInstanceState(Activity state);
    protected override void SaveCompletedContextActivity(Activity rootActivity);
    protected override Activity LoadCompletedContextActivity(Guid activityId, Activity outerActivity);
    protected override bool UnloadOnIdle(Activity activity);

    where SaveWorkflowInstanceState, LoadWorkflowInstanceState, SaveCompletedContextActivity and LoadCompletedContextActivity are the important ones to fill in.

    There are two main methods for serialization and deserialization:

    private void SerializeActivity(Activity rootActivity, Guid id)
    {
        if (rootActivity == null)
        {
            throw new ArgumentNullException(“rootActivity”);
        }

        PersistentWorkflow pwf = new PersistentWorkflow();
        PersistentWorkflow.WorkflowInstanceRow newRow = pwf.WorkflowInstance.NewWorkflowInstanceRow();

        WorkflowStatus status = (WorkflowStatus) rootActivity.GetValue(WorkflowInstance.WorkflowStatusProperty);
        bool blocked = (bool) rootActivity.GetValue(WorkflowInstance.IsBlockedProperty);
        string info = (string) rootActivity.GetValue(WorkflowInstance.SuspendOrTerminateInfoProperty);
        Guid guid = (Guid)rootActivity.GetValue(Activity.ActivityContextGuidProperty);
        newRow.InstanceID = (Guid) rootActivity.GetValue(WorkflowInstance.WorkflowInstanceIdProperty);
        if ((status != WorkflowStatus.Completed) && (status != WorkflowStatus.Terminated))
        {
            newRow.Activities = WorkflowPersistenceService.GetDefaultSerializedForm(rootActivity);
        }
        else
        {
            newRow.Activities = new byte[0];
        }
        newRow.Status = (int) status;
        newRow.Blocked = blocked;
        newRow.Info = info;
        newRow.StateID = guid;
        newRow.Unlocked = true;
        TimerEventSubscriptionCollection collection1 = (TimerEventSubscriptionCollection) rootActivity.GetValue(WorkflowInstance.TimerCollectionProperty);
        TimerEventSubscription subscription1 = collection1.Peek();
        newRow.DateTime = (subscription1 == null) ? DateTime.MaxValue : ((DateTime) subscription1.ExpiresAt);

        pwf.WorkflowInstance.AddWorkflowInstanceRow(newRow);
        string filename = FBaseDir + \\ + id.ToString() + “.xml”;
        pwf.WriteXml(filename);
    }

    private object DeserializeActivity(Activity RootActivity, Guid id)
    {
        string filename = FBaseDir + \\ + id.ToString() + “.xml”;
        PersistentWorkflow pwf = new PersistentWorkflow();
        pwf.ReadXml(filename);

        byte[] instanceState = ((PersistentWorkflow.WorkflowInstanceRow)pwf.WorkflowInstance.Rows[0]).Activities;

        Activity result = RestoreFromDefaultSerializedForm(instanceState, null) ;

        return result;
    }

    As you can see, deserializing the workflow instance is just calling one method (coming with the baseclass) on the activities entry of the dataset.
    Now you might wonder why the other information is persisted when serializing: you will need when managing long running workflows whose lifetime exceeds that of the application’s instance, maybe i will cover that one in another post.

    Invoking the (de)serialization is done in the four load and save methods you have to override:

    protected override void SaveWorkflowInstanceState(Activity rootActivity, bool unlock)
    {
        SerializeActivity(rootActivity, (Guid)rootActivity.GetValue(Activity.ActivityContextGuidProperty));
    }

    protected override Activity LoadWorkflowInstanceState(Guid instanceId)
    {
        object obj = DeserializeActivity(null, instanceId);
        return (Activity)obj;
    }

    protected override void SaveCompletedContextActivity(Activity rootActivity)
    {
        ActivityExecutionContext contextInfo = (ActivityExecutionContext)rootActivity.GetValue(Activity.ActivityContextGuidProperty);
        SerializeActivity(rootActivity, contextInfo.ContextGuid);
    }

    protected override Activity LoadCompletedContextActivity(Guid activityId, Activity outerActivity)
    {
        object obj = DeserializeActivity(outerActivity, activityId);
        return (Activity)obj;
    }

    So, the framework does the main part of the work, with GetDefaultSerializedForm and RestoreFromDefaultSerializedForm from the WorkflowPersistenceService baseclass.

    For using this service in your application, you just have to add the persistence service to your workflow runtime:

    workflowRuntime.AddService(new DatasetPersistenceService());

    Comments appreciated, but keep in mind that this one was just a first quick and dirty approach, so be gentle. ;-)

    [1] Window Workflow Foundation Runtime Services: The Persistence Service
    [2] Managing Workflow’s Lifecycle

    tags:

    , , , , , , , , , ,

    Posted in windows workflow foundation | | del.icio.us digg

    9 Responses

    1. while(availableTime>0) { : Windows Workflow Foundation Resources Says:

      […] Custom WorkflowPersistenceServices - Another great article by Manuel Foerster on the Persistence Service and writing your own. […]

    2. kaushal Says:

      Thanks for this wonderfull example. But my problem is that i m not able to see SaveCompletedContextActivity and LoadCompletedContextActivity methods getting executed. Please let me know if you know anything about it. I have already tried adding compensatableTransaction scope activity in to my workflow. But still when the scope is completed this two methods are not being invoked.

    3. manu Says:

      kaushal,

      thanks for the feedback!
      unfortunately, i haven’t been working with wwf for the last few months.
      i think there have been major changes in the framework since i posted the example above, originally it was created using beta 1.2.
      however, in the meantime microsoft published a (rudimentary) article on custom persistence services (see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/WF_GettingStarted/html/6b1ed562-952a-4c68-b294-a093503cf454.asp), maybe that will help. if not, please drop me a line.
      perhaps i’ll update my example later this month.

    4. mausam Says:

      hi i am new to workflow foundations. trying to build cusom persistence service.

      in the example by u i find a class “PersistentWorkflow” has been used which has some static members as wel.

      i am not getting the class. i am using NovCTP. can u let me know where do i get that “PersistentWorkflow” class?

    5. manu Says:

      mausam,

      sorry for that, i missed to point out that the dataset i described above would be called ‘PersistentWorkflow’.
      if you need some more hints, let me know.

    6. SonnyM Says:

      Nice article, I have question, as you mentioned GetDefaultSerializedForm and RestoreFromDefaultSerializedForm methods do the serialisation. However, they are limited to only Serializing via a binary formatter. I have a situation where I want to query the work flow state in the data store using a XML type (sql server 2005).

      I’m surprised there is no configuration setting that sets the type of serialisation to use, just like a majority of the .net providers such as the profiler provider (SerializeAs=”Xml”).

      I’ve been surfing the net looking for a scenario where a custom SQL work flow persistence state is used that reads/writes to the underling database, but with no luck. I would much appreciate if you could direct me or post an implementation.

      If your interested I’m planning on doing the below to meet my need
      1) create custom db table to contain the xml string(Serialized object).
      2) 2) custom sql work flow persistence service
      If you have any suggestion please do respond.

      If I’m successful and you’re interested in the solution, I’ll let you know.

      Cheers
      Sonny M

    7. K.Aravind Says:

      Hai,
      I am a Beginner to WWF.
      ur information is not clear.
      can u give a detailed solution.

    8. manu Says:

      hi,

      the article should be a hint on how to implement custom persistence services rather than a complete solution.
      if you have specific questions on that topic, let me know.

    9. nils Says:

      I have a problem with a custom implementation of the Windows Workflow Persistence Service that writes to file. If the host application quits while there are workflow instances on idle states due to delay activity, when loads again I need to manually tell the runtime “WF_Runtime.GetWorkflow(InstanceId)” even when the workflow instance time of delay aren’t finished yet. Doing so makes everything works, but if not the instances never load again even when the delay activity’s time finish. Although if I use an ExternalCall activity the “GetWorkflow”’s call is not necessary. At the same time if I never close the host application both types of activities works fine without the call.
      What do you thing I’m doing wrong?
      The implementation of the file persistence service I’m testing and changing is the one given on the Windows Vista and Framework 3 SDK.

    Leave a Comment

    Please note: Comment moderation is enabled and may delay your comment. There is no need to resubmit your comment.