Workflow Hosting


Workflows can be hosted in any type of .NET application, which opens up myriad possible scenarios for workflow-enabled software. The flexible workflow architecture allows individual workflows and their respective instances to be hosted in multiple types of applications across their execution lifecycle. Just as with classes in .NET, workflows have a definition and can have any number of instances of a definition.

A workflow instance might be started from within a Windows Forms application when a user enters data and clicks a button. Next, the workflow may require some interaction with an ASP.NET web form. The same workflow instance and its associated state and context are used in the Windows Forms and ASP.NET applications, which are known as the hosts. The workflow runtime enables hosting in applications.

The Workflow Runtime

The workflow runtime is the gateway between the host application and workflow instances. Even though workflow is the star of the show, the runtime plays a very important role in workflow lifecycle management.

The WorkflowRuntime Class

The System.Workflow.Runtime.WorkflowRuntime class represents the workflow runtime and exposes a great deal of functionality to manage the runtime environment. Using this class, you have complete control over the execution of workflow instances and the runtime itself.

The WorkflowRuntime class is responsible for the following important tasks:

  • Managing the workflow runtime

  • Starting and managing runtime instances

  • Managing runtime services

  • Handling runtime events

Managing the Workflow Runtime

Obtaining a reference to this important class is quite simple. You just create a new instance like any other .NET class, as shown in the following code:

  // create a runtime instance WorkflowRuntime theRuntimeInstance = new WorkflowRuntime(); 

As previously mentioned, the workflow runtime can be hosted in any .NET application or application domain (AppDomain). Because AppDomains and threads are recurring and important themes related to Windows Workflow Foundation, these concepts are covered in the following section.

AppDomains, the Common Language Runtime, and Threads

An AppDomain is a concept specific to the .NET Common Language Runtime (CLR). AppDomains provide an environment for the secure and safe execution of managed code. Just like a process is the smallest unit of isolation on the Windows operating system, an AppDomain is the smallest unit of isolation within the CLR. AppDomains are located within processes and have a one-to-many relationship with them.

AppDomains provide execution isolation and boundaries. This means that code running within one AppDomain cannot affect, adversely or otherwise, code or memory in another AppDomain or process. (You can take deliberate steps to enable an AppDomain to affect the execution of code outside its boundaries, but that is outside the scope of what is discussed here.)

Threads define a further level of code execution. There can be multiple threads in a process and within an AppDomain. A thread does not belong to one AppDomain - it can be executing in one AppDomain one minute and another the next minute, but it can be actively executing within only one application domain at a time. See Figure 5-1 for a representation of the .NET execution model.

image from book
Figure 5-1

Generally, .NET developers are not concerned with the management of application domains. The CLR always creates a default application domain for a .NET application in which a developer’s code runs. However, the .NET class libraries expose a class called AppDomain for application domain management and manipulation. This class also enables you to create new application domains. For example, you may want to create a new application domain for code that is long running, unstable, or both. This provides a level of stability to other code running on the system and in the same process.

Runtime Management

The WorkflowRuntime class exposes two public methods that relate the management of the workflow runtime itself: StartRuntime and StopRuntime.

StartRuntime causes a couple of important actions to take place. First, there are core runtime services that must always exist in a running workflow runtime: a workflow transaction service and a workflow scheduler service. When StartRuntime is called, a check is performed to see if either of these two services has been manually added to the runtime. If not, the runtime creates default instances of each service type. The default class for the transaction service is DefaultWorkflowTransactionService, and the default class for the scheduler service is DefaultWorkflowSchedulerService. After the services have been successfully instantiated and added to the runtime, each service is started with its Start method. In addition to the service configuration that occurs during the runtime startup process, the IsStarted property of the runtime is set to true, and the Started event is raised.

Calling the StopRuntime method has an opposite effect. All services are stopped, all workflow instances are unloaded, the IsStarted property is set to false, and the Stopped event is raised.

Starting and Managing Workflow Instances

One of the most important tasks the workflow runtime can perform is starting workflow instances. In addition to starting instances, the runtime exposes functionality for managing them.

To start a workflow instance, simply call the CreateWorkflow method of your WorkflowRuntime instance. There are several overloads to this method, but the one most commonly used takes a Type instance, which represents a workflow class type. For example:

  // MyWorkflow is a workflow definition class Type workflowType = typeof(MyWorkflow); // use the workflow runtime to create an instance of MyWorkflow WorkflowInstance workflowInstance =    theRuntimeInstance.CreateWorkflow(workflowType); 

Although the preceding code creates a workflow instance, it does not actually start the workflow. The Start method of the WorkflowInstance class does that, as shown here:

  // start the workflow instance! workflowInstance.Start(); 

In addition, if the StartRuntime method of the WorkflowRuntime class has not yet been called, it is called when a workflow instance start is attempted.

