- An Overview of .NET
- Integrating .NET and BizTalk Server Orchestration Schedules
- Creating a Managed AIC Component
- Summary
Integrating .NET and BizTalk Server Orchestration Schedules
To illustrate how simple it is to execute a .NET managed object from a BizTalk Orchestration schedule, we are going to create a simple application that debits one bank account and credits a second. To guarantee the integrity of the application's data, we are going to add transactional support to both the BizTalk Orchestration schedule and the managed methods that update the Bank databases. The methods that require read-only access to a database will be marked as non-transactional. To execute the transfer transaction that is going to call the managed objects, we are going to construct a BizTalk Orchestration schedule that will serve as the bank transaction processing application. A completed BankTransaction.skv schedule is displayed in Figure 14.4. The first action in the schedule checks the balance of the account that is going to be debited to determine whether the account has sufficient funds to accommodate the transfer transaction. If the account has sufficient funds, the schedule will continue processing and call both the Debit and Credit methods in a managed Visual Basic .NET object. If the Debit or Credit transaction fails, the BizTalk Orchestration schedule will execute a compensating action notifying us, using a message box, of the failure. If the initial account does not have sufficient funds to accommodate the transaction, the schedule will again display a message box notifying us.
Note
Figure 14.4 shows the completed schedule, named BankTransaction.skv located in the Schedules directory under the BTSDotNet source code directory for this chapter. The complete source code for this chapter can be downloaded from the publisher's Web site.
The account data is maintained in two simple SQL Server 2000 databases named Fidelity and BankofAmerica. The following sections describe the steps required to set up the development environment, develop the .NET managed classes, register them, and then construct the BizTalk Orchestration schedule that will process an XML bank transaction.
Figure 14.4 The completed bank BizTalk Orchestration schedule.
Setting Up the Development Environment
The development environment for this example requires an MSMQ private queue named ReceiveRequest and two SQL Server databases named BankofAmerica and Fidelity. The MSMQ private queue can be created by selecting the Start menu's Administrative Tools, Computer Management option. In the management console that appears, navigate to the Services and Applications, Messaging, Private Queues folder in the tree view. Right-click on Private Queues and select New. In the dialog that appears, enter the queue name as ReceiveRequest. Be aware that MSMQ queue names are case-sensitive and that the queue must be transactional.
Note
The source code and required setup files to create the MSMQ and databases can be found on the publisher's Web site in the Setup directory for this chapter.
The two database tables can be created by downloading the CreateDBs.sql script located on the publisher's Web site in the Setup directory for this chapter or manually using the SQL Server Enterprise Manager. The two database names are Fidelity and BankofAmerica. Each contains a single database table named Accounts with two fields, AccountNo and Amount, both integers. Each table should have a single record with the account number of 1 and an amount greater then 0. The SQL script will automatically create the databases and insert a single record in each.
Creating a Simple .NET Component
In this section we are going to create two managed Visual Basic .NET components. The classes we create are going to query, debit, and credit the account tables in the Fidelity and BankofAmerica SQL Server databases. Later on in this chapter, we are going to call the methods in these classes from a BizTalk Orchestration schedule. The following sections walk you through the steps required to build a managed component that can be selected in the BizTalk Orchestration Implementation Wizard. These steps are as follows:
Create the Visual Basic .NET project and class file.
Add the project references to the .NET and COM external objects.
Create the QueryBankVB class.
Create the UpdateBankVB class.
Set the Serviced Component and Interop attributes on the classes.
Compile and register the class(es), creating the appropriate COM type libraries and COM+ applications.
Note
There are several alternate ways to accomplish each of the preceding steps. In this chapter we will only explore a few of them. If you would like to learn more about building and deploying managed objects and CCWs, refer to the appropriate Microsoft .NET Framework documentation.
Note
The source code for the completed Bank transaction example can be downloaded from the publisher's Web site.
After we build the BankVB.vb managed object, we are going to create an ASP.NET application to test the methods in the classes.
Before we begin to create the managed Visual Basic .NET components, we should first discuss how to access the data in the SQL Server databases created in the setup for the Bank transaction example. The next section reviews the data access options available when developing .NET applications.
Understanding How to Access SQL Server Data
Most applications require some form of data access, and this chapter's bank transaction example is no exception. Before we begin to develop the .NET classes, we should discuss how we are going to access the data in the SQL Server databases created. If you are creating a .NET application, you generally have three data access choices: ADO.NET, ADO, and OLE DB. Typically, the newer data access technologies tend to reduce development time, simplify code, and provide much better performance. In an effort to direct the focus of this chapter toward BizTalk and .NET/COM interoperability issues, we decided to implement data access via ADO using a RCW.
The data for the Bank transaction example resides in two identical SQL Server 2000 databases, named Fidelity and BankofAmerica. Each database consists of one table named account with two fields, AccountNo and Amount. Figure 14.5 displays the account table structure.
Figure 14.5 The bank databases account table.
Each table contains a single record with an account number of 1 and an amount of $500.
In the next section, we begin to develop our .NET managed classes.
Creating the Visual Basic .NET BankVB Project
To create the BankVB project and managed classes, open Visual Studio .NET and create a new Visual Basic .NET class library project named BankVB. Visual Studio .NET automatically creates a new class for you named Class1.vb. Select the Class1.vb file in the Solution Explorer and right-click. In the menu that appears, select the rename menu option and name the class BankVB.vb. Then select the BankVB solution in the Solution Explorer, right-click, and rename the solution BTSDotNet. Figure 14.6 shows the initial Visual Studio .NET project.
Figure 14.6 The BTSDotNet Visual Studio .NET solution.
The BankVb.vb file will contain two classes, QueryBankVB and UpdateBankVB, and a single Namespace, BankVB.
Note
Visual Basic .NET, unlike Visual Basic 6.0, allows you to have multiple classes in a single code file. In this example, we created a .VB file that contains two classes: one that's transactional, the UpdateBankVB class, and one that is not, the QueryBankVB class.
If you do not specify a namespace, Visual Basic .NET assumes the project name as its namespace. In this example, the namespace is going to be explicitly stated as BankVB. Listing 14.1 displays a code snippet that shows the structure of the methods in each class that we are going to create without their associated implementations.
Listing 14.1 The BankVB Namespace
Namespace BankVB Public Class UpdateBankVB Public Function Credit(ByVal AccountNo As Integer, ByVal _AmountToCredit As Integer, ByVal myBank As String) As Boolean End Function Public Function Debit(ByVal AccountNo As Integer, ByVal _AmountToDebit As Integer, ByVal myBank As String) As Boolean End Function End Class Public Class QueryBankVB Public Function DisplayBalances(ByVal Bank1Account As _Integer, ByVal Bank1 As String, ByVal Bank2Account As Integer, _ByVal Bank2 As String) As String End Function Public Function DisplayMsg(ByVal sMessage As String) End Function Private Function GetBalance(ByVal AccountNo As Integer, _ByVal myBank As String) As String End Function End Class End Namespace
Before we add the source code for the methods in both classes, we need to first set some configuration items on the project. The next section discusses how to accomplish this.
Setting Project References and Importing Namespaces
To call an external object, you must reference it explicitly in the Visual Studio .NET Explorer. In the BankVB project we reference three .NET namespaces and one unmanaged COM DLL to access the databases using ADO. To reference a namespace, you must add the namespace to the reference folder in the Visual Studio .NET Solution Explorer. To reference an unmanaged object, you must also add a reference, but to the COM object. When you add the reference to the COM object, the designer automatically creates an RCW for the unmanaged object and includes the assembly for that unmanaged object in the bin directory of the solution.
To add a reference to the BankVB solution, right-click on the references item in the Solution Explorer and select the Add Reference menu item. In the dialog that appears, select the .NET tab. The tab should display a list of .NET objects as shown in Figure 14.7.
The only namespace that needs to be exclusively added to the reference folder in the BankVB project is the System.EnterpriseServices namespace. The System namespace is referenced, by default, in Visual Studio .NET.
Figure 14.7 Adding namespace references.
When calling objects in a referenced namespace, you can choose to either fully qualify each reference with the full namespace dot notation or simply add an imports statement prior to the namespace declaration in the managed code. In the BankVB example, we use the Imports directive. Three Imports statements need to be added to the top of the BankVB.vb file as displayed in the following code:
Imports System Imports System.Runtime.InteropServices Imports System.EnterpriseServices
The System namespace is the root namespace for all fundamental types in the .NET Framework. The System.Runtime.InteropServices namespace is a sub-namespace of the System namespace that allows you to reference the COMVisible attribute. Without the COMVisible attribute, which will be added later, you would be unable to view the class methods in the BizTalk Orchestration COM Implementation Wizard. The System. EnterpriseServices namespace supplies the managed code with access to the COM+ API.
As stated earlier, we reference the SQL Server databases using ADO. To add the reference to the ADO COM object, again select the References item in the Solution Explorer and right-click. In the pop-up menu that appears, select the Add New Reference menu item. In the dialog that appears, select the COM tab. To reference the ADO.dll object, select the Microsoft ActiveX Data Objects 2.6 Library, as shown in Figure 14.8.
When you select OK, a message box appears asking whether you want to create a Primary Interop Assembly for this type library and have a wrapper automatically generated for you, as shown in Figure 14.9. Select Yes in this dialog.
Figure 14.8 Adding a reference to ADO.
Figure 14.9 Creating the Primary Interop Assembly and wrapper.
Behind the scenes, Visual Studio .NET automatically creates the .NET Interop assembly for you and places it in the appropriate solution directory. The ADO Interop assembly object is copied to the bin directory of the BankVB project and is named Interop.ADODB_2_6_.dll. To view this file in your Solution Explorer, select the Show All Files button at the top of the Solution Explorer.
Note
You could have also created the Interop assembly and wrapper object manually by running the command-line utility tlbimp.exe.
We are now ready to add the source code for both the QueryBankVB and UpdateBankVB Visual Basic .NET classes. The following sections describe how to accomplish this.
Creating the QueryBankVB Class
The QueryBankVB class contains three methods: DisplayBalances, DisplayMsg, and GetBalance. The GetBalance method is the only method that requires access to the SQL Server databases and does nothing more than query the account table for the account balance. The DisplayBalances and DisplayMsg methods accept and supply arguments to a Windows message box. To create the QueryBankVB class in the BankVB.vb file, add the code displayed in Listings 14.2 and 14.3 to the BankVB.vb file.
Listing 14.2 The GetBalance Method
Public Function GetBalance(ByVal AccountNo As Integer, _ByVal myBank As String) As String Dim myConn As New ADODB.Connection() Dim myCommand As New ADODB.Command() Dim myConnectionString As String Try myConnectionString = "Provider=SQLOLEDB.1; _Persist Security Info=False;User ID=sa;Initial Catalog=" & myBank & _ ";Data Source=SUSIEA" myConn.Open(myConnectionString) myCommand.ActiveConnection = myConn myCommand.CommandType = ADODB.CommandTypeEnum.adCmdStoredProc myCommand.CommandText = "AccountBalance" myCommand.Parameters.Refresh() myCommand.Parameters("@AccountNo").Value = AccountNo myCommand.Execute() GetBalance = myCommand.Parameters("@AccountBalance").Value Catch ex As Exception MsgBox("Error: " & ex.ToString()) Finally myCommand = Nothing myConn.Close() myConn = Nothing End Try End Function
The GetBalance method accepts two parameters, myBank and AccountNo, and returns the account balance. Each database in our example contains a stored procedure named AccountBalance that accepts one parameter, AccountNo, and returns a single parameter named AccountBalance. As you can see, the reference to the SQL Server database uses the familiar ADO object model. Also notice that we implement the new .NET error syntax called Try, Catch, Finally.
Listing 14.3 The DisplayBalances and DisplayMsg Methods
Public Function DisplayBalances(ByVal Bank1Account As Integer, _ByVal Bank1 As String, ByVal Bank2Account As Integer, _ByVal Bank2 As String) As String Dim iBank1 As Integer Dim iBank2 As Integer iBank1 = GetBalance(1, Bank1) iBank2 = GetBalance(1, Bank2) DisplayBalances = Bank1 & ": " & CStr(iBank1) & _" " & Bank2 & ": " & CStr(iBank2) DisplayMsg(DisplayBalances) End Function Public Function DisplayMsg(ByVal sMessage As String) MsgBox(sMessage) End Function
The DisplayBalances method accepts both the account number and bank name as arguments. It calls the GetBalance method displayed in Listing 14.2 to retrieve the balance of each account and then display the balance of each. The DisplayMsg method simply accepts a single string and supplies it as a parameter to the MsgBox function.
These three methods comprise the public class QueryBankVB that resides in the BankVB namespace. Due to their nontransactional read-only characteristics, the methods in this class do not require the COM+ services supplied by the System.EnterpriseServices namespace.
Creating the UpdateBankVB Class
In the Bank transfer transaction example, the BankVB namespace contains a second class named UpdateBankVB. The UpdateBankVB class contains two methods, Debit and Credit, that accept three parameters, AccountNo, Amount, and Bank. To create the UpdateBankVB class, open the BankVB.vb file in Visual Studio .NET and add the code displayed in Listings 14.4 and 14.5 to the end of the file.
Listing 14.4 The Credit Method
Public Function Credit(ByVal AccountNo As Integer, _ByVal AmountToCredit As Integer, ByVal myBank As String) As Boolean Dim myConn As New ADODB.Connection() Dim myCommand As New ADODB.Command() Dim myConnectionString As String myConnectionString = "Provider=SQLOLEDB.1; _Persist Security Info=False;User ID=sa;Initial Catalog=" & _myBank & ";Data Source=Susiea" Try myConn.Open(myConnectionString) myCommand.ActiveConnection = myConn myCommand.CommandType = ADODB.CommandTypeEnum.adCmdStoredProc myCommand.CommandText = "Credit" myCommand.Parameters.Refresh() myCommand.Parameters("@AccountNo").Value = AccountNo myCommand.Parameters("@AmountToCredit").Value = AmountToCredit myCommand.Execute() Credit = CBool(myCommand.Parameters("@AccountBalance").Value) Credit = True Catch ex As Exception MsgBox("Error: " & ex.ToString()) Credit = False Finally myCommand = Nothing myConn.Close() myConn = Nothing End Try End Function
Each of the Bank SQL Server databases contain two stored procedures named Credit and Debit that each accept two parameters, AccountNo and AmountToCredit or AmountToDebit, respectively. If the debit or credit is successful, the methods return a boolean value of true; if they are not successful, they return false. The code in each of these methods creates a connection to the database and then executes either the Credit or Debit stored procedure using the ADO object model. The Debit method is displayed in Listing 14.5.
Listing 14.5 The Debit Method
Public Function Debit(ByVal AccountNo As Integer, _ByVal AmountToDebit As Integer, ByVal myBank As String) As Boolean Dim myConn As New ADODB.Connection() Dim myCommand As New ADODB.Command() Dim myConnectionString As String myConnectionString = "Provider=SQLOLEDB.1; _Persist Security Info=False;User ID=sa;Initial Catalog=" _& myBank & ";Data Source=susiea" Try myConn.Open(myConnectionString) myCommand.ActiveConnection = myConn myCommand.CommandType = ADODB.CommandTypeEnum.adCmdStoredProc myCommand.CommandText = "Debit" myCommand.Parameters.Refresh() myCommand.Parameters("@AccountNo").Value = AccountNo myCommand.Parameters("@AmountToDebit").Value = AmountToDebit myCommand.Execute() Debit = CBool(myCommand.Parameters("@AccountBalance").Value) Debit = True Catch ex As Exception MsgBox("Error: " & ex.ToString()) Debit = False Finally myCommand = Nothing myConn.Close() myConn = Nothing End Try End Function
So, what happens if the credit method succeeds and the debit method fails within a single transfer transaction? If we leave the code as is, the credited account would be making money at the expense of the banking institution. To ensure that this doesn't occur, we need to do a few things. First we need to either write custom compensation logic to reverse the updates manually or we need to apply automatic transaction services using the COM+ services API provided in the System.EnterpriseServices namespace. COM+ automatic transactions guarantee that each method is capable of rolling back an update. To guarantee that both methods either succeed or fail, we must wrap the calls to both inside a transaction. In this case, we are going to use a BizTalk Orchestration schedule to call the methods, so when we build the schedule, we will need to add a transaction shape to wrap the method calls. In the BankVB.vb example, we depend on both the System.EnterpriseServices namespace and BizTalk Orchestration to automatically handle transaction management.
To incorporate COM+ services into our BankVB example, we need to add configuration attributes to the classes and project we just created. The next section describes how to accomplish this.
Setting Serviced Component and Interop Attributes
To implement a class that incorporates COM+ services, the class must derive from the ServicedComponent class. In the BankVB.vb example, the UpdateBankVB class inherits the ServicedComponent class using the following syntax:
Public Class UpdateBankVB : Inherits ServicedComponent
As discussed earlier, COM+ properties are specified as declarative attributes in the .NET world. In the BankVB.vb example, we are going to apply three attributes: TransactionAttribute, AutoComplete, and ApplicationName.
The TransactionAttribute provides the equivalent of the COM+ Explorer transactional support traditionally configured using the COM+ Microsoft Management Console (MMC). In the BankVB.vb example, we set the value of the attribute to Required. The Transaction attribute is set once per managed class.
The AutoComplete attribute instructs the runtime to automatically call the SetAbort function if the transaction encounters an exception and must be set on each method in the class. If an exception is not thrown, the SetComplete function is automatically called by the runtime.
The ApplicationName attribute is equivalent to the COM+ application name traditionally created in the COM+ Explorer and is specified once per namespace. To add these attributes, open the BankVB.vb file in Visual Studio .NET and add them as displayed in Listing 14.6. The AutoComplete and Transaction attributes should also be added to the QueryBankVB class.
Listing 14.6 COM+ Serviced Component Declarative Attributes
Imports System Imports System.Runtime.InteropServices Imports System.EnterpriseServices ' - Registration details - ' Supply the COM+ application name. <Assembly: ApplicationName("BankComponent")> Namespace BankVB <Transaction(TransactionOption.Required)> _ Public Class UpdateBankVB : Inherits ServicedComponent <AutoComplete()> _ Public Function Credit(ByVal AccountNo As Integer, _ByVal AmountToCredit As Integer, ByVal myBank As String) As Boolean Dim myConn As New ADODB.Connection() Dim myCommand As New ADODB.Command() ...
Two additional attributes, COMClass and COMVisible, must be set to properly expose the managed class to an unmanaged COM object. COMClass accepts a GUID that uniquely identifies the COM type library generated for COM interoperability, and the COMVisible attribute ensures that the public managed methods are visible in the BizTalk GUI designers. Each managed class must be marked with a unique COMClass attribute and GUID, and the assembly must be marked as COMVisible. You can create the GUID by using the Visual Studio .NET tools, CreateGUID menu option. In the dialog that appears, select the Registry Format option and click Copy to copy it to the Clipboard as displayed in Figure 14.10. To add the attributes to the BankVB.vb file, open the file in Visual Studio .NET and add them as displayed in Listing 14.7.
Figure 14.10 Generating a unique GUID.
Listing 14.7 Additional COM Interop Attributes
Imports System Imports System.Runtime.InteropServices Imports System.EnterpriseServices ' - Registration details - ' Supply the COM+ application name. <Assembly: ApplicationName("BankComponent")> <Assembly: ComVisible(True)> Namespace BankVB <Transaction(TransactionOption.Required), _ ComClass("73297D71-DA59-43b6-9511-9743B379BF02")> _ Public Class UpdateBankVB : Inherits ServicedComponent ' Define a local variable to store the DSN value. <AutoComplete()> _ Public Function Credit(ByVal AccountNo As Integer, _ByVal AmountToCredit As Integer, ByVal myBank As String) As Boolean Dim myConn As New ADODB.Connection() Dim myCommand As New ADODB.Command()
Now that we have added the COMClass attributes to both classes and the COMVisible attribute to the BankVB.vb file, we need to compile and register the BankVB project. Before we can compile it, however, we need to set a few properties using the Properties dialog of the BankVB project in Visual Studio .NET. The following sections walk through each of the steps required to compile and register the BankVB.vb object in COM+.
Compiling and Registering the Component
In the Bank transaction example, we will call the managed Visual Basic .NET object from a COM client represented as a BizTalk Orchestration schedule. The BizTalk Orchestration schedule will instantiate the components from inside the context of a transaction. To call the managed object from a COM client that requires COM+ transaction services, you must first compile it and create a COM type library referencing the managed assembly. Because the managed object is also a serviced component that supports transactions, you also need to sign the managed object with a strong name key file prior to compiling it. Finally, we will need to register the assembly with COM+. To accomplish this, we need to add several additional attributes to the BankVB.vb file we have created, set some additional project properties in the BankVB Visual Studio .NET project, and execute a few command-line utilities. The following sections walk through each of the steps required to accomplish these tasks.
Creating a COM Type Library
To enable a COM client to call a managed component, the managed component must create and register a COM type library reflecting the metadata contained inside its assembly. There are several different ways to create and register a type library for a managed component. In previous sections of this chapter, we discussed the tlbexp.exe and regasm.exe command-line utilities that generate type libraries, the latter of which also automatically registers it as a COM object. In the BankVB.vb example, we will register the managed component using the configuration settings available in Visual Studio .NET.
To mark a managed object as COM-interoperable using Visual Studio .NET, select the BankVB.vb file in the Solution Explorer and right click. In the menu that appears, select the Properties option. Next, select the Configuration Properties folder's Build option and check the Register for COM Interop check box displayed in Figure 14.11. This instructs the Visual Studio designer to automatically generate and register a COM type library for your managed assembly.
Next, we need to sign the assembly with a strong name. To accomplish this, we add an Assembly attribute to the code we created. The following section describes how to accomplish this.
Figure 14.11 Marking an assembly for COM interop in Visual Studio .NET.
Creating a Strong-Named Assembly
As stated earlier, a serviced component must be strong-named. A strong name consists of the assembly simple text name, version number, and culture information, if provided, plus a public key and a digital signature. It is generated from an assembly file using the corresponding private key.
Note
After an assembly is created, you cannot sign it with a strong name. To create a strong-named assembly, you must sign it with a strong name when you create it.
There are two different ways to sign an assembly with a strong name:
Using the AL.exe command-line Assembly Generation Tool
Using declarative assembly attributes to insert the strong name information in your code
In the BankVB.vb example, we rely on an Assembly attribute in the source code. To mark an assembly as requiring a strong name using an attribute, you need to create a .snk key file and then add a reference to that key file to the BankVb.vb code. You can create the key file using the strong name sn.exe command-line utility. To accomplish this, navigate to the directory containing the utility and then enter the following text:
Sn k BankVB.dll Bankvb.snk
This creates a key file named Bankvb.snk. After you create the file, add it to your BankVB solution; then select the BankVB.vb file in the Solution Explorer and open it. Next, add the following line of code prior to the namespace declaration in the BankVB.vb code.
<Assembly: AssemblyKeyFileAttribute("c:\btsdotnet\bankvb\Bankvb.snk")>
Note
The AssemblyKeyFileAttribute properties require a full pathname to the .snk key filename. The sn.exe command-line utility can be located in the \Program Files\Microsoft Visual Studio .Net\FrameworkSDK\bin directory.
We are now ready to build the BankVB.vb file. Select the BankVB project in the Solution Explorer and right-click. In the pop-up menu that appears, select the Build menu option. The assembly has now been signed with a strong name. Next, we need to register the assembly with COM+. The following section describes the steps required to accomplish this.
The next step is to compile your code. To compile BankVB.vb, select the Visual Studio .NET Build, Build menu option.
Registering a Managed Serviced Component with COM+
To register the managed component with COM+, you must run the regsvcs.exe command-line utility. The command-line utility accomplishes the following tasks:
Loads and registers an assembly
Generates, registers, and installs a type library into a specified COM+ 1.0 application
Configures services that you have added programmatically to your class
To run the regsvcs.exe command-line utility, open a command prompt and navigate to the local directory where the Bankvb.dll assembly is located, and then type the following:
Regsvcs.exe Bankvb.dll
Note
Your assembly must be strong-named to run this utility. Serviced components can also be registered automatically by the CLR at runtime in certain situations. In our case, we are restricted to manual registration. If you want to learn more about registering serviced components, refer to the appropriate .NET Framework documentation.
The regsvcs.exe command-line utility is located in the \WINDOWS\Microsoft. NET\Framework\v1.XXXX directory. When you run the utility, ignore the warning in the command window that states that "The class 'BankVB.UpdateBankVB' has no class interface, which means that late bound calls cannot take advantage of AutoComplete methods." This will have no effect on this example.
To verify that the managed object was added to the COM+ catalog, open the COM+ catalog by selecting the Start menu's Programs, Administrative Tools option and then select the Component Services option. If the component was registered successfully, you should see a COM+ application named BankComponent on the local system as shown in Figure 14.12.
Figure 14.12 The BankComponent COM+ application.
Now that the component has been registered, we can test its methods from a client application. In the following section, we will create a simple ASP.NET Web form to test the QueryBankVB and UpdateBankVB classes just created.
Creating a .NET Test Client
To test the QueryBankVB and UpdateBankVB classes, we will create a new ASP.NET project named BankVBWebClient and add it to the BTSDotNet Visual Studio .NET solution. To create the new project, open the BTSDotNet solution, select the solution name in the Solution Explorer, and right-click. In the pop-up menu that appears, select Add, New Project from the menu. In the dialog that appears, select Visual Basic Projects ASP.NET Web Application and name the application BankVBWebClient off the root directory of your local Web server.
The application will consist of a single ASP.NET (.aspx) page named BankVBClient.aspx. When we created the new ASP.NET application, Visual Studio .NET automatically created a new Web form named WebForm1.aspx. To rename the form, select it in the Solution Explorer and right-click. In the pop-up menu that appears, select the rename menu option and name it BankVBClient.aspx.
Next, we need to add a reference to the managed BankVB object in the Visual Studio .NET Explorer References folder.
To add the reference to BankVB, select the References folder under the BankVBWebClient folder in the Solution Explorer and right-click. In the pop-up menu that appears, select the Add Reference menu item. In the dialog that appears, select the COM tab and navigate to and select the BankVB object as shown in Figure 14.13. Remember that the BankVB project was compiled with COM interoperability turned on, which now requires the BankVBWebClient project to reference the managed code using COM interoperability.
Figure 14.13 Adding the BankVB COM reference.
The BankVBWebClient Web form has several controls on it that allow you to enter the bank account names, numbers, and amount to transfer. The two buttons on the form take the data entered and execute the GetBalance, Credit, and Debit methods. Figure 14.14 shows the BankVBWebClient.aspx Web form.
Listing 14.8 displays the code behind the Get Balance VB Button.
Figure 14.14 The BankVBWebClient Web form.
Listing 14.8 The Balance Button Code
Private Sub btnBalance_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles btnBalance.Click Dim oBank As New BankVB.QueryBankVB() Dim iReturn As Integer iReturn = oBank.GetBalance(txtBalanceAccountNo.Text, _rbBalanceBank.SelectedItem.Value) Response.Write("Return Value: " & iReturn) End Sub
Listing 14.9 shows the code behind the Transfer Money VB button.
Listing 14.9 The Transfer Money Button Code
Private Sub btnTransfer_Click(ByVal sender As System.Object, _ByVal e As System.EventArgs) Handles btnTransfer.Click Dim oBank As New BankVB.BankVB.UpdateBankVB() Dim bReturn As Boolean bReturn = oBank.Credit(txtCreditAccount.Text, _txtTransferAmount.Text, rbCreditBank.SelectedItem.Value) Response.Write("Credit : " & bReturn) bReturn = oBank.Debit(txtDebitAccount.Text, _txtTransferAmount.Text, rbDebitBank.SelectedItem.Value) Response.Write("Debit : " & bReturn) End Sub
To modify the BankVBClient.aspx page to call the BankVB methods, add the Web Forms controls listed in Table 14.1 to the new BankVBClient.aspx page by dragging them from the Web Forms toolbox in the Visual Studio .NET Designer onto the Design view of the page.
Table 14.1 Web Forms Controls for BankVBClient.aspx
Control Type |
Name |
Values |
Button |
BtnBalance |
|
Button |
btnTransfer |
|
Text Box |
txtBalanceAccountNo |
|
Radio Button |
rbBalanceBank |
BankofAmerica, Fidelity |
Radio Button |
rbCreditBank |
BankofAmerica, Fidelity |
Radio Button |
rbDebitBank |
BankofAmerica, Fidelity |
Text Box |
txtDebitAccount |
|
Text Box |
txtCreditAccount |
|
Labels(7) |
n/a |
Account No:, Bank:, Bank to Credit:, Bank to Debit:, Amount:, Account No:, Account No: |
Next, add the two event routines displayed in Listings 14.8 and 14.9 to the application by double-clicking on each of the Web Form buttons in the Design page. Finally, compile and run the page by selecting the BankVBWebClient project in the Solution Explorer and right-clicking. In the pop-up menu that appears, select the Build menu option. On completion of the build, select the BankVBClient.aspx page in the Solution Explorer and right-click. In the pop-up menu that appears, select the View in Browser menu option. To execute the code, enter an Account No of 1 and amounts to transfer less than $500.
Creating the Bank Orchestration Schedule
Now that we've successfully created, registered, and tested the BankVB.vb managed code, we can begin to design and construct the BizTalk Orchestration schedule that will also call its methods. Earlier in the chapter, Figure 14.4 displayed a completed version of the BankTransaction.skv file.
Note
This section assumes that you are familiar with constructing and connecting actions in the left-hand side of BizTalk Orchestration Designer. The topics in this section focus on the implementation details specific to the managed objects.
But before we begin to build the schedule, we need to first construct a Transaction document. The Bank transaction schedule begins execution on the receipt of an XML file that contains the Bank names, accounts, and transfer amount. The XML file is dropped into a private MSMQ queue named ReceiveRequest. In the next section, we will discuss the structure of this document and how to create it.
The XML Bank Transaction Document
The bank schedule consumes a simple XML document that consists of the debit and credit bank names and their account numbers as well as the amount of money to transfer. Figure 14.15 shows an instance of the BankTransInstance.xml document.
Figure 14.15 The XML bank transaction XML file.
To reference this document from the BizTalk Orchestration Schedule, we need to create a BizTalk XDR schema that defines the structure of the document using the BizTalk Editor. There are several ways to create the document. The easiest way is to create an instance of the XML document and then import it into BizTalk Editor. To accomplish this, open Notepad, copy the text from Figure 14.16 to the file, and then save it. Then open the BizTalk Editor; select the Tools, Import menu option; and choose the Well Formed XML Instance option. When the dialog appears, select the file you just created. You can also download the code from the publisher's Web site and import the BankTransInstance.XML document. The BankTransaction.XML BizTalk schema is displayed in Figure 14.16.
Figure 14.16 The XML bank transaction BizTalk schema.
Note
The BankTransaction.skv schedule file located in the Schedules directory of the source code on the publisher's Web site requires this document to live in the \BTSDotNet\Docs directory.
The BizTalk XDR schema will be referenced in the BankTransaction.skv schedule. Now that we have created the BizTalk schema, we are ready to begin to construct the BankTransaction.skv schedule.
Creating a New Orchestration Schedule
In this section, we are going to create a new schedule named BankTransaction.skv. The BankTransaction.skv schedule displayed in Figure 14.4 begins with the receipt of an XML bank transaction file from an MSMQ private queue named ReceiveRequest. The first action in the schedule, ReceiveRequest, is bound to the MSMQ queue that waits for an XML message of type BankTransaction. After the receipt of the XML document, the schedule verifies whether there is enough money in the account to be debited by calling the GetBalance method in the QueryBankVB class. Then a decision shape evaluates the return value from the method to determine whether it should continue processing the bank transaction.
If the account balance is sufficient, the schedule then executes the Debit and Credit methods in the UpdateBankVB class. Otherwise, it executes the DisplayMsg method in the QueryBankVB class. If for some reason the Debit or Credit method fails, we add a transaction shape to execute an On Failure action named Error. The Error action calls the QueryBankVB DisplayMsg method notifying us that the transaction failed. The On Failure of BankTransaction Orchestration tab is displayed in Figure 14.17.
Figure 14.17 The compensating transaction.
The schedule contains seven actions bound to managed objects: Check Balance, Get Beginning Balances, Get Ending Balances, Sorry Not Enough Money J, Debit Account, Credit Account, and Error.
To create the BankTransaction.skv schedule, open the BizTalk Orchestration Designer and rename the schedule BankTransaction.skv by selecting the Save As menu option. Then add and connect the Actions, Decision, Fork, and End shapes to the left-hand side of the business process as displayed in Figure 14.4. Next, add a Message Queue implementation shape to the Designer for the .\private$\ReceiveRequest private queue and connect it to the Receive Request action. In the XML Communication Wizard dialog that appears when connecting to the Receive Request action, select the Receive option and click Next. In the XML Communication Wizard that appears, select the Rename Current Message radio button and name the message BankTransaction. In the XML Translation Information Wizard dialog that appears, select the Receive XML Message from the Queue radio button and click Next. In the Message Type Information dialog that appears, enter BankTransaction and click Next. In the Message Specification Information dialog, select the BankTransaction.xml BizTalk XDR schema you created and then click the Validate Message Against the Specification check box. Next, select the Add button and add the Amount, CreditBank, CreditAccount, DebitBank, and DebitAccount fields as displayed in Figure 14.18.
Figure 14.18 The XML Communication Wizard message specification.
Next, select the Decision shape in the Designer and right-click. In the pop-up menu that appears, select the Add Rule menu item. In the dialog that appears, select the Create a New Rule radio button and click OK. Then name the rule Balance OK! and enter the script expression as displayed in Figure 14.19. This expression will evaluate a return value that contains the bank account of the account that is going to be debited by the transaction amount. If the account balance minus the transaction amount is greater than or equal to zero, the schedule will continue processing the transaction.
Figure 14.19 The Balance OK Decision shape rule.
Note
The publisher's Web site contains a schedule named BankTransactionStart.skv that has been preconfigured with the initial steps to this point.
The next two sections discuss how to bind the remaining actions in the schedule to the appropriate methods in the managed object. We will also learn how to incorporate transaction management.
Referencing a .NET Managed Component
Referencing a managed object is as simple as referencing a standard unmanaged COM object. You simply drag the COM Component implementation object onto the designer and step through the wizard. Because we choose to register the managed BankVB object with COM, it appears just as if it were a regular COM object. Figure 14.20 shows the COM Component Binding Wizard Class Information page.
Figure 14.20 The COM Component Binding Wizard Class Information page.
Our schedule implements two instances of the managed BankVB object: one that is transactional, and one that is not. The nontransactional implementation exposes all methods in the QueryBankVB class, and the transaction implementation exposes all the methods in the UpdateBankVB class. When binding the object, you proceed through several wizard steps. The last step in the wizard, Advanced Port Properties, allows you to configure transaction settings for the object. The QueryBankVB class COM implementation object does not implement transaction support and does not enable a transaction to abort if an error is encountered.
To add the BankVB COM implementation objects to the schedule, drag the COM implementation shape onto the designer. In the COM Component Binding Wizard that appears, name the first port CheckBalance and click Next. In the Standard or Dynamic Communication dialog that appears, select the Static option and click Next. In the Class Information dialog that appears, select the BankVB QueryBankVB class and click Next. In the Interface Information dialog, select the _QueryBankVB interface and click Next. In the Method Information dialog that appears, select the Check All button and click Next. In the Advanced Port Properties dialog that appears, keep the default values and click Finish.
Now repeat the same steps to add the UpdateBankVB class, this time selecting the BankVB UpdateBankVB class in the Class Information dialog. In the Advanced Port properties dialog, set the properties as displayed in Figure 14.21.
Figure 14.21 The UpdateBankVB Implementation Advanced Port Properties page.
Note
The transaction attributes must be set on the UpdateBankVB implementation object to bind the object to actions contained in a BizTalk Orchestration transaction shape. You accomplish this by selecting the Required option in the Advanced Port Properties dialog for the COM object.
Next we need to bind each of the actions in the schedule to its appropriate method implementation in either the QueryBankVB object or the UpdateBankVB object. To begin, connect the Check Balance action to the QueryBankVB COM implementation shape. In the Method Communication Wizard that appears, select the Initiate a Synchronous Method Call option and click Next. In the Message Specification Information dialog that appears, select the GetBalance method and click Finish.
Next, connect the Get Beginning Balances action to the QueryBankVB implementation shape. In the Method Communication Wizard that appears, select the Initiate a Synchronous Method Call option and click Next. In the Message Specification Information dialog that appears, select the DisplayBalances method and click Finish. The next action to bind is the Sorry, Not Enough Money J action. Connect this action to the QueryBankVB implementation object. In the Method Communication Wizard that appears, select the Initiate a Synchronous Method Call option and click Next. In the Message Specification Information dialog that appears, select the DisplayMsg method and click Finish. Now, connect the Get Ending Balances action to the QueryBankVB implementation object. In the Method Communication Wizard that appears, select the Initiate a Synchronous Method Call option and click Next. In the Message Specification Information dialog that appears, select the DisplayBalances method and click Finish. Finally, select the On Failure of BankTransaction tab in the BizTalk Orchestration Designer and connect the Error action to the QueryBankVB implementation object. In the Method Communication Wizard that appears, select the Initiate a Synchronous Method Call option and click Next. In the Message Specification Information dialog that appears, select the DisplayMsg method and click Finish.
Now we're ready to connect the Debit and Credit actions to the UpdateBankVB implementation object. Connect one at a time to the UpdateBankVb object. In the Method Communication Wizard that appears, select the Initiate a Synchronous Method Call option and then click Next. In the Message Specification Information dialog that appears, select the Debit method for the Debit action and the Credit method for the Credit action and click Finish. Each action in the schedule is now bound to its appropriate implementation object.
In the next section we will add transactional support to our BizTalk Orchestration schedule for both the Credit and Debit actions.
Incorporating Transaction Management
To ensure the integrity of the database, we created managed objects that implement the System.EnterpriseServices namespace's ServicedComponent interface. The Credit and Debit methods in the UpdateBankVB class both inherit from the ServicedComponent interface and implement attributes to ensure automatic transaction support. If we were to call each of the methods separately, they would both throw exceptions on failure and roll back any transaction-aware logic. In our case, we want to ensure that both actions succeed or fail as an atomic unit. To accomplish this, we wrap both actions, Credit and Debit, in a BizTalk Orchestration transaction.
To wrap the actions, drag the Transaction shape from the flowchart actions pane over both the Credit and Debit actions. Then select the transaction shape and right-click. In the pop-up menu that appears, select the Properties menu option to set its attributes. The BankTransaction attributes should be configured as displayed in Figure 14.22.
Figure 14.22 The bank transaction attributes.
The name of the transaction is BankTransaction and has a type of Short Lived, DTC style. Its retry count is set to three, and its On Failure check box is checked. If either the Credit or Debit method fails, the schedule will now redirect schedule processing to the first action, Error, in the On Failure of BankTransaction tab as displayed in Figure 14.17.
The message flow that occurs in the BizTalk Orchestration schedule is configured using the Data tab of the BizTalk Orchestration Designer. In the next section, we will discuss how to construct the data flow for the BankTransaction.skv schedule.
The Data Page
At this point, we assume that you have a good working knowledge of the importance of the BizTalk Orchestration data page. This page ensures that the messages flow between actions inside the schedule. The message fields should be connected to one another as detailed in Table 14.2.
Table 14.2 Orchestration Data Page Message Bindings
From Message Name |
From Field Name |
To Message Name |
To Field Name |
BankTransaction |
CreditBank |
Credit_in |
myBank |
BankTransaction |
CreditAccount |
Credit_in |
AccountNo |
BankTransaction |
DebitBank |
Debit_in |
myBank |
BankTransaction |
DebitAccount |
Debit_in |
AccountNo |
BankTransaction |
Amount |
Credit_in |
AmountToCredit |
BankTransaction |
Amount |
Debit_in |
AmountToDebit |
BankTransaction |
DebitBank |
DisplayBalances_in_2 |
Bank1 |
BankTransaction |
DebitAccount |
DisplayBalances_in_2 |
Bank1Account |
BankTransaction |
CreditBank |
DisplayBalances_in_2 |
Bank2 |
BankTransaction |
CreditAccount |
DisplayBalances_in_2 |
Bank2Account |
BankTransaction |
DebitBank |
DisplayBalances_in |
Bank1 |
BankTransaction |
DebitAccount |
DisplayBalances_in |
Bank1Account |
BankTransaction |
CreditBank |
DisplayBalances_in |
Bank2 |
BankTransaction |
CreditAccount |
DisplayBalances_in |
Bank2Account |
Constants |
NotEnoughMoney |
DisplayMsg_in |
sMessage |
Constants |
TransactionFailed |
DisplayMsg_in_2 |
sMessage |
BankTransaction |
DebitBank |
GetBalance_in |
myBank |
BankTransaction |
DebitAccount |
GetBalance_in |
AccountNo |
Figure 14.23 displays the data page for the BankTransaction schedule.
Figure 14.23 The bank transaction data page.
Note
The data page maintains two constant values, NotEnoughMoney and TransactionFailed, that contain the message text passed to the message box managed methods.
Compiling and Running the Schedule
We are now ready to compile and then run the schedule. To compile the schedule, select the File, Make XLANG BankTransaction.skx File menu option from the Orchestration designer menu. A dialog appears asking you to save the XLANG file to the local directory. To run the schedule, you will need to accomplish two tasks: drop an instance of the BankTransaction.xml file onto the ReceiveRequest MSMQ queue and start an instance of the BankTransaction.skx schedule.
Note
When compiling the schedule, you may encounter a Just-In-Time Debugging Runtime Exception error. If this occurs, select the No button and continue compiling. This error has no effect on the XLANG schedule creation.
The source code for this chapter located on the publisher's Web site contains two Visual Basic script files located in the \BtsDotNet\Schedules directory. The first, processTrans.vbs, starts an instance of the BankTransaction schedule and calls the second script. The second, File2Queue.vbs, submits an instance of the BankTransaction.xml file, located in the same directory, to the private MSMQ ReceiveRequest queue. Listings 14.10 and 14.11 display the Visual Basic script files.
Listing 14.10 The processTrans.vbs File
dim objSked, strPath, WshShell strPath = WScript.ScriptFullName strPath = Mid(strPath, 1, InStrRev(strPath, "\")) Set WshShell = WScript.CreateObject("WScript.Shell") call WshShell.Run ("""" & strPath & "File2Queue.vbs" _& """ BankTransInstance.xml") set objSked = GetObject("sked:///" & strPath & "BankTransaction.skx")
Listing 14.11 The File2Queue.vbs File
'MSMQ constants 'Access modes const MQ_RECEIVE_ACCESS = 1 const MQ_SEND_ACCESS = 2 const MQ_PEEK_ACCESS = 32 'Sharing modes const MQ_DENY_NONE = 0 const MQ_DENY_RECEIVE_SHARE = 1 'Transaction Options const MQ_NO_TRANSACTION = 0 const MQ_MTS_TRANSACTION = 1 const MQ_XA_TRANSACTION = 2 const MQ_SINGLE_MESSAGE = 3 'Queue Path const QUEUE_PATH = ".\private$\ReceiveRequest" dim qinfo, qSend, mSend, xmlDoc dim sPath, sFile, sMesg, sInFileName 'Input file name 'sInFileName = WScript.Arguments(0) sInFileName = "BankTransInstance.xml" ' Create the queue set qinfo = CreateObject("MSMQ.MSMQQueueInfo") qinfo.PathName = QUEUE_PATH ' Open queue with SEND access. Set qSend = qinfo.Open(MQ_SEND_ACCESS, MQ_DENY_NONE) ' Put file into message. sPath = WScript.ScriptFullName sPath = Mid(sPath, 1, InStrRev(sPath, "\")) sFile = sPath & sInFileName set xmlDoc = CreateObject("MSXML.DOMDocument") xmlDoc.load sFile set mSend = CreateObject("MSMQ.MSMQMessage") mSend.Body = xmlDoc.xml mSend.Label = "BankTransaction" ' Send message to queue. mSend.Send qSend, MQ_SINGLE_MESSAGE msgbox "File """ & sInFileName & """ sent to queue """ & _QUEUE_PATH & """", vbOKOnly, "Basic BizTalk Example" ' Close queue. qSend.Close
If the schedule runs correctly, a message box will appear notifying you of the beginning account balances or of insufficient funds to continue processing. If funds are available and both the Debit and Credit methods complete successfully, a second message box will appear notifying you of the updated balances.
Debugging Tips and Tricks
When building a BizTalk Orchestration schedule that references a managed object, we thought it might be useful to share a few debugging tips and tricks. First, when recompiling a managed object in Visual Studio .NET with the Register for COM Interop configuration setting on, you may need to either select the Tools, Refresh Method Signatures menu option to rebind the managed objects or manually re-associate your actions to the object and methods using the BizTalk Orchestration Designer COM Component Implementation Wizard. Although the signature of the object has not changed, the designer at times requires one or both of these steps.
Second, after a schedule instance is instantiated and has referenced a managed object, that object is locked and cannot be recompiled until all instances of the object are released. To release the object, you can do one or both of the following:
-
Select the Tools, Shut Down All Running XLANG Schedule Instances menu option in the BizTalk Orchestration Designer.
-
Open the COM+ Microsoft Management Console and select the XLANG Scheduler application. To shut down all running instances, right-click the application and choose Shut Down.