Workflow Code


To help the workflow understand the service, the workflow’s code file defines the IBugReportService interface shown in the following code. This interface defines all of the service methods that the workflow can invoke, and defines all of the service events that the workflow can catch.

  Imports System.Workflow.Runtime Imports System.Workflow.Activities Imports System.Workflow.Activities.Rules Imports System.Workflow.ComponentModel <ExternalDataExchange()> _ Public Interface IBugReportService     Event EngineerAssigned As EventHandler(Of StringArgs)     Event BugWorked As EventHandler(Of ExternalDataEventArgs)     Event BugRejected As EventHandler(Of ExternalDataEventArgs)     Event TestPassed As EventHandler(Of ExternalDataEventArgs)     Event TestFailed As EventHandler(Of ExternalDataEventArgs)     Sub AssignEngineer(ByVal bug_description As String)     Sub WorkBug(ByVal bug_description As String)     Sub TestBug(ByVal bug_description As String) End Interface 

Tip 

To use the WF components, you must add references to the appropriate libraries. Open Solution Explorer, double-click My Project, select the References tab, and click the Add button. Select the libraries System .Workflow.Runtime, System.Workflow.Activities, and System.Workflow.ComponentModel, and click OK.

This interface defines five events that let the workflow know when the service has assigned an engineer to the bug, when the bug is worked, when the bug is rejected as not fixed, when the bug fix passes its tests, and when the bug fix fails its tests.

The interface also defines methods to let the workflow ask the service to assign an engineer to the bug, work the bug, and test the bug fix.

The following code shows the beginning of the BugReportWF workflow’s definition:

  Public Class BugReportWF     Inherits SequentialWorkflowActivity #Region "Properties"     Private m_BugDescription As String = ""     Public Property BugDescription() As String         Get             Return m_BugDescription         End Get         Set(ByVal value As String)             m_BugDescription = value         End Set     End Property     Private m_AssignedEngineer As String = ""     Public Property AssignedEngineer() As String         Get             Return m_AssignedEngineer         End Get         Set(ByVal value As String)             m_AssignedEngineer = value         End Set     End Property     Private m_Status As String = ""     Public Property Status() As String         Get             Return m_Status         End Get         Set(ByVal value As String)             m_Status = value         End Set     End Property #End Region ' Properties Private WithEvents m_AssignEngineer As CallExternalMethodActivity Private WithEvents m_EngineerAssignedEvent As HandleExternalEventActivity Private WithEvents m_WorkBug As CallExternalMethodActivity Private WithEvents m_BugWorkedEvent As HandleExternalEventActivity Private WithEvents m_BugRejectedEvent As HandleExternalEventActivity Private WithEvents m_TestBug As CallExternalMethodActivity Private WithEvents m_TestPassedEvent As HandleExternalEventActivity Private WithEvents m_TestFailedEvent As HandleExternalEventActivity 

The class inherits from SequentialWorkflowActivity. The code defines three properties: BugDescrip?tion to hold a description of the bug, AssignedEngineer to hold the assigned engineer’s name, and Status to record the bug report’s final resolution. These are straightforward properties implemented with property procedures.

Next, the code declares several WF activity variables. These are the activities that will make up the workflow. They are declared with the WithEvents keyword to make catching their events easy.