Managing Runtime Services

The WorkflowRuntime class plays an important role in managing runtime services. A runtime service is a class that inherits from the System.Workflow.Runtime.Hosting.WorkflowRuntimeService class and provides functionality related to runtime management. These services run in the background of the workflow runtime, remaining generally invisible to users.

Several runtime services are provided out of the box for transactions, workflow persistence, tracking, threading, workflow communication, and more. These runtime services are covered in detail in Chapter 7.

To enable runtime classes in a host application, you need to add them to the workflow runtime class. The following code is an example of how to do this:

  // create an instance of the SqlWorkflowPersistenceService class SqlWorkflowPersistenceService sqlPersistence =    new SqlWorkflowPersistenceService(); // create a runtime instance reference WorkflowRuntime theRuntime = new WorkflowRuntime(); // add the persistence service to the runtime theRuntime.AddService(sqlPersistence); // start the runtime theRuntime.StartRuntime(); ... 

As you can see, the workflow runtime exposes a method called AddService that takes a workflow runtime service instance as its sole argument. Conversely, the runtime contains a method for removing services, called RemoveService, as shown here:

 // start the runtime theRuntime.StartRuntime(); ... // remove the SqlWorkflowPersistenceService from the runtime theRuntime.RemoveService(sqlPersistence); 

In addition, the workflow runtime exposes methods for obtaining references to services already added to the runtime. If you need to obtain a reference to one specific type of service, use the GetService method, as follows:

  // obtain a reference to the SQL persistence service by specifying its type SqlWorkflowPersistenceService sqlPersistence =    theRuntime.GetService(typeof(SqlWorkflowPersistenceService)); // you can also use the generics overload to get the runtime service you want sqlPersistence = theRuntime.GetService<SqlWorkflowPersistenceService>(); 

The GetService method works only if there is one instance of the service type. If you try to call GetService for a service type that has two or more instances added to the runtime, an Invalid OperationException is thrown, as follows:

  WorkflowRuntime workflowRuntime = new WorkflowRuntime(); MyRuntimeService service1 = new MyRuntimeService(); MyRuntimeService service2 = new MyRuntimeService(); workflowRuntime.AddService(service1); workflowRuntime.AddService(service2); // the following line will throw an exception MyRuntimeService serviceReference =    workflowRuntime.GetService(typeof(MyRuntimeService)); 

If you need a list of all runtime services, or if there is more than one service of the same type currently in the runtime, use the GetAllServices method. For example, the following code returns only the runtime services that are added by default after starting the WorkflowRuntime instance:

  WorkflowRuntime workflowRuntime = new WorkflowRuntime(); workflowRuntime.StartRuntime(); // the following line will retrieve all runtime services // notice the Type we are passing is WorkflowRuntimeService // which is the base class for all runtime services System.Collections.ObjectModel.ReadOnlyCollection<object> services =    workflowRuntime.GetAllServices(typeof(WorkflowRuntimeService)); 

Handling Runtime Events

The WorkflowRuntime class exposes events related to runtime and workflow activities. You can implement these events in the host to handle certain types of workflow and runtime actions.

Table 5-1 describes the workflow runtime events that relate to the runtime.

Table 5-1: Workflow Runtime-Related Events
Open table as spreadsheet

Event

Description

Started

Fired when the workflow runtime is started by calling to Start Runtime method or when the first workflow instance is started.

Passes a WorkflowRuntimeEventArgs instance to the event handler.

Stopped

Fired when the StopRuntime method is called.

Passes a WorkflowRuntimeEventArgs instance to the event handler.

ServicesException NotHandled

Raised when a runtime service that has been added to the runtime does not handle an exception.

Passes a ServicesExceptionNotHandledEventArgs instance to the event handler.

Table 5-2 lists the events related to workflow instances. These events pass useful information back to the host though a WorkflowEventArgs instance or class that inherits from it. The WorkflowEventArgs class exposes its WorkflowInstance property to provide a reference to the workflow instance from which the event was raised. (The WorkflowInstance is covered later in this chapter.)

Table 5-2: Workflow Instance-Related Events
Open table as spreadsheet

Event

Description

WorkflowAborted

Raised when a workflow instance is aborted.

Passes a WorkflowEventArgs instance to the event handler.

WorkflowCompleted

Raised when a workflow instance completes.

Passes a WorkflowCompletedEventArgs instance to the event handler.

WorkflowCreated

Raised when a workflow instance is created.

Passes a WorkflowEventArgs instance to the event handler.

WorkflowIdled

Raised when a workflow becomes idle.

Passes a WorkflowEventArgs instance to the event handler.

WorkflowLoaded

Raised when a workflow instance is loaded into memory.

Passes a WorkflowEventArgs instance to the event handler.

WorkflowPersisted

Raised when a workflow is persisted to a durable medium via a persistence service.

