Click here to Skip to main content
12,818,816 members (28,917 online)
Click here to Skip to main content
Add your own
alternative version


73 bookmarked
Posted 24 Mar 2007

Enhanced Document workflow approval v2

, 24 Mar 2007
Rate this:
Please Sign up or sign in to vote.
This Article supporting these features: Logging to file, TransactionScopeActivity, WorkflowCommitBatchService, SqlTrackingService, fault Handler Activity and Bi-Directional communication using custom services

The purpose of the article

Demonstrating these extra features

• Moving all interfaces in 1 assembly and all other projects will reference this assembly.

• Registering the services "out of the box and custom services" in application configuration file for better maintenance.

• Using "out of the box" system diagnostics LogToFile utility .

• Using "out of the box" WorkflowCommitBatchService, internally with using TransactionScopeActivity

and preparing our document service to work with batches and transactions.

• Using Fault Handler Activities to handle workflow internal errors.

• Bi-Directional communication, workflow with the host, through the custom service.

• Using "out of the box" SqlTrackingService, for tracking the workflow

• Using SqlTrackingService utility for query the tracked workflow.

If you did not read, the first article Document approval workflow system

I encourage you to read the article, and to run it, because this article is enhanced version for better design,

and for more workflow services.

To run this Application

• You should install .Net 3 released version

• you should have Visual studio extension for windows workflow

• This version of the application using SQL Server 2000

• You should create a database for SQL persistence service and for SQL Tracking service These scripts, under

C:\$windows folder$\Microsoft.NET\Framework\v3.0\Windows Workflow Foundation\SQL\EN

• For SQLPersistenceService you may need to run the Distributed Transaction Coordinator service

from control panel -> Administrative tools->services choose Distributed Transaction Coordinator service and run it if it is not running

• For transaction service you should to configure your component service,

from control panel -> Administrative tools->Component service then choose computers -> MyComputer

then with the context menu choose properties and in default properties tab tick Enable distributed com

on this computer and from MSDTC tab choose tick use local coordinator If you run any workflow

samples using SQLPersistenceService with TransactionScopeActivity so you have no need to

do any of the previous settings , and you may to do only the next settings

• You should create the application database that is in download source file

• Then change the app.config file to point to the new name of database or leave as it is if you choose

workflowDB the default database name for persistence and tracking database and DocumentDB for application database

• You may need to populate the Document database with some data to

initialize the users and groups that will appear in Client and Administration application,

so read the readme.txt in the package (zip file)

Features in more detail

• Moving all interfaces in 1 assembly and all other projects will reference this assembly.

This topic was mentioned in detail in the first article,

the Idea is simple, try to put your workflow and all workflow helper classes in separate project,

then separate all interfaces that your workflow need in separate project ,

finally write all your core application code to implement and to use the interfaces,

not the concrete classes so, if you did that

you will achieve 2 main benefits
• You will avoid circular reference, because the workflow will use services in the core application,

and the core application will use the workflow, so the compiler will not allow you to do that.

• General concept for programming using the interfaces makes your application more dynamic and support versioning.

Note, some cases you may use abstracted class rather than interface, in some situations using the

abstracted classes for your basic library is much better than interface, it is out of this article scope or

always use abstracted classes then wrap it with interfaces in separate assembly

"I do that very often" to be used in client classes,

But the concept is the same "the abstracted classes and interfaces should always be away

from the core library or business logic projects".

Figure 1 DocumentBaseLibrary has all application interfaces and abstracted classes and enumerable data types

Figure 2 Class diagram of interfaces in DocumentBaseLibrary

Figure 3 abstracted classes in DocumentBaseLibrary

Registering the services "out of the box and custom services" in application configuration

for better maintenance, You may register the workflow runtime in the app.config and name this section with any name and user the

