The recommendation is to create a simple setup project in Visual Studio for installing your Interop Forms project and the associated prerequisites and have this run in advance of whateve
Trang 11154 ❘aPPendix B Visual BasiC PowER PaCks tools
figure B - 2
Because Visual Basic Express Edition does not support add - ins, this application will not be updated when you install the software
To validate your installation, there are three easy items you can check First, once the installation is
complete, the help topic associated with the Interop Forms Toolkit 2.1 should open Second, when you access the Tools menu, the fi rst item in the menu should be the option to Generate Interop Form Wrapper Classes This menu item should be located above the standard option to Attach Process Third, and
probably most important, when you access the File menu and select the New Project dialog, you should see two new project types within the Visual Basic section, as shown in Figure B - 2
The fi rst custom project type is the VB6 Interop User Control project type This type of project enables you
to create user controls that can then be used to populate the body of an MDI window This project type was introduced with version 2.0 of the Interop Forms Toolkit and is the solution the Visual Basic team developed
to support interoperation within an MDI environment
The second project type is the VB6 InteropForm Library project As the original project type, it was designed to enable you to create a DLL that defi nes a NET form
After you have validated that your installation is working, the next step is to create a simple Interop Form
creating a simple interop form
Select the project type shown in Figure B - 2 and rename the solution ProVB_AppB_InteropForm Click
OK to generate your source project fi les The resulting project opens, and you can open and edit your new Windows Form However, note that what you are creating, while it supports the Form Designer, isn ’ t a standalone executable If you open your project properties, you ’ ll fi nd that your project will build as a DLL, not a standalone executable
Another thing to note is that as part of the generation of your project, a fi le named InteropInfo.vb is created This fi le takes settings that might otherwise exist in your AssemblyInfo.vb fi le and places them here so they are a bit more apparent The fi rst line references the standard COM Interop classes and turns these settings off This is important because you won ’ t be using traditional COM Interop; you ’ ve added
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 2Using the interop forms Toolkit 2.1 ❘ 1155
a new Interop class specifically for this purpose By moving this setting into a separate file, if you do accidentally cause the AssemblyInfo.vb file to be regenerated by Visual Studio, you’ll get a compile error This is good because you can quickly and easily delete the newly duplicated line from AssemblyInfo.vb
and not wonder why your project suddenly isn’t working correctly Compile errors are always better than runtime errors The other item in this file is a declaration that extends the My namespace to include the Interop Toolbox In general, you shouldn’t make any changes to this file, but now you know what it’s doing.Opening InteropForm1.vb in the designer, you have a typical design surface for a form, on which you can add controls Behind the scenes is the code that contains the following:
Imports Microsoft.InteropFormTools
<InteropForm()> _ Public Class InteropForm1 End Class
Code snippet from InteropForm1
As you can see, the default class definition has been decorated with an attribute indicating that this class should be considered an InteropForm This enables the postprocessor that is used to generate your COM wrappings to recognize which type of wrapping should be applied to this class
For now, however, go to the Form Designer, and, because this is a truly simple demo, drag a label and
a TextBox control onto the display Within the code, create the four other types of interface members you’ll want in your production code: an initializer, a property, a method, and an event (in that order) The following code is placed within your class definition:
Public Sub New() ' This call is required by the Windows Form Designer.
InitializeComponent() ' Add any initialization after the InitializeComponent() call.
End Sub <InteropFormInitializer()> _ Public Sub New(ByVal label As String) Me.New()
Label1.Text = label End Sub
<InteropFormProperty()> _ Public Property TextBoxText() As String Get
Return TextBox1.Text End Get
Set(ByVal value As String) TextBox1.Text = value End Set
End Property <InteropFormMethod()> _ Public Sub ChangeLabel(ByVal lbl As String) Label1.Text = lbl
RaiseEvent CustomEvent(lbl) End Sub
<InteropFormEvent()> _ Public Event CustomEvent As CustomEventSig 'Declare handler signature…
Public Delegate Sub CustomEventSig(ByVal lblText As String)
Code snippet from InteropForm1
For the initialization code, you’ll note that first a default New constructor is created When you define the default New constructor, it adds the call to InitializeComponent, which handles the creation of your controls within the form Thus, when the object is initialized, you will be able to reference the controls you have placed on the form
The next step is to create a parameterized constructor so that you can quite literally pass a parameter
as part of the initialization process Note that similar to the class itself, the exposed initialization
Trang 31156 ❘aPPendix B Visual BasiC PowER PaCks tools
method has an attribute as part of its declaration Each type of class member that is to be exposed gets
an attribute matching the type of that method Thus, for the New method, the type of the attribute is
InteropFormInitializer For this simple example, the parameterized New(ByVal label As String) simply changes the text associated with the label Finally, although this class is defi ned in NET syntax, COM and VB6 don ’ t allow parameterized New statements Thus, when you reference this parameterized initializer, you ’ ll fi nd that the method name is in fact Initialize
Next, the code defi nes and exposes a public property In this case, to help simplify the code, there isn ’ t a private member variable to hold the value; this provides an easy way for the code that creates this form to set and retrieve the value of the text box Similarly, there is a method to allow the calling code to update the label shown on the form Note that it has also been attributed; and after you update the label for demonstration purposes, it raises the custom event that is defi ned next
That event, called CustomEvent , is defi ned with an attribute, but the event that is defi ned must also defi ne the signature or defi nition of its handlers In this case, the Delegate CustomEventSig handles a single parameter This NET code, as noted, provides a basic example of each of the primary types of Interop you ’ ll want to carry out The next step is to generate your Interop methods
One of the key differences between an InteropForms project and an Interop User Control project is this step Only the InteropForms project requires the generation of custom COM wrappers To do this, access the Tools menu and select Generate InteropForm Wrapper Classes There is no user interface; instead, the generation process will create a new directory in your project containing the InteropForm1.wrapper.vb class, as shown in Figure B - 3
figure B - 3
For readers developing on Vista and Windows 7: Keep in mind that registry access requires elevated permissions You need to start Visual Studio with the Run as Administrator option on your right - click context menu If you don ’ t, then when you attempt to automatically register your newly built DLL as a COM component, you ’ ll get an error, which Visual Studio refl ects as a Build Error
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 4Using the interop forms Toolkit 2.1 ❘ 1157
At this point, your application is ready to be called from VB6 If you follow best practices, you’ll have the VB6 integrated development environment (IDE) installed on the machine with Visual Studio 2010 In that scenario, you can immediately go to your VB6 project and reference the necessary DLLs, both the Interop Forms Toolkit DLL and your custom DLL Otherwise, you’ll need to get ready for deployment now instead
of later
deployment
To deploy your Interop Forms project, you need a traditional MSI installation Creating a setup project is covered in Chapter 34, so the details of creating your setup project aren’t repeated here However, note a couple of special steps In order for your new Interop Forms project to work on the client, the client needs both the NET Framework 2.0 redistributable and the second MSI you downloaded earlier in this chapter,
microsoft.interopformsredist.msi (refer to Figure B-1) If you are using Visual Studio to create your installation package, then you can add these items as prerequisites for installing your DLL via the user interface
The recommendation is to create a simple setup project in Visual Studio for installing your Interop Forms project and the associated prerequisites and have this run in advance of whatever legacy installation project you have To extend an existing MSI, you need to carry out the appropriate steps for the tool generating your MSI, a subject beyond the scope of this appendix
debugging
When you first start planning to work with the toolkit, you might try to keep the VB6 IDE on a separate machine from your primary development machine However, this leads to two issues First, in order to work with the Interop Forms tools on your VB6 machine, you need to install the tools package a second time That’s a minor issue Second, because VB6 doesn’t know how to step into NET applications, if you want to debug the Interop Form you created in NET, you have a problem The solution to this, of course, is to run both development environments on the same machine
Alternatively, you can try to create a simple Windows Forms EXE that will call and initiate your Interop Forms project from within NET The debugging isn’t perfect, of course, because you aren’t actually calling your code across the correct interface, but it should enable you to find most pure NET coding issues You can also leverage the Debug and Trace classes, but you won’t have any interactive breakpoints in that scenario
This still leaves unresolved the issue that you can’t just open Visual Studio and expect the VB6 IDE to call
it when you are in Debug mode Therefore, this section briefly discusses debugging Interop Forms Toolkit projects when you are running your VB6 application
Once you have compiled your NET application, you have a DLL This DLL is then exposed to your VB6 development environment and added as another COM component in your VB6 application However, when you debug, you can’t step into this DLL from Visual Basic Presuming you have started your Visual Basic 6.0 project so that its process is now running, your next step is to open Visual Studio and your Interop Forms project It is hoped that you have set typical breakpoints in your source code and you might even add new breakpoints
Next, go to the Tools menu in Visual Studio and select the Attach to Process menu item At this point, you get a dialog containing a list of running processes Locate the “Visual Basic 6.0.exe” process Once you have found this process, which represents the running application in VB6, attach to this process
At this point, you can work with your running application; and when the call is made into your NET code, Visual Studio detects the call into the DLL and stops you on your breakpoint In order for Visual Studio
to detect the DLL call, you must be calling the same copy of your DLL that your Interop Forms project references In other words, you can’t just copy it off to some other location on your local machine for installation
Trang 51158 ❘aPPendix B Visual BasiC PowER PaCks tools
VB6 development
Overall, the development process in VB6 is simple Once you have either built your project or deployed it
to the machine on which you have the VB IDE, you ’ ll need to add references to both the Microsoft Interop Form Toolkit library and your custom DLL Keep in mind that both of the DLLs must be registered on your VB6 IDE machine in order for them to be visible If you are building on the same machine, then they are automatically visible Once you have added references for these libraries, you can create a new instance of your Interop Form ’ s Form class and call the standard methods and any custom methods you ’ ve exposed on that form
The one key point to remember, which was mentioned earlier but bears repeating, is that if you have created
a custom constructor, in order to use it, you will call an Initialize method on your Interop Form ’ s Form class
final interop Tips
As noted earlier in the book during the discussion of the WPF Interop controls, the Interop control packages aren ’ t perfect Each has certain limitations that reduces its desirability for the long term To resolve this, keep track of how much of various branches you have already converted There will be a point where it is time to convert a larger section so that you can reduce the number of different Interop DLLs that you are using
Along these lines, note that you can ’ t put an Interop Form and an Interop user control into the same project Each of these items needs its own DLL; and, in fact, you should consider it a best practice to only expose the DLL for a single form or control Similarly, don ’ t plan on calling a VB6 form from within your Interop Form The Interop logic was written to enable you to call NET from VB6
In terms of interfaces, the Interop layer was designed to support only a minimum number of interface types In particular, the String , Integer , and Boolean types should be at the core of what you expect to pass in terms
of data In theory, the Object type is supported, which enables you to pass custom data, so you could pass a
Recordset from NET to VB6 or vice versa; of course, VB6 doesn ’ t know about a Dataset object, so you need
to reference VB6 types as the generic object In general, the best practice is to keep your interfaces as simple as possible
When you start the VB6 IDE with your project, it attaches to your DLL Normally this isn ’ t an issue until you fi rst run your VB6 application At this point, you can ’ t rebuild your Interop project The Interop project is, in fact, referenced and therefore locked by VB6 If you need to rebuild your Interop project, you need to fi rst shut down the VB6 development environment so that your code will correctly reference your latest build As noted previously, debugging your Interop project from VB6 isn ’ t the most productive set of steps
If you change any of the method attributes, you need to regenerate the Interop wrapper classes that you generated in the last step of creating your Interop Forms project Moreover, although it wasn ’ t covered, you can raise errors from NET into VB6 To do this, you want to leverage the following method call on the custom My namespace that was defi ned as part of your Interop Form:
My.InteropToolbox.EventMessenger.RaiseApplicationEvent("CRITICAL_ERROR", _ "Error Detail.")
The other runtime issue that you may encounter is that certain internal events to your NET application will not be triggered in the same fashion that they were in VB6 Under VB6, for example, when you referenced a
If you stop and restart your VB6 application, Visual Studio will maintain the attachment, but if you close the VB6 IDE, then you ’ ll need to reattach the debugger in Visual Studio
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 6property on a Form class, this triggered the Load event on that class Under NET, the Load event is not fired until the form is being displayed, so you need to recognize the impact on any code that you previously set to run on the Load event.
The remaining issue is related to the VB6 IDE The IDE and VB6 don’t really recognize that if you have started a NET DLL, there are other in-memory classes to release For a deployed application, this isn’t an issue because when the application is closed, all of the memory associated with the process is automatically released When you are debugging in VB6, however, the core process is associated with the IDE, not your application As a result, the resources are not released between debugging cycles To ensure that they are released, you can explicitly instantiate a series of code modifications contained in the Interop help files and release the NET resources associated with your application The recommendation is to implement these calls only after your references with the Interop tools are functioning correctly
using The PoWer PacKs 3.0 Tools
Unlike the Interop Forms Toolkit, the Power Packs extensions are intended to facilitate some of the same development simplicity that existed in VB6 for tasks such as printing These classes aren’t meant
to support Interop, they are meant to support migration in the sense that the code for creating simple geometric shapes or using the VB style of form printing could be implemented using syntax similar to that of VB6 After these Power Packs were released, the printing syntax was so popular that the Visual Basic team migrated those classes into the core features of Visual Studio 2008 The continued success
of the 3.0 features led to the inclusion of the most of the 3.0 Power Packs classes in Service Pack 1 for Visual Studio 2008 These components, along with the repeater control continue to ship with Visual Studio 2010
Similar to the Interop Forms Toolkit, the Power PacksTools are already installed for Visual Studio
2010 For previous versions of Visual Studio then can be downloaded and installed from the Microsoft downloads If you review a typical Windows Forms project in Visual Studio 2010, you’ll see the display shown in Figure B-4, which already includes the controls as part of your default Toolbox
Unlike the Interop Forms Toolkit, there is no need to begin with a special project template There is no COM Interop involved because the Power Packs don’t target VB6 They target experienced VB developers who want to be able to continue to implement certain tasks in the same way they could in VB6
When your application ships, you still need to ensure that you create a dependency for the Power Packs library if you aren’t using the DLLs that are included with Visual Studio, but that’s it Additionally, because the Power Packs are just another set of NET libraries, there aren’t any issues related to debugging
For the sample project shown in Figure B-4, you can create a new Windows Forms application and add the PrintForm control to it Visual Studio 2010 has a Toolbox section for the Visual Basic Power Packs, showing the OvalShape and RectangleShape shape controls along with the LineShape, Data Repeater
and PrintForm controls, as shown in Figure B-4
Add a RectangleShape to the upper section of the display and an OvalShape to the center of the display Without getting into pages of details here, using the Visual Studio designer, you should customize the look and feel of the display by adding a variety of controls Take some time to color and fill the shape controls with a solid color The gradient colors are defined by selecting a fill color (Coral), a FillGradientColor (Navy), a FillGradientStyle (Horizontal), and a FillStyle (Solid) All of this can and should be done within the Visual Studio designer to achieve a display similar to what is shown in Figure B-5
Using the Power Packs 3.0 Tools ❘ 1159
Trang 71160 ❘aPPendix B Visual BasiC PowER PaCks tools
figure B-5
figure B-4
The application should build The next step is to ensure that the check box in the lower-right center, labeled
“Landscape” in the figure, is checked Having done this, label the button in the bottom center of the display
“Print Me” and double-click it in the Design view to trigger the automatic event handler generation
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 8The only code needed for this printing demonstration is placed within the handler for this button The code hides the button, determines whether or not the Landscape check box is checked, and uses the Power Packs PrintForm
control to Print Preview the document Once this is completed, the Print Me button is made visible again:
Private Sub ButtonPrintForm_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ButtonPrintForm.Click ' Hide the print button since you don't want to see it in the output.
ButtonPrintForm.Visible = False ' Set the printing to landscape mode by default PrintForm1.PrinterSettings.DefaultPageSettings.Landscape = CheckBox2.Checked ' Update the print action to PrintPreview so instead of wasting paper ' we see what the output would look like if sent to a printer.
PrintForm1.PrintAction = Printing.PrintAction.PrintToPreview ' Execute the print logic.
PrintForm1.Print(Me, PowerPacks.Printing.PrintForm.PrintOption.ClientAreaOnly) 'PrintForm1.Print()
' Restore the print button ButtonPrintForm.Visible = True End Sub
Code snippet from Form1
The code shows how you can reference the PrinterSettings property, which contains the page settings
to change details regarding how the page is printed The PrintAction defines what the control should do There are three options: print to the default/selected printer, print to a file, or use the Print Preview window
In this case, displaying the results (print preview) is the most useful option
The next line is all you need by default to print the current window Note that this control doesn’t call the form to determine what is visible on the form Instead, it essentially captures the current screenshot of the form for printing.The current code uses the ClientAreaOnly option, which you are encouraged to test If you open and resize this project so that it is fairly wide, and print in profile mode, you’ll see how the control truncates the printed image (see Figure B-6)
figure B-6
Using the Power Packs 3.0 Tools ❘ 1161
Trang 91162 ❘ aPPendix B Visual BasiC PowER PaCks tools
As shown in Figure B-6, the default behavior is to show the contents of the screen without the border displayed Unfortunately, in this case the printout shows less than the full window contents However, don’t stop at this option; try out other options The various display options do not always capture the screen accurately, so test In some cases the only things visible in the Print Preview window are the shape controls.However, before you print again, go to the print event handler and comment out the parameterized print line and uncomment the default print line In this case, specify the window, which is Me, and then add one
of the print options The results, which are now correct, are shown in Figure B-7
Overall, the Power Packs shape controls enable you to easily add a custom look to your otherwise gray forms The controls are somewhat limited, but if you want a quick and easy way to add some graphics, they do the trick Similarly, the Print control is a quick and easy way to create a hard copy of what your application is displaying However, keep in mind that the Print control sacrifices capabilities and customizations in order to provide a simple interface
The Power Packs 3.0 provide tools that VB6 developers can leverage for migrating an application; and for
a rapid application design (RAD) prototype, they provide a dynamic and visually interesting display Just keep in mind that when it comes to the shape controls, if you need any sort of fancy graphics, then it is recommended that you leverage the graphical capabilities provided as part of WPF
summary
This appendix covered the Visual Basic Power Packs This set of off-cycle release tools enables experienced Visual Basic developers to leverage their knowledge and existing code with the new capabilities of NET The Visual Basic team has created two downloadable packages that improve your ability to manage COM
to NET Interop migration and to continue to print and create graphics the same way you did before As with all Interop-focused solutions, there are key limitations in working with the Interop Forms Toolkit, but
figure B-7
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 10in general it provides classes that will help you if you need to migrate an existing application in a controlled and cost-effective manner In particular, this appendix highlighted the following:
The focus of the Visual Basic Power Packs
Trang 12C Workfl ow 2008 specifi cs
As discussed in Chapter 26, Windows Workfl ow Foundation (WF) has changed substantially in the NET Framework 4 The models used to organize your workfl ows have changed, and many of the older activities do not have counterparts in the new version This appendix discusses the version of WF supported by the NET Framework versions 3.0 and 3.5 (i.e., Visual Basic 2005 with NET Framework 3.0 and Visual Basic 2008) This information is retained in this edition for those users who still need
to maintain existing WF solutions using these older versions For new applications, the new model
is highly recommended Here, the older style of building workfl ows is called Windows Workfl ow Foundation 3.x (or just WF 3.x)
Building WorKfloWs
The actual workfl ow fi les in WF 3.x are XML fi les written in a version of XAML This is the same XAML used to describe Windows Presentation Foundation (WPF) fi les (See Chapter 17 for more details on WPF.) They describe the actions to perform within the workfl ow, and the relationship between those actions You can create a workfl ow using only a text editor, but Visual Studio makes creating these workfl ows much easier It provides a graphical designer that enables developers to visually design the workfl ow, creating the XAML in the background The following code shows a section of the XAML for a workfl ow:
< ns0:CodeFieldReferenceExpression.TargetObject >
< ns0:CodeTypeReferenceExpression Type="TranslateActivity.TranslateActivity" / >
< /ns0:CodeFieldReferenceExpression.TargetObject >
Trang 131166 ❘aPPendix c woRkFlow 2008 sPECiFiCs
of creating workflows: sequential and state machine Sequential
workflows (see Figure C-1) 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 reaches 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
is needed for the workflow
State machine workflows (see Figure C-2) 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 This style of workflow is common in hardware
systems 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
figure c-1
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 14figure c-2
A good way to identify a candidate for a state machine workflow is determining whether the process is better defined in terms of modes, rather than a linear series of steps 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 incrementally Start Visual Studio and create a new Sequential Workflow Console application (see Figure C-3) called HelloWorkflow Note that you will need to target the NET Framework 3.5 (or 3.0)
in order to see this project type when you are creating the new project The dropdown list at the top of the New Project dialog (highlighted in Figure C-3) allows you to select the version of the NET Framework used
by the project Select NET Framework 3.5 from the list
figure c-3
Building Workflows ❘ 1167
Trang 151168 ❘aPPendix c woRkFlow 2008 sPECiFiCs
This project creates two files: a module that includes the Main file for the
application and the workflow The sequential workflow begins life with only
two steps: start and finish, as shown in Figure C-4 You build the workflow by
adding steps between these two
To begin, drag a Code activity between the start and finish markers Note that
even if you are targeting the NET Framework 3.5, most of the controls are still
located within the Windows Workflow 3.0 section of the Toolbox
Notice the red exclamation mark on the new activity in the diagram (shown
in grayscale in Figure C-5) WF makes heavy use of these tips to help you set
required properties
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
Code snippet from HelloWorld
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 C-6), along with the message you should be expecting
figure c-4
figure c-5
figure c-6
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 Select Condition Editor (see Figure C-7) To display the Select Condition Editor, select Declarative Rule Condition for the Condition property of the first
ifElseBranchActivity component Once you have selected Declarative Rule Condition, you can click the ellipsis on the ConditionName property to display the dialog
Clicking New brings up the Rule Condition Editor (see Figure C-8) This enables you to create simple expressions that will be used by the IfElse activity to determine flow
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 16Click the New button on the Select Condition Editor to add a new rule to the If half of the IfElse activity
to determine whether the current time is before noon:
System.DateTime.Now.TimeOfDay.Hours < 12Right-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
Add a Code activity to each of the three sections of the diagram (see Figure C-9) You will use these activities to affect the message that is displayed Assign the properties as follows:
figure c-9
codeActivity2 ExecuteCode SetMessageMorningcodeActivity3 ExecuteCode SetMessageAfternooncodeActivity4 ExecuteCode SetMessageEvening
Finally, update the code for the SayGreetings method created earlier to include the new Message variable, and the methods used to set the value
Public class Workflow1 Inherits SequentialWorkflowActivity Private Message As String
Building Workflows ❘ 1169
Trang 171170 ❘aPPendix c woRkFlow 2008 sPECiFiCs
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 SetMessageEvening(ByVal sender As System.Object, _ ByVal e As System.EventArgs)
Message = "Good night"
End Sub End ClassEach 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 used 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 activities for WF 3.x are defined within the System.Workflow.Activities namespace These 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 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
ele-ment 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:
CallExternalMethod As the name implies, this activity calls an external method The activity requires
two properties The first property identifies an interface shared by the workflow and the external code The second property identifies the method on that interface that will be called If the method requires additional parameters, they appear on the property grid after setting the other two properties This activity
is frequently used in combination with the HandleExternalEvent activity This activity executes the external method synchronously, so be cautious when calling external methods that take a long time to execute
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 18acTiViTy descriPTion
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 property identifies a shared interface and the second property 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 the 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 Keep in mind that this external workflow is called synchronously, so the original workflow will not be processed until the called workflow completes
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 flow control 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:
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 for monitoring 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
Building Workflows ❘ 1171
Trang 191172 ❘aPPendix c woRkFlow 2008 sPECiFiCs
Several composite activities may cooperate to complete a single logical action by grouping other activities:
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 either the child activities 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 a 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:
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 item selected by the client
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 CodeActivity These activities are the cornerstone of any workflow The standard activities in this group include the following:
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
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 20acTiViTy descriPTion
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 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 existing system
So you can 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, called TranslationActivity Again, you will have to target the NET Framework 3.5 to view the correct template This project will create a DLL that contains the activities you create 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
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:
Public Enum TranslationOptions As Integer EnglishToFrench
EnglishToSpanish EnglishToGerman EnglishToItalian EnglishToRussian EnglishToChinese FrenchToEnglish SpanishToEnglish GermanToEnglish ItalianToEnglish RussianToEnglish ChineseToEnglish End Enum
Code snippet from TranslateActivity
Building Workflows ❘ 1173
Trang 211174 ❘aPPendix c woRkFlow 2008 sPECiFiCs
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
Code snippet from TranslateActivity
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 WF 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 translate 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)) <DesignerSerializationVisibility(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 <DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)> _ <BrowsableAttribute(False)> _
<DescriptionAttribute("Translated text")> _ Public ReadOnly Property OutputText() As String Get
Return CStr(MyBase.GetValue(OutputTextProperty)) End Get
End Property <DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)> _ <BrowsableAttribute(True)> _
<DescriptionAttribute("Language pair to use for the translation")> _
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 22Public 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
Code snippet from TranslateActivity
While you may be tempted to not include the line continuation characters on some of these long lines, remember that you are targeting NET Framework 3.5, so you will need to continue to use line continuation characters here
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 AJAX translation service: Private Const SERVICE_URL As String = _
Encode(Me.InputText), _ BuildLanguageClause(Me.TranslationType)) Dim respString As String
Dim req As HttpWebRequest Try
req = CType(WebRequest.Create(reqString), HttpWebRequest) req.ProtocolVersion = HttpVersion.Version10
Using resp As HttpWebResponse = CType(req.GetResponse(), _ HttpWebResponse)
If resp.StatusCode = HttpStatusCode.OK Then respString = ExtractText(resp.GetResponseStream) Else
respString = "Error translating text"
End If End Using
If Not String.IsNullOrEmpty(respString) Then MyBase.SetValue(OutputTextProperty, _ Decode(respString))
End If Catch ex As Exception Console.WriteLine("Error translating text: " & ex.Message) End Try
End Sub
Code snippet from TranslateActivity
A typical request to the Google AJAX translation service is performed using the service URL, available at
http://ajax.googleapis.com/ajax/services/language/translate You can get more information on this API at http://code.google.com/apis/ajaxlanguage/documentation The service then returns a JSON (JavaScript Object Notation) response A typical response looks like
{"responseData": { "translatedText":"Ciao mondo"
},
"responseDetails": null, "responseStatus": 200}
Building Workfl ows ❘ 1175
Trang 231176 ❘ aPPendix c woRkFlow 2008 sPECiFiCs
where the result is the text after the “translatedText” label You could use normal string handling to find the resulting text Instead, I’ve used the JSON handling code from the System.ServiceModel.Web.dll To use these classes, you need to include references to the NET assemblies System.ServiceModel.Web.dll, and System.Runtime.Serialization.dll
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 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 = Encode(_langOptions.Item(languages)) Return result
End Function Private Function ExtractText(ByVal data As Stream) As String Dim result As String = String.Empty
Dim reader As XmlDictionaryReader = _ JsonReaderWriterFactory.CreateJsonReader(data, _ XmlDictionaryReaderQuotas.Max)
While reader.Read
If reader.Name = "translatedText" Then result = reader.ReadElementString() End If
End While Return result End Function
Code snippet from TranslateActivity
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 XmlDictionaryReader to extract the translated text This is created using the JsonReaderWriterFactory class To use these classes, you also need to add a couple of imports
to the Translate.vb file:
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 24Imports System.Net Imports System.Runtime.Serialization.Json Imports System.Xml
Imports System.IO
Code snippet from TranslateActivity
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 C-10 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 Select the TranslateActivity and click the ellipses button on the InputText
property in the property grid to bring up the Bind property dialog (see Figure C-11) 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(translateActivity1.OutputText & ", from workflow") Console.WriteLine("Press enter to continue")
Console.ReadLine() End Sub
Code snippet from HelloWorkflowTranslate
Select the TranslationType and run the test project Depending on the time of day and the language selected, you should see something similar to what is shown in Figure C-12
Building Workflows ❘ 1177
Trang 251178 ❘aPPendix c woRkFlow 2008 sPECiFiCs
using WorKfloWs WiTh oTher aPPlicaTions
Workflows are not typically standalone applications, or run as part of a console application, although this
is an excellent way to develop them initially Usually workflows are created to work within some larger application, so you need to integrate your workflow with the rest of your application, whether it is a Windows Forms application or ASP.NET
using Workflow foundation with Windows forms
When combining WF with Windows Forms, there are three main points of contact:
hosting (and starting) the workflow
WF runtime, load the workflow, and start it In addition, the workflow host may initialize event handlers for the events that the WF runtime will throw The following code shows an example of hosting the WF runtime and loading a workflow:
Imports System.Workflow.Activities Imports System.Workflow.ComponentModel Imports System.Workflow.Runtime Public Class MainForm
Private WithEvents wr As WorkflowRuntime Private wf As WorkflowInstance
Private Sub TranslateButton_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _
Handles TranslateButton.Click
If wr Is Nothing Then
wr = New WorkflowRuntime wr.StartRuntime() End If
'load a new instance of the workflow Me.EventList.Items.Add("Translating: " & Me.MessageField.Text) Dim parms As New Dictionary(Of String, Object)
parms.Add("Message", Me.MessageField.Text)
figure c-12
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 26Using Workflows with other applications ❘ 1179
wf = wr.CreateWorkflow(GetType(TranslateWorkflow.SimpleWorkflow), parms) 'start the workflow
wf.Start() End Sub Private Sub MainForm_FormClosing(ByVal sender As Object, _ ByVal e As System.Windows.Forms.FormClosingEventArgs) _ Handles Me.FormClosing
If wr IsNot Nothing Then
If wr.IsStarted Then wr.StopRuntime() End If
End If End Sub
Code snippet from HelloWorldWinForms
In addition, you have to load references to the three workflow DLLs, and to the assembly that holds the workflow you want to create Notice that you must create and start the WF runtime before you can load and start workflows While the preceding code creates only a single instance of a workflow, you can create multiple instances from a single application Stopping the runtime is not absolutely necessary but gives you better control when the resources used by the WF runtime are freed
The second step in working with WF and Windows Forms is providing parameters to the workflow This is done by supplying a Dictionary when you create the workflow The items in the Dictionary should match the public properties of the workflow This changes the code used to create the workflow in the preceding sample as follows:
'load a new instance of the workflow Dim parms As New Dictionary(Of String, Object) parms.Add("Message", Me.MessageField.Text)
wf = wr.CreateWorkflow(GetType(TranslateWorkflow.SimpleWorkflow), parms)
Code snippet from HelloWorldWinForms
By using a Dictionary with an Object value, any type of data can be supplied to the workflow This provides flexibility in terms of the number and type of parameters you supply to the workflow, including changing the parameters over time
The final step when working with WF and Windows Forms is retrieving data from the workflow This
is slightly more difficult than it may first seem because the workflow runs on a separate thread from the Windows Forms code Therefore, the workflow can’t directly access the controls on a form, and vice versa The communication between the two is best performed by having the workflow generate events The following code receives the WorkflowCompleted event and updates the ListBox control on the form:
Private Sub wr_WorkflowCompleted(ByVal sender As Object, _ ByVal e As System.Workflow.Runtime.WorkflowCompletedEventArgs) _ Handles wr.WorkflowCompleted
If Me EventList.InvokeRequired Then
Me EventList.Invoke(New EventHandler(Of WorkflowCompletedEventArgs)( _ AddressOf Me.wr_WorkflowCompleted), _
New Object() {sender, e}) Else
Me.EventList.Items.Add("Translation: " & _ e.OutputParameters("Message").ToString()) End If
End Sub
Code snippet from HelloWorldWinForms
Recall that the workflow runtime is actually running on a separate thread Therefore, any attempts to access the EventList directly throw an exception The first time through this code, the InvokeRequired property
of the EventList is true This means that the running code is executing on a separate thread In this case, the code invokes a new instance of the event, passing in copies of the sender and EventArgs This has the
Trang 271180 ❘aPPendix c woRkFlow 2008 sPECiFiCs
side effect of marshaling the data across to the thread
containing the form In this case, InvokeRequired is
false, and you can retrieve the data from the workflow
Figure C-13 shows the result
Combining ASP.NET with Windows Workflow
Foundation raises many of the same issues involved in
using WF with other technologies That is, you still need
to host the services and the runtime of WF within the
host process under which ASP.NET runs — within IIS
However, developing solutions using ASP.NET offers
more features and requires more decisions than other
solutions In particular, it is possible to publish workflows
as ASP.NET Web services Hosting workflows within ASP.NET solutions is similar to hosting workflows with Windows Forms, but an ASP.NET solution might actually be supporting multiple concurrent users This means that you must be more aware of where the runtime is created and how instances are created and freed
You can host a workflow as a Web service if it has one or more WebServiceInput activities This activity represents a SOAP endpoint The WebServiceInput activity needs two properties set: InterfaceType
and MethodName Communication between the client code and the Web service is achieved via a shared interface This interface is the value needed for the InterfaceType property It represents the contract between the client code and the WebServiceInput activity The MethodName identifies the method on the interface that will initiate the Web service call The first WebServiceInput activity should have the
IsActivating property set to true In addition to the WebServiceInput activity, the workflow should also include a WebServiceOutput activity if the method includes a return value Including a WebServiceFault
activity is also useful if you need to return an error to the client code If the Web service has parameters or return values, these may be mapped to the properties of the workflow using the Bind property dialog (see Figure C-14) Open this dialog by clicking the ellipsis next to the property in the Properties window
Once you have built the workflow, including the WebServiceInput and WebServiceOutput activities (see Figure C-15), you then publish it as a Web service This adds an additional ASP.NET Web Service project
to the solution The wizard creates the ASMX file that wraps the workflow and adds the required settings to the web.config file The ASMX wrapper does nothing but delegate to the workflow class
Trang 28Using Workflows with other applications ❘ 1181
The additional settings in the configuration file add a new section for configuring the workflow runtime and load the workflow HTTP handler that translates the incoming request:
</configSections>
<WorkflowRuntime Name="WorkflowServiceContainer">
<Services>
<add type="System.Workflow.Runtime.Hosting.ManualWorkflowSchedulerService, System.Workflow.Runtime, Version=3.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35"/>
<add type="System.Workflow.Runtime.Hosting.DefaultWorkflowCommitWorkBatchService, System.Workflow.Runtime, Version=3.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35" name="WorkflowHost"/>
</httpModules>
</system.web>
</configuration>
The resulting Web service works just like any other created by Visual Studio: You can access it in a browser
to receive a test form (see Figure C-16), request the WSDL, and access it using Web service clients
figure c-16
Trang 291182 ❘ aPPendix c woRkFlow 2008 sPECiFiCs
Beyond Web services, ASP.NET applications can also host and access regular workflows When hosting workflows in ASP.NET, keep in mind that your application may be accessed by many concurrent users,
so you must be aware of when you create the runtime instance In addition, remember that each workflow instance can use a good deal of memory Therefore, limit the creation of workflows to when they are needed and free them quickly when they are no longer needed
As you will probably want a single workflow runtime instance supporting all of your workflows, the best place to create the workflow runtime is when the application first starts You can do this in the application’s
Start event in the global.asax file:
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs) Dim wfRun As New System.Workflow.Runtime.WorkflowRuntime Dim wfSked As _
New System.Workflow.Runtime.Hosting.ManualWorkflowSchedulerService wfRun.AddService(wfSked)
wfRun.StartRuntime() Application.Item("WorkflowRuntime") = wfRun End Sub
Code snippet from TranslateService
This ensures that the same runtime is available to all sessions Next, free up the resources used by the runtime when the application ends:
Sub Application_End(ByVal sender As Object, ByVal e As EventArgs) Dim wfRun As System.Workflow.Runtime.WorkflowRuntime
wfRun = CType(Application.Item("WorkflowRuntime"), _ System.Workflow.Runtime.WorkflowRuntime)
wfRun.StopRuntime() End Sub
Code snippet from TranslateService
Running a workflow instance is now a matter of retrieving the runtime instance and using it to execute the workflow This leads to another issue related to the way Web pages are handled Recall that the
workflow typically runs asynchronously This could mean that the workflow instance continues to run
in the background after the Web page has returned Therefore, you must run the workflow instance synchronously, so that it completes before returning data to the Web page:
Dim wfRun As WorkflowRuntime wfRun = CType(Application.Item("WorkflowRuntime"), WorkflowRuntime) Dim wfSked As ManualWorkflowSchedulerService
wfSked = wfRun.GetService(GetType(ManualWorkflowSchedulerService)) Dim wfInst As WorkflowInstance
wfInst = wfRun.CreateWorkflow(GetType(SimpleWorkflow)) wfInst.Start()
wfSked.RunWorkflow(wfInst.InstanceId)
Code snippet from TranslateService
The preceding code extracts the workflow runtime from the Application storage It then retrieves the workflow scheduling service that was associated with the runtime as part of the Application_Start event handler This scheduling service executes the workflows synchronously This ensures that the entire workflow runs before the Web page is returned The runtime is also used to create a new instance of
the workflow desired, which is then started and associated with the scheduler You could provide parameters
to the workflow just as you did with the Windows Forms sample, by creating a Dictionary and populating it with the properties This Dictionary would then be provided as a second parameter on the CreateWorkflow
call Similarly, you could retrieve the result of the workflow using the OutputParameters property in the
Completed event handler for the workflow, just as you did with Windows Forms
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 30While Windows Workflow Foundation does not have the visual glitz of WPF or the broad reach of WCF, it is a highly useful addition to the NET Framework Most business applications have some need for workflows, and having a standard means of creating this workflow ensures that the workflow is fully featured and accurately reflects business needs As WF is readily available with the NET Framework, developers no longer need to recreate a core business rules engine with each application WF is extensible, so developers can take advantage
of it in their applications, without being limited to the designed features
As with the other components of the NET Framework, WF integrates well into other applications, including Windows Forms and ASP.NET applications It provides the means to extract the frequently complex workflow from those applications and to graphically design it This graphical representation can be used to communicate the process to business users, increasing the chance that the workflow is represented correctly Finally, as business needs change, it is a simple process to update the workflow, without requiring changes to the core application
summary ❘ 1183
Trang 32D enterprise services
Chapter 28 explored the vast hinterland of legacy software known as COM This appendix looks
at “ what COM did next ” and how it fi ts into the world of NET, in the form of NET Enterprise Services
To understand Enterprise Services, you must go back in time (all the way to the last century!) when
a number of technologies began to emerge from Microsoft, including Microsoft Transaction Server (MTS), Microsoft Message Queuing (MSMQ) , and Microsoft Clustering Services The aim of these
developments was to increase the scalability, performance, and reliability of applications
Handling transactions involved a considerable extension to the NT/COM runtime It also involved the introduction of several new standard COM interfaces, some to be used or implemented by transactional components and some to be used or implemented by the underlying resource managers, such as SQL Server These additions, along with some other innovations relating to areas such as
asynchronous COM, came to be known as COM +
This appendix explores the NET Enterprise Services In particular, it looks at transaction processing and queued components using the classes of the System.EnterpriseServices namespace The
System.EnterpriseServices provides a number of classes that wrap the technologies that composed COM+ These include the classes that represent the ObjectContext , and the component interfaces that assist the system in transactions and queuing
This is an enormous subject that could easily fi ll a whole book by itself, so this appendix only scratches the surface of it However, by the end of the appendix, you will understand how all the pieces fi t together Let ’ s begin by looking at what transactions are, and how they fi t into Visual Basic
TransacTions
A transaction is one or more linked units of processing placed together as a single unit of work, which
either succeeds or fails If the unit of work succeeds, then all the work is committed If the unit fails, then every item of processing is rolled back and the process is returned to its original state
The standard transaction example involves transferring money from account A to account B The money must either end up in account B (and nowhere else), or — if something goes wrong — stay in account A (and go nowhere else) This avoids the very undesirable case in which you have taken money from account A but haven ’ t put it in account B
Trang 331186 ❘aPPendix d ENtERPRisE sERViCEs
The acid Test
Transaction theory starts with ACID, an acronym describing the following properties that all transactions
should have:
➤ Atomicity—A transaction is atomic; that is, everything is treated as one unit However many different
components the transaction involves, and however many different method calls are made on those components, the system treats it as a single operation that either entirely succeeds or entirely fails If it fails, then the system is left in the state it was in before the transaction was attempted
➤ Consistency—All changes are done in a consistent manner The system goes from one valid state to
another
➤ Isolation—Transactions that are going on at the same time are isolated from each other If transaction
A changes the system from state 1 to state 2, transaction B will see the system in either state 1 or 2, but not some half-baked state in between the two
➤ Durability—If a transaction has been committed, the effect is permanent, even if the system fails.
Let’s illustrate this with a concrete example Imagine that after spending a happy afternoon browsing
in your favorite bookstore, you decide to shell out some of your hard-earned dollars for a copy of, yes,
Professional Visual Basic 2010 (a wise choice) You take the copy to the checkout and exchange a bit of cash
for the book A transaction is going on here: You pay money and the store provides you with a book.The important aspect of this transaction isn’t the exchange of money, but that only two reasonable
outcomes are possible—either you get the book and the store gets its money or you don’t get the book and the store doesn’t get its money If, for example, there is insufficient credit on your credit card, then you’ll leave the shop without the book In that case, the transaction doesn’t happen The only way for the transaction to complete is both for you to get the book and for the store to get its money This is the
decides to exert the wisdom of Solomon and give you half each
Now suppose you take the book home and the bookstore calls you to ask if they can have the book back Apparently, an important customer (well, far more important than you, anyway) needs a copy You would
find this a tad unreasonable, and a violation of the principle of durability.
At this point, it’s worth considering what implications all this is likely to have on the underlying components How can you ensure that all of the changes in the system can be unwound if the transaction is aborted at some point? Perhaps you’re in the middle of updating dozens of database files and something goes wrong.There are three aspects to rescuing this situation with transactions:
Knowledge that something has gone wrong
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 34This isn’t at all unusual: Sometimes a component can detect that something has gone wrong itself and signal that recovery is necessary, while on other occasions it may not be able to do so, because it has crashed.
Later, you will see how all this works as you build a transactional application
TransacTional comPonenTs
To understand what components are actually managed by Enterprise Services and what purpose they serve,
you need to consider what a typical real-world n-tier application looks like The bottom tier is the persistent
data store, typically a database such as SQL Server or Oracle However, there are other possible data stores,
including the file system (on Windows NT and above) These are termed resource managers because they
manage resources The software here is concerned with maintaining the integrity of the application’s data and providing rapid and efficient access to it
The top tier is the user interface This is a completely different specialization, and the software here
is concerned with presenting a smooth, easy-to-follow front end to the end user This layer shouldn’t actually do any data manipulation at all, apart from whatever formatting is necessary to meet each user’s presentational needs The interesting stuff is in the tiers in between—in particular, the business logic In the NET/COM+ transactional model, the software elements that implement this are components running under the control of the Enterprise Services runtime
Typically, these components are called into being to perform some sort of transaction and then, to all intents and purposes, disappear again For example, a component might be called into play to transfer information from one database to another in such a way that the information is either in one database or the other, but not both This component might have a number of different methods, each of which does a different kind of transfer However, each method call would carry out a complete transfer:
Public Sub TransferSomething() TakeSomethingFromA
AddSomethingToB End Sub
Crucially, this means that most transaction components have no concept of state; there are no properties
that hold values between method calls You can see the reason for this if you imagine what would happen
if you had a number of instances of the preceding components all vying for the attention of the database If instance one of the control started the transfer, remembering the state or current values of A and B just after instance two had done the same, you could end up with the state being different between the two instances This would violate the isolation of the transaction Persistence is left to the outside data stores in this model.The business logic is the area of the system that requires all the transactional management Anything that happens here needs to be monitored and controlled to ensure that all the ACID requirements are met The neatest way to do this in a component-oriented framework is to develop the business logic as components that are required to implement a standard interface The transaction management framework can then use this interface to monitor and control how the logic is implemented from a transactional point of view The transaction interface is a means for the business logic elements to talk to the transaction framework and for the transaction framework to reply to the logic elements
So what’s all this about not having state? Well, if you maintain state inside your components, then you immediately have a scaling problem The middle tiers of your application are now seriously resource hungry If you want an analogy from another area of software, consider why the Internet scales so well: because HTTP is a stateless protocol Every HTTP request stands in isolation, so no resources are tied up in maintaining any form of session It’s the same with transactional components
This is not to say that you can never maintain state inside your transactional components You can, but it’s not recommended, and the examples in this appendix don’t illustrate it
Transactional Components ❘ 1187
Trang 351188 ❘aPPendix d ENtERPRisE sERViCEs
an example of Transactions
For the transaction example, you’ll build a simple business-logic component that transfers data from one bank account to another account The current balance in the first bank account will be represented by a row
in one database, while the other will be represented by a row in another database
Before beginning, note one important point: You can’t have transactions without any resource managers It’s very tempting to assume that you can experiment with transactional component services without actually involving, say, a database, because (as you shall see) none of the methods in the transactional classes make any explicit references to one However, if you do try to do this, then you will find that your transactions don’t actually trouble the system’s statistics Fortunately, you don’t need to lay out your hard-earned cash for a copy of SQL Server (nice though that is), because a lightweight (but fully functional)
version of SQL Server is available: SQL Server 2008 Express Edition, or more simply SQL Server Express
In addition, SQL Express is available separately, so you can even work with databases if you use Visual Basic Express
Creating the Databases
First, set up the databases Check whether the Server Explorer tab is visible
in Visual Studio (see Figure D-1) If not, then open it by selecting View
Server Explorer Create a new database in the Data Connections tree
Right-click Data Connections and select Create New SQL Server Database
from the menu The Create New SQL Server Database dialog appears (see
Figure D-2)
Enter the database name (BankOfWrox) and select Use Windows Authentication After clicking OK, you
are prompted to create the database if it doesn’t exist You should now see BankOfWrox in the list of data connections (see Figure D-3)
figure d-1
Set up the database If you open the new node, you will see several other nodes, including Tables click this and then select Add New Table from the menu Another dialog should appear (see Figure D-4) Create two columns, Name and Amount, as shown Make sure that Name is set up to be the primary key When you click Close, you’ll be asked whether you want to save the changes to Table1 Select Yes, and the Choose Name dialog will appear (see Figure D-5)
Right-Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 36Use the name Accounts for the table You should now see a child node
called Accounts below Tables in the tree That completes the creation
of BankOfWrox Repeat the process for the BankOfMe database
The structure is exactly the same (although it doesn’t need to be for the purposes of this example) Don’t forget to set Name as the primary key You could have created these two as separate rows in the same database, but it doesn’t really simulate the scenario for which Enterprise Services is intended (inter-application communication)
Populating Your Databases
The next thing to do is populate the databases If you right-click over Accounts for either database and select Show Table Data from Table from the menu, you will see a grid that enables you to add
rows and initialize the values of their columns (see Figure D-6)
Enter two accounts in BankOfWrox—Professional Visual Basic 2010 and Professional XML—and allocate $5,000 to each Now repeat the process for BankOfMe, setting up one account, Me, with $0 in it
The Business logic
The next step is to create the transactional component to support the business logic Create a new Class Library project called Transactions Then, add a reference to
System.EnterpriseServices (see Figure D-7)
Trang 371190 ❘aPPendix d ENtERPRisE sERViCEs
This reference is needed because in order to come under the control of the Enterprise Services runtime, the component must inherit from the System.EnterpriseServices.ServicedComponent class:
Imports System.EnterpriseServices Imports System.Configuration Imports System.Data.SqlClient
<Assembly: ApplicationName("WroxTransactions")>
<Assembly: ApplicationAccessControl(True)>
Public Class BankTransactions Inherits ServicedComponent
Code snippet from Transactions
Here’s the main function in the component, TransferMoney:
Public Sub TransferMoney(ByVal amount As Decimal, _ ByVal sourceBank As String, _
ByVal sourceAccount As String, _ ByVal destinationBank As String, _ ByVal destinationAccount As String) Try
Withdraw(sourceBank, sourceAccount, amount) Try
Deposit(destinationBank, destinationAccount, amount) Catch ex As Exception
'deposit failed Throw New _ ApplicationException("Error transfering money, deposit failed.", _ ex)
End Try 'both operations succeeded ContextUtil.SetComplete() Catch ex As Exception 'withdraw failed Throw New _ ApplicationException("Error transfering money, withdrawal failed.", _ ex)
End Try End Sub
Code snippet from Transactions
Ignoring for the moment the references to ContextUtil, you have effectively divided the logic into two halves: the half that takes money from the Wrox account (represented by the private function Withdraw), and the half that adds it to your account (represented by the private function Deposit) In order for the function to complete successfully, each of the two halves must complete successfully
The ContextUtil class represents the context of the transaction Within that context are basically two bits
that control the behavior of the transaction from the point of view of each participant: the consistent bit and the done bit The done bit determines whether or not the transaction is finished, so that resources can be
reused The consistent bit determines whether or not the transaction was successful from the point of view
of the participant This is established during the first phase of the two-phase commit process In complex distributed transactions involving more than one participant, the overall consistency and completeness are voted on, such that a transaction is only consistent or done when everyone agrees that it is If a transaction completes in an inconsistent state, then it is not allowed to proceed to the second phase of the commit
In this case, there is only a single participant, but the principle remains the same You can determine the overall outcome by setting these two bits, which is done via SetComplete and SetAbort, which are static methods in the ContextUtil class Both of these set the done bit to True SetComplete also sets the consistent bit to True, whereas SetAbort sets the consistent bit to False In this example, SetComplete is set only if both halves of the transaction are successful
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 38The First Half of the Transaction
Now it’s time to see what’s going on in the two halves of the transaction itself The component is responsible for reading from and writing to the two databases, so it needs two connection strings You could hard-code these into the component, but a better solution is to use the project settings feature to include them Double-click My Project in the Solution Explorer and navigate to the Settings tab Add the two connection strings using the names BankOfWrox and BankOfMe, as shown in Figure D-8
figure d-8
1. Here’s the function that removes money from the Wrox account:
Private Sub Withdraw(ByVal bank As String, _ ByVal account As String, _
ByVal amount As Decimal)
Code snippet from Transactions
2. Establish a connection to the database and retrieve the current account balance from it:
Dim ConnectionString As String Dim SQL As String
Dim conn As SqlConnection = Nothing Dim cmdCurrent As SqlCommand Dim currentValue As Decimal Dim cmdUpdate As SqlCommand ConnectionString = My.Settings.Item(bank).ToString SQL = String.Format("SELECT Amount FROM Accounts WHERE Name = '{0}'", _ account)
Code snippet from Transactions
3. The call to ExecuteScalar retrieves a single value from the database—in this case, the amount for the requested account Note that there is an exception handler started with the Try keyword You’ll finish the Try block in a moment:
Try conn = New SqlConnection(ConnectionString) conn.Open()
cmdCurrent = New SqlCommand(SQL, conn) currentValue = CDec(cmdCurrent.ExecuteScalar())
Code snippet from Transactions
Transactional Components ❘ 1191
Trang 391192 ❘ aPPendix d ENtERPRisE sERViCEs
4. Note the current balance and determine whether you can afford to transfer the amount asked for If not, raise an exception:
'check for overdrafts
If amount > currentValue Then Throw New ArgumentException("Attempt to overdraft account") End If
Code snippet from Transactions
5. Otherwise, subtract the amount and update the table accordingly:
'otherwise, we're good to withdraw SQL = _
String.Format("UPDATE Accounts SET Amount = {0} WHERE Name = '{1}'", _ currentValue - amount, account)
cmdUpdate = New SqlCommand(SQL, conn) cmdUpdate.ExecuteNonQuery()
Code snippet from Transactions
6. Close the exception handler and the database:
Catch ex As Exception Throw New DataException("Error withdrawing", ex) Finally
If Not conn Is Nothing Then conn.Close()
End If End Try End Sub
Code snippet from Transactions
The Second Half of the Transaction
The second half of the transaction is similar, except that the failure conditions are slightly different First, the code stipulates that you can’t transfer less than $50 Second, a bug has been included such that
an attempt to transfer a negative amount will cause a divide by zero (You’ll see why this was added in a moment.) Here’s the code:
Private Sub Deposit(ByVal bank As String, _ ByVal account As String, _
ByVal amount As Decimal) Dim ConnectionString As String Dim SQL As String
Dim conn As SqlConnection = Nothing Dim cmdCurrent As SqlCommand Dim currentValue As Decimal Dim cmdUpdate As SqlCommand ConnectionString = My.Settings.Item(bank).ToString SQL = String.Format("SELECT Amount FROM Accounts WHERE Name = '{0}'", _ account)
If amount < 0 Then amount = amount / 0 ElseIf amount < 50 Then Throw New ArgumentException("Value of deposit must be greater than $50") Else
Try conn = New SqlConnection(ConnectionString) conn.Open()
'get the current value cmdCurrent = New SqlCommand(SQL, conn) currentValue = CDec(cmdCurrent.ExecuteScalar())
Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com
Trang 40SQL = _ String.Format("UPDATE Accounts SET Amount = {0} WHERE Name = '{1}'", _ currentValue + amount, account)
cmdUpdate = New SqlCommand(SQL, conn) cmdUpdate.ExecuteNonQuery()
Finally
If Not conn Is Nothing Then conn.Close()
End If End Try End If End Sub
Code snippet from Transactions
The business logic component is complete Let ’ s see how you can bring it under the control of Enterprise Services First, of course, you need to build your DLL Select Build Transactions from the Build menu Why was the divide by zero error included? This gives you a chance to see what happens to the transaction when an exception occurs in your code The transaction will automatically fail and roll back, which means that your data will still be in a good state at the end
registering Your Component
Because the Enterprise Services infrastructure is COM - oriented, you need to expose the NET component as
a COM component, and register it with Component Services Component Services handles all transaction coordination; that is, Component Services tracks any changes
and restores the data should the transaction fail First, some changes to the component are needed to enable this COM interaction Prepare to take a trip down memory lane
All COM components must have a GUID (globally unique identifi er) that uniquely identifi es it to the COM infrastructure This was done for you in Visual Basic 6.0, but NET requires you to add a value In addition, your component needs an attribute to make it visible to COM You can set both of these in the Assembly Information dialog
Double - click My Project in the Solution Explorer On the Application page, click Assembly Information There should already be a GUID assigned to your component Check the option Make Assembly COM - Visible, as shown in Figure D - 9
This makes all of the Public types accessible to COM
You should also update the Assembly Version fi elds as you make changes to the component figure d - 9
Chapter 28 contains more information about strong names and assemblies
The problem is that the assembly is a private assembly In order to make it available to the transaction
framework, it needs to be a shared assembly To do this, give the assembly a cryptographically strong name , generally referred to as its strong name
Cryptographically strong means that the name has been signed with the private key of a dual key pair This isn ’ t the place to go into a long discussion about dual - key cryptography, but essentially a pair of keys
is generated, one public and one private If something is encrypted using the private key, it can only be decrypted using the public key from that pair, and vice versa It is therefore an excellent tool for preventing
Transactional Components ❘ 1193