Professional VB 2005 with .NET 3.0 (Programmer to Programmer)
The actual workflow files in WF are XML files written in a version of XAML. This is the same XAML used to describe Windows Presentation Foundation (WPF) files. (See Chapter 17 for more details on WPF). They describe the actions to perform within the workflow, and the relationship between those actions. You can create a workflow using only a text editor, but Visual Studio makes creating these workflows much easier. It provides a graphical designer that enables developers to visually design the workflow, creating the XAML in the background.
<RuleDefinitions xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow"> <RuleDefinitions.Conditions> <RuleExpressionCondition Name="TranslationCallWorked"> <RuleExpressionCondition.Expression> <ns0:CodeBinaryOperatorExpression Operator="ValueEquality" xmlns:ns0="clr-namespace:System.CodeDom;Assembly=System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> <ns0:CodeBinaryOperatorExpression.Left> <ns0:CodeBinaryOperatorExpression Operator="ValueEquality"> <ns0:CodeBinaryOperatorExpression.Left> <ns0:CodeMethodInvokeExpression> <ns0:CodeMethodInvokeExpression.Parameters> <ns0:CodeFieldReferenceExpression FieldName="OutputTextProperty"> <ns0:CodeFieldReferenceExpression.TargetObject> <ns0:CodeTypeReferenceExpression Type="TranslateActivity.TranslateActivity" /> </ns0:CodeFieldReferenceExpression.TargetObject> </ns0:CodeFieldReferenceExpression> </ns0:CodeMethodInvokeExpression.Parameters> <ns0:CodeMethodInvokeExpression.Method> <ns0:CodeMethodReferenceExpression MethodName="GetValue"> <ns0:CodeMethodReferenceExpression.TargetObject> <ns0:CodeThisReferenceExpression /> </ns0:CodeMethodReferenceExpression.TargetObject> </ns0:CodeMethodReferenceExpression> </ns0:CodeMethodInvokeExpression.Method> </ns0:CodeMethodInvokeExpression> </ns0:CodeBinaryOperatorExpression.Left> <ns0:CodeBinaryOperatorExpression.Right> <ns0:CodePrimitiveExpression /> </ns0:CodeBinaryOperatorExpression.Right> </ns0:CodeBinaryOperatorExpression> </ns0:CodeBinaryOperatorExpression.Left> <ns0:CodeBinaryOperatorExpression.Right> <ns0:CodePrimitiveExpression> <ns0:CodePrimitiveExpression.Value> <ns1:Boolean xmlns:ns1="clr-namespace:System;Assembly=mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">false</ns1:Boolean> </ns0:CodePrimitiveExpression.Value> </ns0:CodePrimitiveExpression> </ns0:CodeBinaryOperatorExpression.Right> </ns0:CodeBinaryOperatorExpression> </RuleExpressionCondition.Expression> </RuleExpressionCondition> </RuleDefinitions.Conditions> </RuleDefinitions>
The workflow is composed of a number of rule definitions. Each definition includes activities, conditions, and expressions. Activities are the steps involved in the workflow. They are executed based on the workflow’s design and the conditions included. Controlling the behavior of the workflow are conditions, which are evaluated and may result in code running. Finally, expressions describe the individual tests used as part of the conditions. For example, each side of an equality condition would be expressions. When building the workflow by hand, you are responsible for creating the markup. Fortunately, Visual Studio writes it as you design your workflow.
Adding Workflow with Windows Workflow Foundation
Windows Workflow Foundation is composed of a number of components that work together with your application to carry out the desired workflow. Six main components make up any WF application:
-
Host process - This is the executable that will host the workflow. Typically, this is your application, and is usually a Windows Form, ASP.NET, or Windows service application. The workflow is hosted and runs within this process. All normal rules of application design apply here: If another application needs to communicate with the workflow, then you need to use Web Services or remoting to communicate between the two applications.
-
WF runtime services - Windows Workflow Foundation provides several essential services to your application. Most notable, of course, is the capability to execute workflows. This service is responsible for loading, scheduling, and executing your workflows within the context of the host process. In addition to this service, WF provides services for persistence and tracking. The persistence service enables saving the state of a workflow as needed. Because a workflow may take a long time to complete, having multiple workflows in process can use a lot of the memory of the computer. The persistence services enable the workflow to be saved for later use. When there is more to complete, the workflow can be reactivated and continue, even after weeks of inactivity. The tracking services enable the developer to monitor the state of the workflows. This is particularly useful when you might have multiple workflows active at any given time (such as in a shopping checkout workflow). The tracking services enable the creation of applications to monitor the health of your workflow applications.
-
Workflow runtime engine - The runtime engine is responsible for executing each workflow instance. It runs in-process within the host process. Each engine may execute multiple workflow instances simultaneously, and multiple engines may be running concurrently within the same host process.
-
Workflow - The workflow is the list of steps required to carry out an action. It may be created graphically using a tool such as Visual Studio, or manually. Each workflow is composed of one or more activities, and may consist of workflow markup and/or code. Multiple instances of a workflow may be active at any given moment in an application.
-
Activity library - The activity library is a collection of the standard actions used to create workflows. There are several different types of activities. Some are used to communicate with outside processes, while others affect the flow of a workflow.
-
Custom activities - In addition to the standard activities that exist within the activity library, developers can create custom activities. This may be to support a particular application you need to integrate with WF, or as a simplification of a complex composite activity. Creating custom activities is done mostly through attributes and inheritance.
Figure 25-1 shows how the main components of WF fit together.
Windows Workflow Foundation supports two main styles of creating workflows: sequential and state machine. Sequential workflows (see Figure 25-2) are the classic flowchart style of process. They begin when some action initiates the workflow, such as the submission of an expense report or a user decision to check out a shopping cart. The workflow then continues stepwise through the activities until it comes to the end. There may be branching or looping, but generally the flow moves down the workflow. Sequential workflows are best when a set series of steps are needed for the workflow.
State machine workflows (see Figure 25-3) are less linear than sequential workflows. They are typically used when the data moves through a series of steps toward completion. At each step, the state of the application has a particular value. Transitions move the state between steps. One example of a state machine workflow that most people are familiar with (unfortunately) is voice mail. Most voice-mail systems are collections of states, represented by a menu. You move between the states by pressing the keys of your phone. State machine workflows can be useful when the process you are modeling is not necessarily linear. There may still be some required steps, but generally the flow may iterate between the steps for some time before completion.
A good way to identify a candidate for a state machine workflow is determining whether the process is defined in terms of modes. For example, a shopping site is a classic example of a state machine. The user is either in browse mode or cart view mode. Selecting checkout would likely initiate a sequential workflow, as the steps in that process are more easily described in a linear fashion.
A Simple Workflow
As with any other programming endeavor, the best way to understand WF is to create a simple workflow and extend it with additional steps. Start Visual Studio and create a new Sequential Workflow Console Application (see Figure 25-4) called HelloWorkflow.
This project creates two files: a module that includes the Main for the application and the workflow. The sequential workflow begins life with only two steps: start and finish, as shown in Figure 25-5. You build the workflow by adding steps between these two.
To begin, drag a Code activity between the start and finish markers. Note the red exclamation mark on the new activity in the diagram (shown without color in Figure 25-6). WF makes heavy use of these tips to help you identify the next step in the process.
Click the code tip and select the menu item Property 'ExecuteCode’ is not set. This will bring up the Properties window for the Code activity. Enter SayGreetings and press Enter. This brings up the code window for the activity. Add the following code:
Private Sub SayGreetings(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Console.WriteLine("Hello world, from workflow") Console.WriteLine("Press enter to continue") Console.ReadLine() End Sub
Notice that coding the action for the activity is the same as any other event. Run the project to see the console window (see Figure 25-7), along with the message you should be expecting.
While trivial, the project makes a useful test bed for experimenting with the various activities. Add an IfElse activity before the Code activity. IfElse activities are one of the main ways to add logic and control of flow to your workflows. They have a condition property that determines when each half of the flow will be executed. The condition may be code that executes or a declarative rule. For this example, declarative rules are enough. You create these rules in the Rule Condition Editor (see Figure 25-8). To display the Rule Condition Editor, select Declarative Rule Condition for the Condition property of the ifElseBranchActivity component. Once you have selected Declarative Rule Condition, you can click the ellipsis to display the dialog.
Clicking New brings up the Rule Condition Editor (see Figure 25-9). This enables you to create simple expressions that will be used by the IfElse activity to determine flow.
Set the rule on the If half of the IfElse activity to determine whether the current time is before noon:
System.DateTime.Now.TimeOfDay.Hours < 12
Right-click on the activity and select Add Branch to create a third branch to the IfElse activity. Set the condition for this one as you did for the first activity, but use 18 for the value to check for hours.
Add Code activities to each of the three sections of the diagram (see Figure 25-10). You will use these activities to affect the message that is displayed. Assign the properties as follows:
Activity | Property | Value |
---|---|---|
codeActivity2 | ExecuteCode | SetMessageMorning |
codeActivity3 | ExecuteCode | SetMessageAfternoon |
codeActivity4 | ExecuteCode | SetMessageEvening |
Finally, change the code in the workflow to the following (note that this replaces the SayGreetings method created earlier):
Public class Workflow1 Inherits SequentialWorkflowActivity Private Message As String Private Sub SayGreetings(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Console.WriteLine(Message & ", from workflow") Console.WriteLine("Press enter to continue") Console.ReadLine() End Sub Private Sub SetMessageMorning(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Message = "Good morning" End Sub Private Sub SetMessageAfternoon(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Message = "Good afternoon" End Sub Private Sub SetMessageNight(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Message = "Good night" End Sub End Class
Each of the three SetMessage methods changes the greeting as appropriate. The final greeting is displayed in the SayGreetings method. Run the project again. You should be greeted appropriately for the time of day.
While this workflow is probably overkill to generate a simple message, the example does show many of the common steps in defining a workflow. Workflows are composed of multiple activities. Many activities can in turn be composed of other activities. Activities may use declarative properties, or code may be executed as needed.
Standard Activities
The standard WF activities can be divided into five major categories:
-
Activities that communicate with external code - These activities are either called by external code to initiate a workflow or are used to call to external code as part of a workflow.
-
Control of flow activities - These activities are the equivalent of Visual Basic’s If statement or While loop. They enable the workflow to branch or repeat as needed to carry out a step.
-
Scope activities - These activities group a number of other activities together into some logical element. This is usually done to mark a number of activities that participate in a transaction.
-
State activities - These activities are used exclusively in state machine workflows. They represent the state of the process involved as part of the overall state machine.
-
Action activities - These activities perform some action as part of the overall workflow.
In order for a workflow to begin, there must be some way for external code to initiate it. In addition, a workflow would be limited if there were no way for the workflow to execute external code and/or Web Services. The standard activities that are used to communicate with external code include the following:
Activity | Description |
---|---|
CallExternalMethod | As the name implies, this activity calls an external method. The activity requires two properties. The first identifies an interface shared by the workflow and the external code. The second identifies the method on that interface that will be called. If the method requires additional parameters, then they appear on the property grid after setting the other two properties. This method is frequently used in combination with the HandleExternalEvent activity. This activity executes the external method synchronously, so be cautious when calling external methods that will take a long time to execute. |
HandleExternalEvent | Receives a trigger from an external block of code. This is a commonly used activity to initiate a workflow when the workflow is running in the context of a Windows Forms or ASP.NET application. As with the CallExternalMethod activity, it requires at least two properties. The first identifies a shared interface and the second identifies the event on that interface that will be received. |
InvokeWebService | Calls an external Web Service. You assign a WSDL file to the activity and it generates a proxy class for the Web Service. You must also identify the method on that class that will be called. The SessionId property is used to identify the session that will be used for the requests. All requests with the same SessionId value share the session. If the SessionId is blank, then this activity creates a new session per request. |
InvokeWorkflow | Calls another workflow. This is a useful activity for chaining multiple workflows together, reducing the complexity of each workflow. The external workflow must complete before the current workflow continues. |
WebServiceInput | Receives an incoming Web Service request. You must publish the workflow containing this activity for it to work. You publish the workflow by selecting Publish as Web Service from the Project menu. This generates a new Web Service project that includes the output from the workflow project as well as an ASMX file that serves as the address for the workflow. |
WebServiceOutput | Produces the output for a Web Service request. This activity is used in partnership with the WebServiceInput activity. |
WebServiceFault | Triggers a Web Service error. This is used in partnership with the WebServiceInput activity to signal an error with the Web Service call. |
All programming languages need some form of control of flow to regulate the applications. Visual Basic includes language elements such as If..Else, Do..While, For..Next, and Select Case to perform these actions. WF includes a number of activities to perform similar actions, although the options are more limited:
Activity | Description |
---|---|
IfElse | Provides for executing two or more different workflow paths based on the status of a condition. The condition may be code or an expression. This is a commonly used activity to branch a workflow. |
Listen | Provides for executing two or more different workflow paths based on an event. The path chosen is selected by the first event that occurs. This is a useful activity to use to monitor a class that could generate multiple events (such as a class that could either approve or reject a request). |
Policy | Provides for executing multiple rules. Each rule is a condition with some resulting action. This activity provides a way to group multiple related rules into a single activity. |
Replicator | Enables the workflow to create multiple instances of an activity for processing. The resulting child activities may run serially or in parallel. This is an excellent way to divide a large task: For example, you could have the Replicator activity create multiple child activities that are responsible for mailing a newsletter to a large list. The child activities could run in parallel, dividing the list into smaller groups for faster processing. |
While | Loops the workflow until a condition has been met. The condition may be the result of code or an expression. This is typically used to receive multiple input values or to process multiple requests, such as a batch job. |
Several composite activities may cooperate to complete a single logical action by grouping other activities:
Activity | Description |
---|---|
CompensatableSequence | Similar to the Sequence activity (see below), this activity differs in that it supports “undoing” the child activities. You can think of this in terms of a transaction: If one child activity fails, then the completed activities must be undone. The CompensatableSequence activity includes handles that enable the developer to perform this correction. |
ConditionedActivityGroup | Includes a number of child activities that are run based on a condition. All child activities will execute until some defined condition occurs. This provides a means of grouping a number of related activities into a single activity. |
EventDriven | Responds to an external event to initiate a set of activities. This is similar to the HandleExternalEvent activity, but the events are internal to the workflow. This activity is commonly used in a state-machine workflow to move between the states. |
FaultHandler | Enables handling an error within a workflow. You use the FaultHandler activity to either correct or report the error gracefully. For example, a timeout may occur, triggering a fault condition in the workflow. This handler would contain other activities that are responsible for an alternate method of processing the item. |
Parallel | Contains a series of child activities that run concurrently. You should only use this if the child activities either do not affect the data or the order of change is not important. |
Sequence | Contains a series of child activities that run in order. This is the default model for an workflow. Each child activity must complete before the next one begins. |
State activities represent the current state of the data and process for the workflow. They are only used within state-machine workflows:
Activity | Description |
---|---|
State | Represents the current state of the workflow. For example, in a workflow driving a voice-mail system, the state would represent the current menu that the client is on. |
StateFinalization | Provides an activity to handle the actions needed as a given state is completed. This would provide a place to record the user’s selection or to free up resources used by the state. |
StateInitialization | Provides an activity to handle the actions needed before the given state is entered. This would enable the creation of any data or code needed to prepare for the state functioning. |
The final group of activities are those that perform some action. You already saw this activity type in the form of the Code activity. These activities are the cornerstone of any workflow. The standard activities in this group include the following:
Activity | Description |
---|---|
Code | Enables custom Visual Basic code to be performed at a stage in the workflow. You can use these wherever you need to perform some action not done by another activity. Whenever you use one of these - especially if you use the same type of code frequently - you should consider moving the code into a custom activity. |
Compensate | Enables custom code to undo a previous action. This is typically done if an error occurs within the workflow. |
Delay | Pauses the flow of the workflow. This is typically used to schedule some event. For example, you might have a workflow that is responsible for printing a daily report. The Delay activity could be used to schedule this printout so that it is ready as the workers come in to read it. You can either set the delay explicitly by setting the TimeoutDuration property or set it via code using the event identified in the InitializeTimeoutDuration property. |
Suspend | Temporarily stops the workflow. This is usually due to some extraordinary event that you would want an administrator or developer to correct. The workflow will continue to receive requests, but not complete them past the Suspend activity. The administrator may then resume the workflow to complete processing. |
Terminate | Ends the workflow immediately. This should only be done in extreme situations such as when the workflow is not capable of any further processing (e.g., it has lost the connection to a database or other needed resource). |
Throw | Creates an exception that can be caught by the code hosting the workflow. This provides a means of propagating an error from the workflow to the containing code. |
Building Custom Activities
In addition to the standard activity library, WF supports extensibility through the creation of custom activities. Creating custom activities is a matter of creating a new class that inherits from Activity (or one of the existing child classes). Several available attributes enable the customization of the activity and how it appears when you use it in your workflows.
Creating custom activities is the primary means of extending WF. You might use custom activities to simplify a complex workflow, grouping a number of common activities into a single new activity. Alternatively, custom activities can create a workflow that is easier to understand, using terms that are more familiar to the developers and business experts. Finally, custom activities can be used to support software used within the business, such as activities to communicate with a CRM or ERP system.
To see the steps required for creating a custom activity, the next exercise creates a simple activity that wraps the Google translation service. Create a new project using the Workflow Activity Library template. This project will create a DLL that contains the activities you create. Name the project TranslationActivity. It will include a single custom activity initially. This activity inherits from SequenceActivity, so it might include multiple child activities. You can change this as needed, but it’s a good enough default for most activities. Drag a Code activity onto the designer. This activity does the actual translation work. Alternately, you could change the top-level class to inherit from Activity and override the Execute method to perform the translation. Name the new activity TranslateActivity.
Because the new activity will be used to convert between a number of set language pairs, create an enumeration containing the valid options. This enumeration can be expanded as new options become available. You can either add this enumeration to a new class file or add it to the bottom of the current module (after the End Class statement):
Public Enum TranslationOptions As Integer EnglishToFrench EnglishToSpanish EnglishToGerman EnglishToItalian EnglishToRussian EnglishToChinese FrenchToEnglish SpanishToEnglish GermanToEnglish ItalianToEnglish RussianToEnglish ChineseToEnglish End Enum
The new activity has three properties: the input text, a language pair that defines the source and target languages, and the output text (the latter being a read-only property). You can create properties normally in an activity, but it is beneficial to create them so that they participate in the workflow and are available to other activities. In order to do this, use the following pattern to describe your properties:
Public Shared SomeProperty As DependencyProperty = _ DependencyProperty.Register("PropertyName", _ GetType(ReturnType), _ GetType(ClassName)) Public Property PropertyName () As ReturnType Get Return CType(MyBase.GetValue(SomeProperty), _ ReturnType) End Get Set(ByVal value As ReturnType) MyBase.SetValue(SomeProperty, value) End Set End Property
The initial shared field of type DependencyProperty identifies the field that will be used to communicate with other activities. DependencyProperty is a common type used in WPF programming, enabling easier communication between nested types. The Public property enables the more common use of the property. Notice that it stores the data in the shared property between all instances of the type.
As described, there are three properties in the translation activity:
Public Shared InputTextProperty As DependencyProperty = _ DependencyProperty.Register("InputText", _ GetType(System.String), _ GetType(TranslateActivity)) Public Shared TranslationTypeProperty As DependencyProperty = _ DependencyProperty.Register("TranslationType", _ GetType(TranslationOptions), _ GetType(TranslateActivity)) Public Shared OutputTextProperty As DependencyProperty = _ DependencyProperty.Register("OutputText", _ GetType(System.String), _ GetType(TranslateActivity)) <DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)> _ <BrowsableAttribute(True)> _ <DescriptionAttribute("Text to be translated")> _ Public Property InputText() As String Get Return CStr(MyBase.GetValue(InputTextProperty)) End Get Set(ByVal value As String) MyBase.SetValue(InputTextProperty, value) End Set End Property <DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)> _ <BrowsableAttribute(False)> _ <DescriptionAttribute("Translated text")> _ Public ReadOnly Property OutputText() As String Get Return CStr(MyBase.GetValue(OutputTextProperty)) End Get End Property <DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)> _ <BrowsableAttribute(True)> _ <DescriptionAttribute("Language pair to use for the translation")> _ Public Property TranslationType() As TranslationOptions Get Return CType(MyBase.GetValue(TranslationTypeProperty), TranslationOptions) End Get Set(ByVal value As TranslationOptions) MyBase.SetValue(TranslationTypeProperty, value) End Set End Property
Attributes are added to the properties to enable communication with the designer. The core translation method is assigned to the ExecuteCode property of the Code activity. It calls the Google translation service and extracts the result from the returned HTML:
Private Const SERVICE_URL As String = "http://translate.google.com/translate_t" Private Sub Translate(ByVal sender As System.Object, _ ByVal e As System.EventArgs) 'uses Google's translation service to translate text ' Dim req As String = _ String.Format("hl=en&ie=UTF8&text={0}&langpair={1}", _ Encode(Me.InputText), _ BuildLanguageClause(Me.TranslationType)) Dim client As New Net.WebClient Dim resp As String Try resp = client.UploadString(SERVICE_URL, req) If Not String.IsNullOrEmpty(resp) Then MyBase.SetValue(OutputTextProperty, _ Decode(ExtractText(resp))) End If Catch ex As Exception Console.WriteLine("Error translating text: " & ex.Message) End Try End Sub
A typical request to the Google translation service is performed using the service’s Web page, available at www.google.com/translate_t. However, you can make the same type of request the Web page would, and parse the resulting HTML to extract the returned text. The request is made using a POST, to enable sending large blocks of text. For safety, the text is URL encoded using the HttpUtility class.
The routines used by the Translate method are as follows:
Private _langOptions As New List(Of String)() Public Sub New() ' This call is required by the Windows Form Designer. InitializeComponent() ' Add any initialization after the InitializeComponent() call. _langOptions.Add("en|fr") _langOptions.Add("en|es") _langOptions.Add("en|de") _langOptions.Add("en|it") _langOptions.Add("en|zn-CH") _langOptions.Add("en|ru") _langOptions.Add("fr|en") _langOptions.Add("es|en") _langOptions.Add("de|en") _langOptions.Add("it|en") _langOptions.Add("ru|en") _langOptions.Add("zn-CH|en") End Sub Private Sub Translate(ByVal sender As System.Object, _ ByVal e As System.EventArgs) 'uses Google's translation service to translate text ' Dim req As String = _ String.Format("hl=en&ie=UTF8&text={0}&langpair={1}", _ Encode(Me.InputText), _ BuildLanguageClause(Me.TranslationType)) Dim client As New Net.WebClient Dim resp As String Try resp = client.UploadString(SERVICE_URL, req) If Not String.IsNullOrEmpty(resp) Then MyBase.SetValue(OutputTextProperty, _ Decode(ExtractText(resp))) End If Catch ex As Exception Console.WriteLine("Error translating text: " & ex.Message) End Try End Sub Private Function Encode(ByVal value As String) As String Return Web.HttpUtility.UrlEncode(value) End Function Private Function Decode(ByVal value As String) As String Return Web.HttpUtility.HtmlDecode(value) End Function Private Function BuildLanguageClause( _ ByVal languages As TranslationOptions) As String Dim result As String = String.Empty result = _langOptions.Item(languages) Return result End Function Private Function ExtractText(ByVal value As String) As String Dim result As String = String.Empty Dim r As RegularExpressions.Regex Dim m As RegularExpressions.Match r = New RegularExpressions.Regex("<div?[^>]*>(?<result>[^<]*)</div", _ RegularExpressions.RegexOptions.IgnoreCase Or _ RegularExpressions.RegexOptions.Multiline Or _ RegularExpressions.RegexOptions.IgnorePatternWhitespace) m = r.Match(value) If m IsNot Nothing Then result = m.Groups.Item("result").Value End If Return result End Function
The _langOptions list is used to track the strings needed by the various language pairs. This is used by the BuildLanguageClause method to write the appropriate pair to the posted data. The order of the items in the TranslationOptions enumeration matches the order in which items are added to the list, so the BuildLanguageOptions method simply does a lookup into the list.
The ExtractText function uses a regular expression to extract the translated text. The translated text appears in a <div> tag within the resulting HTML. Fortunately, it is the only <div>, although you could modify the regular expression to look for a div with the id of result_box.
<div id=result_box dir=ltr>Bonne chance, Mandrin. Je pense que ceci pourrait juste fonctionner </div>
The resulting activity can now be compiled and included in other workflows. Just as with custom controls, you can add this DLL to the Toolbox using the Choose Toolbox Items dialog after it has been compiled. If the Workflow Activity project is in the same solution as the workflow, it will be automatically added to the Toolbox after it has been compiled. Figure 25-11 shows the Translate activity added to the earlier example.
Recall that the Message field was used to store the message you wanted the workflow to generate. This is the text you want to translate. Click the ellipsis button on the InputText property in the property grid to bring up the Bind property dialog (see Figure 25-12). This enables you to visually connect the Message field to the input of the TranslateActivity.
The last change to the workflow is to update the text you output. Change the code for the SayGreetings method to display the OutputText of the TranslateActivity, as shown here:
Private Sub SayGreetings(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Console.WriteLine(Me.translateActivity1.OutputText & ", from workflow") Console.WriteLine("Press enter to continue") Console.ReadLine() End Sub
Select the TranslationType and run the test project. Depending on the time of day and the language selected, you should see something similar to Figure 25-13.
Категории