Workflow Runtime constructor to use it App.config sample
            System.Workflow.Runtime, Version=3.0.00000.0,
            Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <add name="documentDB"
        connectionString="Data Source=(local);Initial Catalog=documentDB;Persist Security Info=True;User ID=sa;Password=sa"
        providerName="System.Data.SqlClient" />
    <add name="workflowDB"
        connectionString="Data Source=(local);Initial Catalog=workflowDB;Persist Security Info=True;User ID=sa;Password=sa"
        providerName="System.Data.SqlClient" />
            <add name="System.Workflow.Runtime" value="All" />
            <add name="System.Workflow.Runtime.Hosting" value="All" />
            <add name="System.Workflow.Runtime.Tracking" value="Critical" />
            <add name="System.Workflow.Activities" value="Warning" />
            <add name="System.Workflow.Activities.Rules" value="Off" />

            <add name="System.Workflow LogToFile" value="1" />
      <add name="ConnectionString"
           value="Data Source=(local);Initial Catalog=WorkflowDB;
                     Integrated Security=true"/>
      <add type=
         System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral,
        maxSimultaneousWorkflows="3" />
              System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral,
              PublicKeyToken=31bf3856ad364e35" UnloadOnIdle="true" />
      <add type=
              System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral,

It is good Idea to create a WorkflowHelper Class that is singleton or static class has only static members and has different

WorkflowRuntime properties to simplify the workflow runtime initialization and to avoid running more

than workflow runtime instance, or to load the workflow from different configuration sections

This class should encapsulate all accessing to workflow sections, and responsible to initiate the workflow instance

Using "out of the box" system diagnostics LogToFile .

LogToFlile is configurable diagnosis utility, under system.diagnostics section in app.config

After you run the application you will find a log file created called WorkflowTrace.log

beside the text file, and that is the sample of this file result

System.Workflow.Runtime Start: 0 :
Workflow Runtime tracer is alive! System.Workflow.Runtime.Hosting Information: 0 :
WorkflowRuntime: Created WorkflowRuntime 7816a986-75e8-4383-81dc-033c17ce8055 System.Workflow.Runtime.Hosting Information: 0 : WorkflowRuntime: Starting WorkflowRuntime 7816a986-75e8-4383-81dc-033c17ce8055 System.Workflow.Runtime.Hosting Information: 0 : SqlWorkflowPersistenceService(00000000-0000-0000-0000-000000000000): Starting, LoadInternalSeconds=120 System.Workflow.Runtime.Hosting Information: 0 :
DefaultWorkflowCommitWorkBatchService: Starting System.Workflow.Runtime.Hosting Information: 0 : SqlWorkflowPersistenceService OpenConnection start: 03/20/2007 19:09:19 System.Workflow.Runtime.Hosting Information: 0 : SqlWorkflowPersistenceService. OpenConnection end: 03/20/2007 19:09:20 System.Workflow.Runtime.Hosting Information: 0 : SqlWorkflowPersistenceService.RetrieveNonblockingInstanceStateIds ExecuteReader start: 03/20/2007 19:09:20 It is not fair to compare workflow logging utilities and workflow tracking services The Logging utility will help you in programming time to debug the workflow (the track service will do that as well) the result will be written in plain file, so it will be quite hard to extract any information , or to run queries against this file, and this file should be very large. You should purge this file regularly So logging is not reliable neither customizable like tracking service, so It is good Idea if you enabled the Logging utility in debugging mode only, and stop it in release mode using #if DEBUG directive

Using "out of the box" WorkflowCommitBatchService

using this service internally using TransactionScopeActivity and preparing our document service

to work with batches and transactions.

Working with transaction in workflow is something very important, it is essential especially in real world workflow scenarios,

if any step failed and all the dependent steps not rolled back you will suffer the data inconsistency in your application.

Few programmers design their workflows and finish the applications, then after they finish the basic test

they begin adding transactional services and fault handle activities.

It is not bad idea as long as your application will support the transaction and has a well "Disaster Recovery Plan"

before the application deployed in production server.

Now, the problem of transaction came to our workflow because the fact of using more than 1 database

