A question I still get asked frequently is how Microsoft can call the Windows Workflow Foundations (WWF) framework a Workflow solution. The short answer is it’s not. It is a framework. Microsoft’s workflow solution is BizTalk Server (BTS). BTS is a complete solution that provides all of the functionality which is required in a workflow solution. Actually, BTS is more than just a workflow solution but we’ll leave that for another blog.
WWF on the other hand is a framework that enables a developer to develop their own custom workflow solution. Microsoft is so invested in this framework that all product teams in Redmond need to use it for any workflow functionality in their future products. Even the BTS team will rewrite their orchestration engine to be built on the WF framework for the next version. Each product team will integrate the WF framework into their products and they will add any new activities that are specialized for their application.
A very compelling reason for developers to use WF is the price. It’s free! If a developer builds a solution using BTS or Webmethods then valid licenses of those servers need to be purchased for each machine that the application is deployed. If you check the pricing on either of those products you will see that it can substantially increase the overall price tag for the solution.
Developing a solution using WF
When creating an application using Workflow Foundation there are really three major development areas:
Creating the Workflows
This is creating the workflows either programmatically or using the designer UI.
Creating the Runtime Host
WWF is a framework. The developer needs to write code to host the workflow runtime engine. Typically, this code is called the runtime host. Ideally this code is re-used within an organization.
Creating the Reporting Application for the tracking data (optional)
Not all workflow scenarios warrant tracking but when they do it is important to create a reporting application to report on the collected data.
There are a lot of great blogs and white papers on creating workflows. For the rest of this blog I am going to talk about the runtime host and the runtime services.
Workflow Runtime, Runtime Host and Runtime Services
The workflow runtime is in System.Workflow.Runtime.WorkflowRuntime. As stated above, a runtime host is a process that programmatically instantiates the workflow runtime and that then runs the workflow instances. Microsoft will provide runtime hosts for their workflow enabled products and developers will often write their own based on the requirements of the application.
The workflow runtime is configurable and has default runtime services that are used to customize the characteristics of the runtime.
Workflow Runtime Set of classes provided in the WF framework that provide everything to run instances of workflows.
Runtime Host The custom code that configures and instantiates the Workflow runtime.
Runtime Services Additional services that are used to customize the behavior and provide features for the workflow runtime.
This service saves the current state of the workflow. Examples of when this is useful include the host process being stopped or an activity that may take a long time getting a response, like waiting for an approval from a user. In the first case if the state is not persisted then the state would be lost and the workflow would be terminated while in process. In the second case it is desirable to unload the process from memory so that the resources are not held. Once the user issues the approval the process would restart where it left off by getting the state from the persistence store. A workflows state is persisted in a variety of ways that include the host application calling Unload, an activity configured to PersistOnClose or even a custom callback method called from the workflow.
The default Persistence Runtime Service that is supplied with WF is SQLStatePersistenceService. The state is stored ina SQL Server DB. If you are going to use this you need to create the DB and run the scripts SQLPersistenceService_Schema.sql and SQLPersistenceService_Logic.sql provided at <windows>\Microsoft.Net\Framework\v3.0\Windows Workflow Foundation\SQL\EN. Remember that they must also be deployed to all the other environments that you will work in (QA, Prod Support, Production,…).
The persistence service can be added to the runtime instance in two ways. The first is programmatically using code like the following:
WorkflowRuntime workflowRuntime = new WorkflowRuntime();
SqlStatePersistenceService stateservice = new
Or you can use a section in App.Config.
<WorkflowRuntime Name="SampleApplication" UnloadOnIdle="true">
type="System.Workflow.Runtime.Hosting.SqlStatePersistenceService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" ConnectionString="Data Source=localhost;Initial Catalog=WFState;Integrated Security=True;" />
Some things to note are:
· If you are implementing this in an application that will run in the logged in users context you should review if user a SQL application account would be a better option. If you use integrated security then the logged in user will need to have access to the persistence DB.
· The version will need to change if you use .Net 3.5
· Setting these values programmatically, as in the example above, hardcodes them and would make deployment to other environments a nightmare. Consider storing the values in an XML file or DB so they can be easily changed.
Some other attributes that can be defined are:
OwnershipTimeoutSeconds Setting this tells other blocks other workflows from loading the instance for the specified period of time.
UnloadOnIdle When true the service will persist the workflow and unload it from memory.
LoadIntervalSconds Tells the service how frequently to check for expired timers in the workflow.
EnableRetries When true tells the service to retry persisting a workflow if it fails. The default is true. The runtime throws an exception and the host would need to catch and handle it.
The tracking service is used to track the activity in the running workflows. The default tracking service that is provided with WF is SQLTrackingService and this, like the persistence service, requires a SQL Server database. If you are going to use this you need to create the DB and run the scripts Tracking_Schema.sql and Tracking_Logic.sql provided at <windows>\Microsoft.Net\Framework\v3.0\Windows Workflow Foundation\SQL\EN. Remember that they must also be deployed to all the other environments that you will work in (QA, Prod Support, Production,…).
The default behavior is to catch all events from a running workflow so the developer can create custom tracking profiles. A tracking profile tells the service what events to capture and to ignore the rest. They can be created programmatically or as an XML document. They need to be inserted into the tracking database after they are created in order to be used. The database provides the stored procedure UpdateTrackingProfile to accomplish this.
Adding the tracking service to the runtime programmatically is the same as with the persistence service except you use SQLTrackingService instead of SQLStatePersistenceService.
Adding it through App.Config is also the same except there are different attributes to set.
PartitionOnCompletion Partioning the DB provides an efficient method to archive and delete tracking records. Seting this makes the portioning happen at the end of every workflow. Use this for tracking databases that have no scheduled downtime. If this is not set then you need to run manual jobs that will lock records so they need to run during scheduled downtime.
EnableRetries If this is set then record writes to the transaction DB will retry if they fail.
ProfileChangeCheckInterval The tracking profile can be changed at any time and updated in the DB. The interval set here is used by the tracking service to know the frequency to check for changes to the tracking profile. If it detects an update then an event is fired and the new profile is loaded into the profile cache and used.
UseDefaultProfile If true then workflows that do not have a tracking profile will use the default profile.
There are 2 transaction services that come with the WF framework.
DefaultWorkflowCommitWorkBatchService is the default service used unless otherwise specified. What it does is enable the registering of a delegate so that the Transaction service will call back into a method in the workflow host and allow the developer to add custom code. The WF framework does not allow the developer of the runtime host to create their own transactions so they need to customize through this method. The transaction service is responsible for creating transactions for the persistence DB and the Tracking DB if they are on different servers and/or have different connections.
If the Tracking and Persistence database are on the same server and share the same connection then use SharedConnectionWorkflowCommitWorkBatchService.
These can be added either programmatically or through the App.Config file. The attribute EnableRetries when set to true tells the service to retry the commit in case of failure.
There are two scheduler services. One allows workflows to run asynchronously and the other runs them synchronously.
To run them asynchronously use the DefaultWorkflowSchedulerService. When the runtime host starts a workflow the request is put in an internal queue. The scheduler service acquires a thread from the .Net framework thread pool. If there are no threads available then the workflow waits in the queue until a thread becomes available. It is important to note that it is possible for the .Net thread pool to starve. Transaction objects use the .Net thread pool so thread starvation could cause timeouts for the persistence service.
The ManualWorkflowSchedulerService forces the workflow instance to be run on the host instances thread so the host application is blocked until the workflow instance completes or becomes idle. This method is recommended when it is important to conserve the .Net thread pool, specifically when a workflow is instantiated by ASP.Net.
Writing Custom Runtime Services
All services provide abstract classes that a developer can inherit from and implement. This allows the developer to implement whatever their application calls for.