The following code shows the BugReportWF class’s constructor. This routine builds the activities that make up the workflow, and is one of the more complicated parts of the workflow code.

  Public Sub New()     Me.CanModifyActivities = True     ' Assign an engineer.     m_AssignEngineer = _         New CallExternalMethodActivity("invoke_AssignEngineer")     m_AssignEngineer.InterfaceType = GetType(IBugReportService)     m_AssignEngineer.MethodName = "AssignEngineer"     Dim assign_param As New WorkflowParameterBinding     assign_param.ParameterName = "bug_description"     assign_param.Value = ""    ' We will change this later.     m_AssignEngineer.ParameterBindings.Add(assign_param)     ' Catch the EngineerAssigned event.     m_EngineerAssignedEvent = _         New HandleExternalEventActivity("catch_EngineerAssigned")     m_EngineerAssignedEvent.InterfaceType = GetType(IBugReportService)     m_EngineerAssignedEvent.EventName = "EngineerAssigned"     ' Work the bug.     m_WorkBug = New CallExternalMethodActivity("invoke_WorkBug")     m_WorkBug.InterfaceType = GetType(IBugReportService)     m_WorkBug.MethodName = "WorkBug"     Dim work_param As New WorkflowParameterBinding     work_param.ParameterName = "bug_description"     work_param.Value = ""    ' We will change this later.     m_WorkBug.ParameterBindings.Add(work_param)     ' Listen for either the BugWorked or BugRejected event.     ' Catch the BugWorked event.     m_BugWorkedEvent = New HandleExternalEventActivity("catch_BugWorked")     m_BugWorkedEvent.InterfaceType = GetType(IBugReportService)     m_BugWorkedEvent.EventName = "BugWorked"     ' Catch the BugRejected event.     m_BugRejectedEvent = New HandleExternalEventActivity("catch_BugRejected")     m_BugRejectedEvent.InterfaceType = GetType(IBugReportService)     m_BugRejectedEvent.EventName = "BugRejected"     ' Make activities driven by the BugWorked and BugRejected events.     Dim eventdriven_worked As New EventDrivenActivity("eventdriven_Worked")     eventdriven_worked.Activities.Add(m_BugWorkedEvent)     Dim eventdriven_rejected As New EventDrivenActivity("eventdriven_Rejected")     eventdriven_rejected.Activities.Add(m_BugRejectedEvent)     ' Listen to see if it was worked.     Dim listen_bug_worked As New ListenActivity("listen_Worked")     listen_bug_worked.Activities.Add(eventdriven_worked)     listen_bug_worked.Activities.Add(eventdriven_rejected)     ' Test the bug fix.     m_TestBug = New CallExternalMethodActivity("invoke_TestBug")     m_TestBug.InterfaceType = GetType(IBugReportService)     m_TestBug.MethodName = "TestBug"     ' If the bug was fixed, test it.     eventdriven_worked.Activities.Add(m_TestBug)     ' Listen for either the TestPassed or TestFailed event.     ' Catch the TestPassed event.     m_TestPassedEvent = New HandleExternalEventActivity("catch_TestPassed")     m_TestPassedEvent.InterfaceType = GetType(IBugReportService)     m_TestPassedEvent.EventName = "TestPassed"     ' Catch the TestFailed event.     m_TestFailedEvent = New HandleExternalEventActivity("catch_TestFailed")     m_TestFailedEvent.InterfaceType = GetType(IBugReportService)     m_TestFailedEvent.EventName = "TestFailed"     ' Make activities driven by the BugWorked and BugRejected events.     Dim eventdriven_passed As New EventDrivenActivity("eventdriven_Passed")     eventdriven_passed.Activities.Add(m_TestPassedEvent)     Dim eventdriven_failed As New EventDrivenActivity("eventdriven_Failed")     eventdriven_failed.Activities.Add(m_TestFailedEvent)     ' Listen to see if it was worked.     Dim listen_test_passed As New ListenActivity("listen_Passed")     listen_test_passed.Activities.Add(eventdriven_passed)     listen_test_passed.Activities.Add(eventdriven_failed)     ' After we test, listen for these events.     eventdriven_worked.Activities.Add(listen_test_passed)     ' Add the activities.     Me.Activities.Add(m_AssignEngineer)     Me.Activities.Add(m_EngineerAssignedEvent)     Me.Activities.Add(m_WorkBug)     Me.Activities.Add(listen_bug_worked)     Me.CanModifyActivities = False End Sub 

The first activity the constructor makes is m_AssignEngineer. This represents the task of someone assigning an engineer to the bug report. This is a CallExternalMethodActivity object that calls the service’s AssignEngineer subroutine. The InterfaceType and MethodName properties tell the object that it will be invoking the AssignEngineer subroutine provided by a service that implements the IBugReportService interface.

The AssignEngineer subroutine takes as a single parameter a string named bug_description. At this point, the workflow doesn’t know the bug’s description, but it binds a parameter to the activity anyway, giving it a blank temporary value. The code will set this value to the actual bug description before it invokes the activity.