in workflow applications at least 1 database for the SQLPersitanceService

and at another (or more) for the application itself ( by the way you can let the tracking service participate in transaction

as well and may be had another database) So the Work flow foundation gives you a very handy tool

for to manage transaction without explicitly using transaction object, or distributed transaction service,

simply you will drag the TransactionScopeActivity and put your activities inside it.

As long as you will not put any activity make the workflow idle "like long delay activities or listener activities"

And remember that you could not make the transaction opening for such long time.

Then your custom service should implement IPendingWork
public interface IPendingWork
        void Commit(Transaction transaction, ICollection items);
        void Complete(bool succeeded, ICollection items);
        bool MustCommit(ICollection items);
Then all your method should do nothing but save all application request information (method name, parameters)

to Workbatch object and in the Commit method you will perform the real actions Note that if you are working with

SQL Server 2000, by default all your database connection will be Enlisted the global transaction,

so do not use connection.EnlistTransaction() method,

it will through exception because it should belongs

to one transaction only, if you want to check the current global transaction simply put break point in any

method and test this command in Immediate window System.Transactions.Transaction.Current

Just to be confident that your own code is working in transaction

Now that is sample of Document TransactionalService code
    public class DocumentTransactionalService : IDocumentService, IPendingWork
public void CreateInWorkflowDocumentForUser(IDocument document, string approvalUser, Guid workflowID)
            Request req = new Request();
            req.RequestType = RequestType.CreateInWorkflowDocumentForUser;
            req.Document = document;
            req.ApprovalUser = approvalUser;
            req.WorkflowID = workflowID;
            WorkflowEnvironment.WorkBatch.Add(this, req);
public void ApproveDocumentWorkflow(IDocument document, string approvalUser, Guid workflowID)
            Request req = new Request();
            req.RequestType = RequestType.ApproveDocumentWorkflow;
            req.Document = document;
            req.ApprovalUser = approvalUser;
            req.WorkflowID = workflowID;
            WorkflowEnvironment.WorkBatch.Add(this, req);

public void Commit(System.Transactions.Transaction transaction, System.Collections.ICollection items)
            foreach (Request request in items)
                switch (request.RequestType)
                    case RequestType.ApproveDocumentWorkflow:
                        ApproveDocumentWorkflowInTransaction(request.Document, request.ApprovalUser, request.WorkflowID);
                    case RequestType.CreateInWorkflowDocumentForGroup:
                        CreateInWorkflowDocumentForGroupInTransaction(request.Document, request.ApprovalGroup, request.WorkflowID);
                    case RequestType.CreateInWorkflowDocumentForUser:
                        CreateInWorkflowDocumentForUserInTransaction(request.Document, request.ApprovalUser, request.WorkflowID);
                    case RequestType.RejectDocumentWorkflow:
                        RejectDocumentWorkflowInTransaction(request.Document, request.ApprovalUser, request.WorkflowID);
                    default: throw new NotSupportedException(request.RequestType.ToString());

        public void Complete(bool succeeded, System.Collections.ICollection items)
            // nothing to clean

        public bool MustCommit(System.Collections.ICollection items)
            return true;

Testing the Transactional service: The simplest way to proof that your workflow is transactional,

simply throw an unhandled exception after any activity that made changes in database ,

and double check this exception with registering WorkflowTerminated event of workflowRuntime.

Then you will see that all changes rolled back again.

In our sample the Purchase branch has unhandled throw activity, this activity throw an Exception of the type

System.InvalidOperationException, although I put fault handler to handle this error but I re threw it again,

Then you will see how the document will be rolled back to open state despite the changes that happened

with the first activity that kicked him in workflow in purchase group.

Figure 4 Throw activity1 will halt the workflow however the workflow will

rollback all works that be made through the activity in ApproveFromPurchaseGroup branch

Using Fault Handler Activities to handle workflow internal errors.

Fault handler is exactly catching and handling exception in simple c# application,

