May 2007 - Posts

SSIS Book Published!

My wife called me the other day to let me know that the SSIS book I had contributed to, "Expert SQL Server 2005 Integration Services" had been delivered to the house. As a "Contributing Author", I do not get my pretty picture or name on the cover, but I am still honored that I had the opportunity to help my pals, Brian and Erik!

Pro WF: Windows Workflow In .Net

I want to share some insight about to a new book that I have been reading for the past month now that has helped me tremendously with WF topics that I felt I needed some clarification on. Bruce Bukovics’, Pro WF: Windows Workflow In .Net 3.0 book has everything you need to get up to speed on WF. This book has 684 pages of interesting knowledge on WF. It also has complete code included so there are no misunderstandings as to the direction of the topic! You might even get out of your recliner after reading a couple of pages and feel like Neo, wanting to use Kung fu!

Load WF State Transitions For The WF Host

When working with state machine workflows there is a need to notify the WF Host, of possible transitions or events that can be rendered, based on the current state of the workflow. This way the host can react to the workflow’s state rather than having to embed separate responsive logic within the host application. This can easily be accomplished by using the StateMachineWorkflowInstance object.

Consider a windows state machine workflow host that controls the flow of the workflow by firing events from different buttons on the form. Each button has an ID of an activity's ID represented within workflow. So now that each button represents the states of the workflow, let’s take a closer look and analyze the code below.

In the following method, UpdatePossibleStateTransitions, the System.Workflow.Activities.StateMachineWorkflowInstance is instantiated by passing in the running WorkflowRuntime and the running WorkflowInstanceId for the workflow instance that I am interested in retrieving possible transitions. The read-only generic collection, System.Collections.ObjectModel.ReadOnlyCollection is the object type that is set with current Workflow State’s possible state transitions, from the StateMachineInstance.PossibleStateTransitions object.

private void UpdatePossibleStateTransitions()
{
     Button cmd;
     StateMachineWorkflowInstance StateMachineInstance =
          new StateMachineWorkflowInstance(_wfRuntime, _wfInstance.InstanceId);
 
     System.Collections.ObjectModel.ReadOnlyCollectionstring> PossibleStates
          = StateMachineInstance.PossibleStateTransitions;
 
 
     foreach (Control cntrl in this.Controls)
     {
          if (cntrl is Button)
          {
              cmd = (Button)cntrl;
              if(cmd.Name!=cmdStopRuntime.Name)
                    cmd.Enabled = false;
 
              foreach (string PossibleState in PossibleStates)
              {
                  if (PossibleState == cmd.Name.Substring(3, cmd.Name.Length-3))
                      cmd.Enabled = true;
              }
          }
     }
}

Now that the possible state transitions are loaded based on the current state of the workflow, the code loops through the controls on the form and finds only the controls that are Type Button. First all the buttons on the form are disabled except the one that actually stops the workflow runtime, and then the possible state activities are looped through matching buttons that have the same name as the iterated activity name, and then enabling those buttons that can be transition to as next possible transition(s).

There are two critical opportunities for getting possible state transitions. When a workflow instance is loaded from a persisted state and while the workflow instance is running. The above code can be called after a workflow instance is loaded, however if the workflow is never persisted, events need to be fired from the workflow to make the host aware that the state has changed.

Retrieving Peristed Data Sent to Workflows

There are two ways to communicate information with a workflow through its hosted runtime. First, data can be sent to the workflow via parameters. When a workflow starts up and runs it can process that data sent to it, however during the workflow’s lifecycle, the initial data sent to the workflow can change. With short-running workflows where the workflow host continues to run while the workflow finishes, the workflow throws a “workflow completed” event and passes the processed data to the host.

The second way to pass data to a workflow is through events, and if the workflow is again short-running, then you could use CallExternalMethod Activities to raise local service events to the hosted runtime to pass back processed data. But what if the workflow is long-running? What happens if multiple applications need to re-hydrate the workflow for processing? Is data sent to the workflow via events persisted too? Sure it is… and it is very easy to retrieve.

So let’s already assume that you have need for a long running workflow that will be persisted by default when it becomes idle. You have also created a local service with events and added it to the host to communicate with workflows, by passing “args” to it. Make sure that Parameters “e” property (found in the property window) for each HandleExternalEvent activity that implements an event from the local service is set to a local workflow variable of its type. Next, when the “SQLWorkflowPersistenceService” service is added to the runtime, it should have a reasonable lock-time on each persisted instance (about 5 minutes or more), and call, “StopRuntime()” to STOP the runtime rather than just killing the runtime host during debugging, to avoid situations when trying to unload workflows that the runtime still thinks are in memory. There is a workflow method that is called each time that it loads called, “OnActivityExecutionContextLoad”, and a property called, “UserData” that all activities derive from that objects can be added.

The following code verifies if the eventargs, local property in the workflow has been set. If so, it adds the property to a collection and gives the item a distinct key. In this case, it makes more since to make the key the same as the instanceID of workflow instance.

private Movie.RentalArgs _rental; //Property set by HandleExternalEvent activities
 
public Movie.RentalArgs Rental
{
     get { return _rental; }
     set { _rental = value; }
}
 
protected override void OnActivityExecutionContextLoad(IServiceProvider provider)
{
     if (_rental != null)
        this.UserData.Add(_rental.InstanceId, _rental);
}

Finally, the workflow host should check to see if there are any persisted workflow instances. When a persisted instance is identified, the following code shows how to get the workflow definition, to get to the “UserData” property that was set when the workflow loaded.

private void LoadPersistedWorkflows()
{
    Activity Act;
    IEnumerable PersistedInstances = null;
 
    PersistedInstances = _persistanceService.GetAllWorkflows();
 
    foreach (SqlPersistenceWorkflowInstanceDescription Instnc in PersistedInstances)
    {
        _wfInstance = _wfRuntime.GetWorkflow(Instnc.WorkflowInstanceId);
        Act = wfInstance.GetWorkflowDefinition();
        _rental = Act.UserData[_wfInstance.InstanceId] as Movie.RentalArgs;
    }
}

Retrieval of data that is initially sent to workflows can be important. Expecially if that data could change over the lifecycle of the workflow. In some cases, it may be appropriate to store this data outside of the workflow, however you still may need to populate screens with data from the workflow, based on the state of workflow.