Passes a WorkflowEventArgs instance to the event handler.

WorkflowResumed

Raised when a workflow instance is resumed.

Passes a WorkflowEventArgs instance to the event handler.

WorkflowStarted

Raised when a workflow instance is started. Passes a Workflow EventArgs instance to the event handler.

WorkflowSuspended

Raised when a workflow instance becomes suspended. This can occur when the Suspend or RequestSuspend method of the Workflow Instance is called by a SuspendActivity inside a workflow instance or when the runtime needs to suspend the instance.

Passes a WorkflowSuspendedEventArgs instance to the event handler.

WorkflowTerminated

Raised when a workflow instance is terminated.

Passes a WorkflowTerminatedEventArgs instance to the event handler.

WorkflowUnloaded

Raised when a workflow instance is unloaded from memory.

Passes a WorkflowEventArgs instance to the event handler.

The following code listing is a simple example of how the host handles runtime events. A Workflow Runtime instance is created, and there are several event handlers wired in the Main method. The event handlers use their respective event argument parameters to display relevant information about each event.

  static void Main(string[] args) {     WorkflowRuntime workflowRuntime = new WorkflowRuntime();     workflowRuntime.Started += new      EventHandler<WorkflowRuntimeEventArgs>(workflowRuntime_Started);     workflowRuntime.Stopped += new      EventHandler<WorkflowRuntimeEventArgs>(workflowRuntime_Stopped);     workflowRuntime.WorkflowCreated += new      EventHandler<WorkflowEventArgs>(workflowRuntime_WorkflowCreated);     workflowRuntime.WorkflowCompleted += new      EventHandler<WorkflowCompletedEventArgs>(workflowRuntime_WorkflowCompleted);     workflowRuntime.WorkflowTerminated += new      EventHandler<WorkflowTerminatedEventArgs>(workflowRuntime_WorkflowTerminated);     workflowRuntime.WorkflowIdled += new      EventHandler<WorkflowEventArgs>(workflowRuntime_WorkflowIdled);     workflowRuntime.StartRuntime();     WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(MyWorkflow));     instance.Start(); } // runtime related event handlers private void workflowRuntime_Started(object sender, WorkflowRuntimeEventArgs e) {     Console.WriteLine("The workflow runtime has been started. It's status is: " +         e.IsStarted ? "running" : "not running" + "."); } private void workflowRuntime_Stopped(object sender, WorkflowRuntimeEventArgs e) {     Console.WriteLine("The workflow runtime has been stopped. It's status is: " +         e.IsStarted ? "running" : "not running" + "."); } // workflow instance related event handlers private void workflowRuntime_WorkflowCreated(object sender, WorkflowEventArgs e) {     Console.WriteLine("A workflow instance has been created with the identifier " +         e.WorkflowInstance.InstanceId.ToString() + "."); } private void workflowRuntime_WorkflowCompleted(object sender,    WorkflowCompletedEventArgs e) {     Console.WriteLine("The workflow instance with the identifier " +         e.WorkflowInstance.InstanceId.ToString() + " has completed."); } private void workflowRuntime_WorkflowIdled(object sender, WorkflowEventArgs e) {     Console.WriteLine("The workflow instance with the identifier " +         e.WorkflowInstance.InstanceId.ToString() +         " has gone idle."); } private void workflowRuntime_WorkflowTerminated(object sender,    WorkflowTerminatedEventArgs e) {     Console.WriteLine("The workflow instance with the identifier " +         e.WorkflowInstance.InstanceId.ToString() +         " has been terminated.");     Console.WriteLine("It threw an exception, here are the details: " +         e.Exception.Message); } 

Persistence Points

You can use Windows Workflow Foundation to persist running workflows and their respective states, such as when a workflow instance is long running, and you do not want to store the workflow state in memory.

Persistence is a topic deeply imbedded in the concept of the workflow runtime. The runtime dictates when a workflow should be persisted to a designated store and makes the necessary persistence services method calls to do so.

The milestones when the runtime tells an active persistence service to persist a workflow instance are known as persistence points. To effectively manage a long-running workflow environment, you need to understand where in the workflow lifecycle these points exist. Persistence points occur at the following times in the lifecycle:

  • Directly before a workflow instance is completed or terminated

  • After a workflow instance becomes idle

  • When the workflow instance is explicitly unloaded, which occurs when the Unload or TryUnload method of the workflow instance is called

  • When activities that are decorated with the PersistOnClose attribute are completed

The workflow runtime is generally smart enough to know when to save a workflow’s state to a data store. This is generally seamless to developers and most definitely invisible to end users.

Workflow persistence services are covered in detail in Chapter 7, along with the other runtime services.

The WorkflowInstance Class

The WorkflowInstance class represents all workflows in their instantiated form. You use this class to monitor and manipulate an instance related to its execution. Think of it as a wrapper around an instance of a workflow definition class.