the only advanced features in the workflow designer simply is the fault handler designer, otherwise is the same.

So whatever you know in catching exception is c# code you can apply it in fault handling,

and that is the simple guidelines to use fault handlers

• Never catch exception without good handling plan, it is better to leave it and the host will handle it

or will leave it as unhandled exception

• Avoid Catching generic Exception like System.Exception always use multiple catch blocks,

and let it be ordered from the most specific exception to the most generic exception Like this code
            catch (OperationCanceledException oEx)
                // code to handle this specific exception
            catch (NotSupportedException nEx)
                // code to handle this specific exception
            // ---
            // ---
            catch (Exception ex)
            // generic exception that all code will come here if it is not fell down in any other exceptions

There are many other design guidelines in exceptions like wrapping exceptions with custom exceptions

and log the tech. Exceptions and add the friendly message to application exception,

all these detail is out of this article scope.

Now you can map the workflow fault handlers with the previous Exception code

First you should select the fault handler designer like in figure 5.

Figure 5 fault handler designer

Note that not all activities support fault handlers, only Condition branches and other activities

support fault handlers, Then you should drag the fault handler activity in the fault handler design

(you can add more than default handler, each one can handle "catch'" different exceptions and select from the story board one activity handler)

Figure 6

you can choose 1 FaultHandlerActivity from story board, and add your activity to handle this specific exception (fault)

Second step you should choose the Exception that your handler will handle from FaultHndlerActivity

Figure 7 FaltType is representing a specific Exception Type

If you choose System.Exception you will handle ALL Exceptions but it is not

good Idea at all, you should as possible put handlers for specific faults.

Transaction and fault handling:

If you are in transaction scope and you caught the exception and did not threw it again,

the transaction will commit partially so, you will end up with in consistence data So be careful with handling

exceptions, in case of transactions. In the code sample, I made up 2 fault branches and I emulated the error through

throw exceptions, and both under global transaction, one of them was handled and re threw again, and the other was handled and

absorbed, so in the second scenario we ended up with data inconsistency,

so you should roll back the transaction or throw it again

Bi-Directional communication, workflow with the host, through the custom service.

The workflow should communicate with the host with any way, but we know there are basic of steps

that we expect if an Object A will communicate with an Object B.

First we have to create a channel or queue to pass (Queue) the data,

Then we have to notify the client that the data is ready or available One of the communication

techniques is using WorkflowQueuingService, it is ready to use built in services, But I will focus in this topic about

the custom service, and how you can use it as Bi-direction communication You can let the client Notify the workflow

with any change and sending the data that the workflow wants And you can let the workflow notify

the client with any change and supplying the client with any data that he wants

Let us examine the both scenarios:

• Notify the workflow with changing from the Client:
You can do that with using DataExchangeService , simply decorate your Service interface with this attribute [ExternalDataExchange] Like in
the sample below
    public interface IDocumentService

        #region events to fire methods for workflow
        event EventHandler<externalexternaldocumenteventargument __designer:dtid="562949953421586"> RaiseApproveDocumentWorkflowEvent;
        event EventHandler<externalexternaldocumenteventargument> RaiseRejectDocumentWorkflowEvent;
/// other code

And use special events that Generic with classes inherits from ExternalDataEventArgs
    public class ExternalExternalDocumentEventArgument:ExternalDataEventArgs
/// other code

Then you may add ListenActivity in your workflow and waiting for the client to raise event,

you have to register the workflow for these specific events through HandleExternalEventActivity

Figure 8 Listener

Each HandleExternalEventActivity, will use one event that has a ExternalDataEventArgs class,

for any interface decorated with [ExternalDataExchange]

• Notify the client with changing from the workflow:

The beauty of the workflow service that you can access this service any where as long as you can access

WorkflowRuntime Class so you can use the GetService<>() method to get a reference for the service,

then you will use the service like any object that you already used to. For example,

I created method and events that will be raised internally in the Classes that will implement IDocumentService
    public interface IDocumentService