When the m_AssignEngineer activity executes, the service program’s AssignEngineer method prompts the user for the name of the engineer to assign to the bug. When it has finished, the service raises the EngineerAssigned event and the workflow must catch this event.

The constructor next makes a HandleExternalEventActivity object. Its InterfaceType and Event?Name properties indicate that it will respond to an EngineerAssigned event raised by a service that implements IBugReportService.

After it handles this event, the workflow must call the service’s WorkBug method. The constructor makes a CallExternalMethodActivity object similar to the m_AssignEngineer object it created earlier, except this one calls the service’s WorkBug method.

The service’s WorkBug method lets the engineer fix the bug, and then indicate whether the bug has been fixed. This corresponds to the Bug Fixed? condition in Figure 30-2. The service then raises either the BugWorked or BugRejected event, and the workflow must wait until it sees one of those events.

To look for those events, the constructor makes two HandleExternalEventActivity objects, one for each event. It then makes two corresponding EventDrivenActivity objects and adds the Handle?ExternalEventActivity objects as their children. Finally, it creates a ListenActivity object and adds the two EventDrivenActivity objects as its children.

If the workflow receives a BugWorked event, it needs to test the bug. The constructor makes a Call?External?MethodActivity object to call the service’s TestBug method. This is similar to the m_AssignEngineer and m_WorkBug objects created earlier.

The constructor adds the m_TestBug object to the children of the EventDrivenActivity object that responds to the BugWorked event. If the service raises this event, then the workflow executes the new m_TestBug activity.

Depending on whether the bug fixed worked, the service raises either the TestPassed or TestFailed event. Just as it waited for either the BugWorked or BugRejected event, it must now wait for either the TestPassed or TestFailed event. The constructor creates a similar set of HandleExternalEvent?Activity objects with corresponding EventDrivenActivity objects, and puts them inside a new ListenActivity.

The constructor adds the new ListenActivity object to the EventDrivenActivity object that responds to the BugWorked event. If the service originally raised the BugWorked event, then the workflow executes the m_TestBug activity and then listens for either the TestPassed or TestFailed event.

Finally, after it has created all of the activity objects, the constructor adds the main objects to its Activities collection. First, the workflow executes the m_AssignEngineer activity to call the service’s Assign?Engineer method. Next, it executes m_EngineerAssignedEvent to wait for the EngineerAssigned event. It then executes m_WorkBug to call the service’s WorkBug method. It finishes by performing the listen_bug_worked activity, which waits for the BugWorked or BugRejected events, calls the TestBug method, and waits for the TestPassed and TestFailed events.

The BugReportWF class’s constructor sets up the sequence of activities. Much of the hard work in the workflow is performed by the service application, which assigns an engineer, fixes the bug, and tests the results, but there are still some chores that the workflow’s code must handle.

When the service raises the EngineerAssigned event, the m_EngineerAssignedEvent activity raises its Invoked event. The following code catches that event and processes it:

  ' Save the assigned employee's name. Private Sub m_EngineerAssignedEvent_Invoked(ByVal sender As Object, _  ByVal e As System.Workflow.Activities.ExternalDataEventArgs) _  Handles m_EngineerAssignedEvent.Invoked     ' Convert to AssignedEngineerArgs.     Dim assigned_args As StringArgs = DirectCast(e, StringArgs)     ' Save the value.     m_AssignedEngineer = assigned_args.Value     Debug.WriteLine("Assigned: " & m_AssignedEngineer) End Sub  

The event handler’s parameter e is actually a StringArgs object. StringArgs is a subclass of External?DataEventArgs, and the event handler refers to it as an ExternalDataEventArgs object, so the code converts it into a StringArgs object. When it raises the EngineerAssigned event, the service sets this object’s Value property to the name of the engineer it assigned to the bug. This code saves that name in the workflow’s m_AssignedEngineer variable and displays the name in the Immediate window.