The standard technique for obtaining a reference to a new workflow instance is to call the CreateWorkflow method of the WorkflowRuntime class. Calling this method returns a reference to a workflow instance representing a workflow of the type passed as a parameter. Calling CreateWorkflow does not start the workflow. The Start method of WorkflowInstance must be explicitly called to begin the execution of the workflow itself.

Table 5-3 lists the methods of the WorkflowInstance class.

Table 5-3: WorkflowInstance Methods
Open table as spreadsheet

Method

Description

Abort

Ends execution of the current workflow instance in a synchronous manner. Calling Abort causes all changes made since the most recent persistence point to be discarded. Therefore, this method is usually used when a workflow cannot recover from a serious error. Calling Resume starts the workflow instance from the most recent persistence point after an Abort call.

ApplyWorkflow Changes

Takes a WorkflowChanges instance as its sole parameter and is used for dynamic update (see Chapter 11).

EnqueueItem

Places a message to the specified workflow in the queue.

EnqueueItemOnIdle

Places a message to the specified workflow in the queue only after the workflow instance has gone idle.

GetWorkflow Definition

Returns the workflow definition class that the workflow instance represents.

GetWorkflow QueueData

Returns a collection that contains information related to workflow queues and their associated work.

Load

Loads an unloaded workflow instance from a persistence store. The workflow state is read from the store, and the workflow continues execution.

ReloadTracking Profiles

Causes each TrackingProfile related to a workflow instance to be reloaded.

Resume

Resumes execution of a workflow instance that was previously suspended or aborted.

Start

Begins execution of a workflow instance.

Suspend

Suspends a workflow instance’s execution.

Terminate

Terminates an active workflow instance and attempts to persist the workflow’s state. This method is also called when an unhandled exception is raised from within a workflow instance.

TryUnload

Makes a request to unload the workflow instance’s state to a persistence store at the point when the instance is next suspended or idle.

Unload

Synchronously makes a request to unload the workflow instance’s state to a persistence store.

Workflow Execution

When the Start method of a WorkflowInstance class is called, two things have to happen to begin the workflow. Because activities make up a workflow definition and define the process flow, the runtime must find the root activity of a workflow and call its protected Execute method. From here, the workflow continues until an action causes a disruption in the flow.

Several events can halt or otherwise modify a workflow’s execution. The host can call the Abort, Terminate, Unload, or TryUnload method of the WorkflowInstance class to manually stop or pause the execution of a workflow instance. Workflow execution can also come to a stop when there is no work that can be immediately performed. This happens when the workflow is waiting for input from an outside entity, such as a person or external software system. Finally, exceptions that are not properly handled in a workflow instance cause the workflow execution to come to an end.

Conventional wisdom tells developers to put code that initializes variables and otherwise readies a class for execution in the class constructor. However, in Windows Workflow Foundation, this is not the recommend way to do things, because the workflow class constructor is actually called twice: once to validate the workflow’s activities and again when the class is instantiated for execution. Therefore, you should place code that usually goes in the constructor in the ExecuteCode event handler of a Code activity.

The WorkflowEnvironment Class

The WorkflowEnvironment class enables you to access the transactional context of the workflow instance executing on the current thread. This class exposes two properties of interest: the WorkflowInstanceId, which is the globally unique identifier (GUID) for a particular workflow instance, and IWorkBatch, which enables transactional functionality in the workflow. Work batching and transactional services are discussed in the following section.

Work Batching

Windows Workflow Foundation provides transactional functionality through work batching. This concept allows discrete chunks of work to be added to a set and completed at the same time. During the execution of a workflow, units of work can be added to the WorkBatch property of the WorkflowEnvironment class. When the workflow runtime reaches a commit point, all work items are performed within the context of a single transaction. That way, if any errors occur during this work, the workflow remains in a valid state after a rollback.

To enable this transactional process, you need to create a class that implements the IPendingWork interface. This interface describes a class with a couple of methods, including Commit, which is where the actual work happens. The Commit method receives an ICollection instance that contains a list of all objects added with the WorkBatch property of WorkflowEnvironment. The Commit method can iterate through each object in the ICollection instance and perform some kind of work.

Developing Batch Services

Services that inherit from WorkflowCommitWorkBatchService are responsible for taking the items in the work batch and performing their actions in a transactional manner. You are, of course, free to inherit from this class and develop your own batch service. However, if you do not specify that such a service be added to the workflow runtime, an instance of DefaultWorkflowCommitWorkBatchService is automatically added for you. (Chapter 7 has more on batching and transactions related to this type of runtime service.)



Professional Windows Workflow Foundation
Professional Windows Workflow Foundation
ISBN: 0470053860
EAN: 2147483647
Year: 2004
Pages: 118
Authors: Todd Kitta

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net