// code

        #region events to used in the host, to track what is happening in workflow
        event EventHandler<documenteventarguments __designer:dtid="562949953421612"> OnCreated;
        event EventHandler<documenteventarguments> OnUpdated;
        event EventHandler<documenteventarguments> OnArchived;
        event EventHandler<documenteventarguments> OnDeleted;
        event EventHandler<documenteventarguments> OnSentToWorkflow;
        event EventHandler<documenteventarguments> OnApproved;
        event EventHandler<documenteventarguments> OnRejected;
        event EventHandler<documenteventarguments> OnError;

Then I raised this events inside DocumentTransactionalService protected void
CreateInWorkflowDocumentForUserInTransaction(IDocument document, string approvalUser, Guid workflowID)
            DocumentDalc.CreateInWorkflowDocumentRecordForUser(document, workflowID, approvalUser);
            if (this.OnSentToWorkflow != null) OnSentToWorkflow.Invoke(this, new DocumentEventArguments(document, "InWorkflow"));

So you can let the client register this event to notify him, when the document will be sent to workflow,

So, you can simply create events as much as you can in the custom services, and make the client be

notified with any change May be you need to pass the workflow id for tracking workflow instance purpose,

you can custom your event argument as you like
_service = WorkflowHelper.DocumentService as DocumentTransactionalService;
            _service.OnCreated += ReceiverEvents.ServiceEvents;
            _service.OnSentToWorkflow += ReceiverEvents.ServiceEvents;
            _service.OnError += new EventHandler<documenteventarguments __designer:dtid="562949953421622">(_service_OnError);
            return wr;

Using "out of the box" SqlTrackingService, for tracking the workflow

Tracking service is one of the most important services in workflow, that is for simple reason that the workflow state is

saved as binary data in database, and after the workflow instance completed , it will be removed from InstanceState

table in database, and there is no logging or datahistory for InstanceState data in the database So, you need to

track the workflow events, before and after workflow instance be completed. You can use "out of the box" ,

SqlTrackingService And it is ready to use, just create the database " you may choose the same workflowDB as in the

sample" You can use profile to track specific events, or leave it with default profile, so you will track all events You

can make your tracking service became transactional The target of this article is giving you a very quick look to

tracking service, through registering the SqlTrackingService in app.config file

                  System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral,
This service is using SQL Server database, by default it will use the database in common parameter inside the WorkflowRuntime section
          <add name="ConnectionString"
           value="Data Source=(local);Initial Catalog=WorkflowDB;
                     Integrated Security=true"/>

The tracking service basically track 3 main events

• User Events.

• Workflow Events.

• Activity Events.

It is, obvious that the database will grow very quick not like the persistence service that purge each workflow

after each workflow ended. This problem was solved through Table partitioning property In tracking service,

You can configure the tracking service to be partitioned every month or year or any period that you wish.

In the application sample I used the minimum SQLTrackingService configuration, just to run the Queries

against the tracking service and to taste the beauty of this service.

Now to run the Tracking service reports, first run the Administration application and choose from the menu

That is the code for running the Tracking queries
private IList<UserTrackingRecord> GetUserEvents(Guid instanceID)
        SqlTrackingQuery sqlTrackingQuery = new SqlTrackingQuery(WorkflowHelper.TrackingConnectionString);
        SqlTrackingQueryOptions ops = new SqlTrackingQueryOptions();
        SqlTrackingWorkflowInstance wftrackInstance;

        sqlTrackingQuery.TryGetWorkflow(instanceID,out wftrackInstance);
        return wftrackInstance.UserEvents;
    private IList<ActivityTrackingRecord> GetActivityEvents(Guid instanceID)
        SqlTrackingQuery sqlTrackingQuery = new SqlTrackingQuery(WorkflowHelper.TrackingConnectionString);
        SqlTrackingQueryOptions ops = new SqlTrackingQueryOptions();
        SqlTrackingWorkflowInstance wftrackInstance;

        sqlTrackingQuery.TryGetWorkflow(instanceID, out wftrackInstance);
        return wftrackInstance.ActivityEvents;
    private IList<WorkflowTrackingRecord> GetWorkflowEvents(Guid instanceID)
        SqlTrackingQuery sqlTrackingQuery = new SqlTrackingQuery(WorkflowHelper.TrackingConnectionString);
        SqlTrackingQueryOptions ops = new SqlTrackingQueryOptions();
        SqlTrackingWorkflowInstance wftrackInstance;

        sqlTrackingQuery.TryGetWorkflow(instanceID, out wftrackInstance);
        return wftrackInstance.WorkflowEvents;