The following code shows the workflow’s other event handlers that catch events raised by the service application:

  ' The bug was rejected. Private Sub m_BugRejectedEvent_Invoked(ByVal sender As Object, _  ByVal e As System.Workflow.Activities.ExternalDataEventArgs) _  Handles m_BugRejectedEvent.Invoked     Debug.WriteLine("Bug rejected")     Debug.WriteLine("")     Status = "Rejected" End Sub ' The bug was worked. Private Sub m_BugWorkedEvent_Invoked(ByVal sender As Object, _  ByVal e As System.Workflow.Activities.ExternalDataEventArgs) _  Handles m_BugWorkedEvent.Invoked     Debug.WriteLine("Bug worked") End Sub ' The test failed. Private Sub m_TestFailedEvent_Invoked(ByVal sender As Object, _  ByVal e As System.Workflow.Activities.ExternalDataEventArgs) _  Handles m_TestFailedEvent.Invoked     Debug.WriteLine("Test failed")     Debug.WriteLine("")     Status = "Test Failed" End Sub ' The test passed. Private Sub m_TestPassedEvent_Invoked(ByVal sender As Object, _  ByVal e As System.Workflow.Activities.ExternalDataEventArgs) _  Handles m_TestPassedEvent.Invoked     Debug.WriteLine("Test passed")     Debug.WriteLine("")     Status = "Test Passed" End Sub 

The m_BugRejectedEvent activity’s Invoked event handler prints a message in the Immediate window and sets the workflow’s Status property to Rejected. After this activity finishes, the workflow has no more activities to run, so it ends.

The m_BugWorkedEvent activity simply displays a message in the Immediate window. The workflow will next execute the m_TestBug activity.

The m_TestFailedEvent and m_TestPassedEvent activities work similarly to m_BugRejectedEvent, displaying messages in the Immediate window and setting the workflow’s Status property appropriately.

The following code shows the workflow’s final two event handlers:

  ' We are about to invoke this method. ' Copy the bug's description into the  Private Sub m_AssignEngineer_MethodInvoking(ByVal sender As Object, _  ByVal e As System.EventArgs) Handles m_AssignEngineer.MethodInvoking     m_AssignEngineer.ParameterBindings.Item("bug_description").Value = _         m_BugDescription End Sub Private Sub m_WorkBug_MethodInvoking(ByVal sender As Object, _  ByVal e As System.EventArgs) Handles m_WorkBug.MethodInvoking     m_WorkBug.ParameterBindings.Item("bug_description").Value = _         m_BugDescription End Sub Private Sub m_TestBug_MethodInvoking(ByVal sender As Object, _  ByVal e As System.EventArgs) Handles m_TestBug.MethodInvoking     m_TestBug.ParameterBindings.Item("bug_description").Value = _         m_BugDescription End Sub 

These MethodInvoking events fire just before their corresponding activities execute. They update the parameters bound to their activities to give them the bug’s correct description. When the activities invoke the service’s methods, they pass those methods the bug’s description.

The final piece to the BugReportWF class’s code module is the definition of the StringArgs class shown in the following code:

  <Serializable()> _ Public Class StringArgs     Inherits ExternalDataEventArgs     Public Value As String     Public Sub New(ByVal instance_id As Guid, ByVal new_value As String)         MyBase.New(instance_id)         Value = new_value     End Sub End Class 

The program uses this class to pass string values into workflow event handlers. It inherits from External?DataEventArgs because that’s the type of argument that the event handlers expect to receive. Look in the following section to see how the code that raises these events uses this class.

Tip 

Note that the StringArgs class must have the Serializable attribute or the workflow will throw a rather cryptic “The workflow failed validation” exception.

Service Code

The main service application creates the workflow runtime, builds the workflow object, and starts it running. It provides methods to perform the workflow’s major tasks: assigning the bug, working the bug, and testing the bug fix.

The following code shows how the main program’s form code begins:

  Imports System.Workflow.Runtime Imports System.Workflow.Runtime.Hosting Imports System.Workflow.Activities Public Class Form1     Implements IBugReportService     Private WithEvents m_WorkflowRuntime As WorkflowRuntime     Private m_ExchangeService As ExternalDataExchangeService     Private m_WorkflowInstance As WorkflowInstance     ' Prepare the workflow runtime engine.     Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) _      Handles Me.Load         m_WorkflowRuntime = New WorkflowRuntime()         m_ExchangeService = New ExternalDataExchangeService()         m_WorkflowRuntime.AddService(m_ExchangeService)         m_ExchangeService.AddService(Me)         m_WorkflowRuntime.StartRuntime()     End Sub 

Note that the form’s class implements the IBugReportService interface defined in the workflow’s code module. After the Implements statement, the code declares a few variables work dealing with WF. It declares a WorkflowRuntime variable to represent the WF runtime engine, it makes an External?DataExchangeService object to make exchanging data with the workflow easier, and it declares a WorkflowInstance object to process a bug report.

The form’s Load event handler creates new WorkflowRuntime and ExternalDataExchangeService objects. It adds the ExternalDataExchangeService to the runtime engine’s list of services. It also adds itself to the list or services (remember that the form implements the IBugReportService interface). The Load event handler then starts the runtime engine.

When you enter a bug description in the form’s text box and click Submit, the following code executes:

  ' Start the workflow. Private Sub btnSubmit_Click(ByVal sender As System.Object, _  ByVal e As System.EventArgs) Handles btnSubmit.Click     ' Get the workflow's type.     Dim wf_type As Type = GetType(BugReportWF)     ' Construct workflow arguments.     Dim args As New Dictionary(Of String, Object)()     args.Add("BugDescription", txtBugDescription.Text)     ' Create and start the workflow.     m_WorkflowInstance = _         m_WorkflowRuntime.CreateWorkflow(wf_type, args)     m_WorkflowInstance.Start() End Sub 

This code starts by making a Type object to represent the BugReportWF type. Next, it creates a Dictionary object to hold parameters that will be used to create the workflow instance. It sets the value of the Bug?Description entry to the value you entered in the form’s text box.

The program then calls the runtime engine’s CreateWorkflow method, passing it the type of the workflow to create and the Dictionary. The runtime engine creates the workflow object, and then uses the Dictionary’s entries to set the workflow’s properties. In this example, it sets the workflow object’s BugDescription property to the text you entered on the form.

Tip 

If there’s anything wrong with the workflow, the call to CreateWorkflow will probably throw a rather uninformative “The workflow failed validation” exception. For example, if the workflow’s constructor tries to build an illegal workflow (such as an activity assigned to two parents or a CallExternalMethodActivity object referring to a non-existent service method), the call throws this error. Other mistakes that can cause this error include an Event?DrivenActivity that has no child activities, a ListenActivity that has fewer than two child activities, a HandleExternalEventActivity without a specified InterfaceType or EventName, and many others. As is always the case when error information is scarce, you’ll probably save time if you build the workflow gradually, testing the pieces frequently so that you catch any validation errors right away.

Note that the engine creates the workflow first and then uses the Dictionary to set property values. That means the workflow’s constructor cannot use the property values because they are not yet set.

The code then calls the workflow object’s Start method to get things rolling.

In this example, the workflow’s first activity is a CallExternalMethodActivity object named m_AssignEngineer. It calls the form’s AssignEngineer method shown in the following code:

  Public Delegate Sub AssignEngineerDelegate(ByVal bug_description As String) Public Sub AssignEngineer(ByVal bug_description As String) _  Implements IBugReportService.AssignEngineer     If Me.InvokeRequired Then         Me.Invoke(New AssignEngineerDelegate _             (AddressOf Me.AssignEngineer), New Object() {bug_description})     Else         Dim engineer_name As String = _             InputBox("Who do you want to assign to work bug report:" & _                 vbCrLf & vbCrLf & bug_description, _                 "Assign Engineer", "Debugger Dan")         ' Tell the workflow who we assigned.         Dim args As New StringArgs( _             m_WorkflowInstance.InstanceId, engineer_name)         RaiseEvent EngineerAssigned(Nothing, args)     End If End Sub 

The workflow runs on a thread other than the user interface (UI) thread so that its call to AssignEngineer cannot directly interact with the UI. Because the program needs to interact with the user, it calls Invoke?Required to see if it is running in the UI thread. If InvokeRequired returns true, then it is not running in the UI thread, so it uses Invoke to call AssignEngineer on the UI thread. The code passes an Assign?Engineer?Delegate initialized with the AssignEngineer method’s address and the bug_description parameter to the call to Invoke.

Invoke then calls AssignEngineer on the UI thread. This time, InvokeRequired returns false, so the routine executes the code after the Else statement. The subroutine displays an input box where you can enter the name of the engineer who should be assigned to the bug report. The code then creates a new StringArgs object to store the engineer’s name and raises the EngineerAssigned event. The workflow catches the event and saves the engineer’s name in its AssignedEngineer property.

At that point, control returns to the workflow. Having finished running its first activity, m_Assign?Engineer, the workflow runs activity m_WorkBug. That activity invokes the service’s WorkBug method shown in the following code:

  Public Delegate Sub WorkBugDelegate(ByVal bug_description As String) Public Sub WorkBug(ByVal bug_description As String) _  Implements IBugReportService.WorkBug     If Me.InvokeRequired Then         Me.Invoke(New WorkBugDelegate _             (AddressOf Me.WorkBug), New Object() {bug_description})     Else         ' Tell the workflow whether we fixed the bug.         If MessageBox.Show("Did you fix the bug:" & _             vbCrLf & vbCrLf & bug_description, _             "Bug Fixed?", MessageBoxButtons.YesNo, _             MessageBoxIcon.Question) _                 = Windows.Forms.DialogResult.Yes _         Then             RaiseEvent BugWorked(Nothing, _                 New ExternalDataEventArgs(m_WorkflowInstance.InstanceId))         Else             RaiseEvent BugRejected(Nothing, _                 New ExternalDataEventArgs(m_WorkflowInstance.InstanceId))         End If     End If End Sub 

This code is similar to the AssignEngineer subroutine shown previously. It uses InvokeRequired to see if it is running in the UI thread, and invokes to that thread if necessary. It then displays a message box asking whether the engineer has fixed the bug. If you click Yes, the code raises the BugWorked event. If you click No, the code raises the BugRejected event. The workflow catches whichever event the service code raises, and responds appropriately.

If the workflow catches the BugWorked event, it then executes its m_TestBug activity. That activity calls the service’s TestBug method shown in the following code:

  Public Delegate Sub TestBugDelegate(ByVal bug_description As String) Public Sub TestBug(ByVal bug_description As String) _  Implements IBugReportService.TestBug     If Me.InvokeRequired Then         Me.Invoke(New TestBugDelegate _             (AddressOf Me.TestBug), New Object() {bug_description})     Else         ' Tell the Bugflow whether we fixed the bug.         If MessageBox.Show("Did the test pass for:" & _             vbCrLf & vbCrLf & bug_description, _             "Test Passed?", MessageBoxButtons.YesNo, _             MessageBoxIcon.Question) _                 = Windows.Forms.DialogResult.Yes _         Then             RaiseEvent TestPassed(Nothing, _                 New ExternalDataEventArgs(m_WorkflowInstance.InstanceId))         Else             RaiseEvent TestFailed(Nothing, _                 New ExternalDataEventArgs(m_WorkflowInstance.InstanceId))         End If     End If End Sub 

Subroutine TestBug is very similar to subroutine WorkBug. It invokes itself if necessary, and asks if the bug passed its tests. It then raises either the TestPassed or TestFailed event.

After the workflow finishes executing its last activity, the WorkflowInstance object in the service application receives a WorkflowCompleted event and executes the event handler shown in the following code:

  Private Sub m_WorkflowRuntime_WorkflowCompleted(ByVal sender As Object, _  ByVal e As System.Workflow.Runtime.WorkflowCompletedEventArgs) _  Handles m_WorkflowRuntime.WorkflowCompleted     Dim txt As String = "Workflow completed" & vbCrLf & vbCrLf     txt &= "Description: " & e.OutputParameters("BugDescription").ToString() & _         vbCrLf     txt &= "Assigned to: " & e.OutputParameters("AssignedEngineer").ToString() & _         vbCrLf     txt &= "Result: " & e.OutputParameters("Status").ToString() & vbCrLf     MessageBox.Show(txt) End Sub 

This code builds a summary message and displays it in a message box. The event handler’s e parameter has an OutputParameters property that contains the values of the workflow’s properties when it finishes. This routine adds the workflow’s BugDescription, AssignedEngineer, and Status properties to its message.




Visual Basic 2005 with  .NET 3.0 Programmer's Reference
Visual Basic 2005 with .NET 3.0 Programmer's Reference
ISBN: 470137053
EAN: N/A
Year: 2007
Pages: 417

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