To work with windows workflow foundation, in real application, You can use a lot of "out of the box"

Services that be introduced by Windows workflow foundation frame work To take off the burden of coding

the transactional database commands and tracking Database, or other useful services


This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


About the Author

Refky Wahib
Software Developer (Senior) NSW Curriculum & Learning Innovation Centre
Australia Australia
I am a senior developer self taught,
the main study is electronics and communication engineering

I am working as senior programmer in center for learning and innovation

I develop Software since 1995 using Delphi with Microsoft SQL Server

before 2000, I didn’t like Microsoft tools and did many projects using all database tools and other programming tools specially Borland C++ Builder, Delphi, Power Builder
And I loved Oracle database for its stability until I was certified as Master in Database Administration from Oracle University.

I tried to work in web programming but I felt that Java is hard and slow in programming, specially I love productivity.

I began worked with .Net since 2001 , and at the same time Microsoft SQL Server 7 was very stable so I switched all my way to Microsoft Tech.
I really respect .Net Platform especially in web applications

I love database Applications too much
And built library with PowerBuilder it was very useful for me and other developers

I have a wide experience due to my work in different companies
But the best experience I like in wireless applications, and web applications.
The best Application I did in my life is Novartis Marketing System 1999 it takes 8 months developing with PowerBuilder and Borland C++, SQL Server
Performance was the key challenge in this Application.
The other 2 applications that I loved Multilingual Project in Scada company in Italy 2000 and SDP Mobile media content platform server for ChinaUnicom 2004
I hope that you enjoy any Article I post.
God bless you.

You may also be interested in...


Comments and Discussions

QuestionUnable to open database connection Parameter name: connectionString Pin
msanaei25-Nov-12 22:36
membermsanaei25-Nov-12 22:36 
QuestionMultiple Approval Groups Pin
mGOLD_12320-Nov-12 18:15
membermGOLD_12320-Nov-12 18:15 
GeneralNetwork access for Distributed Transaction Manager (MSDTC) has been disabled. Pin
Member 25699494-Mar-10 10:49
memberMember 25699494-Mar-10 10:49 
GeneralRetrieveNonblockingInstanceStateIds Pin
ijanzz10-Aug-09 0:25
memberijanzz10-Aug-09 0:25 
GeneralGetting error when tracking service menu is clicked Pin
ajaysarangi23-Jul-08 3:22
memberajaysarangi23-Jul-08 3:22 
Questionwindows workflow in a web based library circulation system and using visual studio 2005 Pin
sohair zaki17-Jul-08 18:45
membersohair zaki17-Jul-08 18:45 
GeneralStateMachine Workflow for document approval Pin
Pooja Bajaj22-Jun-08 21:13
memberPooja Bajaj22-Jun-08 21:13 
GeneralRe: StateMachine Workflow for document approval Pin
farrukhahmedqureshi1-Jul-12 21:20
memberfarrukhahmedqureshi1-Jul-12 21:20 
GeneralNeed Some Answer !!!! Pin
wildabdat26-May-08 1:25
memberwildabdat26-May-08 1:25 
GeneralDelay in Service Pin
some1shamsi19-Feb-08 2:15
membersome1shamsi19-Feb-08 2:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.170308.1 | Last Updated 24 Mar 2007
Article Copyright 2007 by Refky Wahib
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid