Architecture The architecture of the migrated application appears as shown in Figure 13-12. The requests received by the Web server are processed by the ASP.NET Web application. The ASP.NET application uses all three components used by the original ASP application, (i.e., VBLoginCOM , VBAdminCOM , and VBBuySellCOM ). We have migrated VBLoginCOM to a .NET component, used the interoperability features for VBAdminCOM , and exposed VBBuySellCOM as a Web service using SOAP ToolKit. Minimal changes were made to the GUI and most of the client-side code was maintained as is. We will cover migration of the application, keeping in mind the actual flow of logic. Figure 13-12. Architecture of migrated ASP.NET application. A new Web application Stock_Trading_NET is created in .NET using Visual Studio .NET and all files in the ASP application are added to the new application. Before we begin with migration, we declare the two most commonly used variables across the application in the global.asax : Public Shared errMsg As Boolean Public Shared strMsg As String The variable errMsg is initialized to False under the method Session_ Start . Customer Module We begin migration of the application with the Customer module. The first page in the Customer module, login_customer.asp , is renamed to login_customer.aspx . The @Page directive is at the start of the page with the Language=VB and Explicit=true attributes. Following this is the @Import directive, pointing to the namespace Stock_Trading_NET.Global formed by compiling the global.assax.vb under the project workspace. This makes the global variables accessible at the page level. All the variables are declared explicitly as required by the attribute Explicit=true : dim strLogin as String dim k as integer dim strLogout as String dim strChange as String The functions changePassword , validateUser , fnpage , and userLogout are enclosed in the <script> ... </script > tags. These functions refer to various methods of the VBLoginCOM component. Migrating VBLoginCOM VBLoginCOM is migrated into a .NET component. When the original Visual Basic project containing the code is run through the upgrade wizard, the class file Login.cls is converted into Login.vb . The upgrade report for the migrated component is shown in Figure 13-13. As shown in the report, the .NET Framework does not support data type Any , which is in the function declaration for GetPrivateProfileString and hence raises a compile error: Figure 13-13. Upgrade Report for VBLoginCOM. Private Declare Function GetPrivateProfileString Lib "ker_ nel32" Alias "GetPrivateProfileStringA" (ByVal lpApplica_ tionName As String, ByVal lpKeyName As Any , ByVal lpDefault_ As String, ByVal lpReturnedString As String, ByVal nSize As_ Long, ByVal lpFileName As String) As Long This is changed to data type String : Private Declare Function GetPrivateProfileString Lib "kernel32" Alias "GetPrivateProfileStringA" (ByVal lpApplica- tionName As String, ByVal lpKeyName As String , ByVal lpDefault As String, ByVal lpReturnedString As String, ByVal nSize As Integer, ByVal lpFileName As String) As Integer The upgrade report shows a runtime warning for being unable to resolve the default property for a variable strMessage. The variable was declared as data type Variant in the Visual Basic 6.0 component code: Public Function Login(strUserID As String, strpassword As String, ByRef strMessage As Variant ) As Integer This warning occurs because variants are no longer supported under the .NET Framework. The data type Variant is converted into the default data type for the .NET Framework, Object , during migration. The variable is declared as data type String in the upgraded version: Public Function Login(ByRef strUserID As String, ByRef str- password As String, ByRef strMessage As String ) As Short After making these changes in the upgraded code, Login.vb is compiled into its assembly VBLoginCOM.dll . This assembly is added as a reference to the .NET Web application Stock_Trading_NET . The function validateUser is then rewritten as follows : function validateUser() dim objLogin as VBLoginCOM.cLogin dim usrName as String dim usrPwd as String dim strMessage as String dim intLogin as integer usrName = trim(Request.Form("txtID")) if trim(Request.Form("txtNewPWD")) <> "" then usrPwd = trim(Request.Form("txtNewPWD")) else usrPwd = trim(Request.Form("txtPWD")) end if strMessage="" objLogin = new VBLoginCOM.cLogin() intLogin = objLogin.Login(cstr(usrName), _ cstr(usrPwd), strMessage) objLogin = nothing if intLogin <> 0 then validateUser = strMessage else validateUser = "" end if end function The variables local to the function are declared explicitly within the function, and early binding is used to instantiate the new component. All the functions using the component are rewritten in a similar manner. After successful login, the user is taken to page that was welcome.asp in the original application and is now renamed welcome.aspx . This page uses ADO.NET to fetch the user name from the database. The following code shows the use of SQLConnection , SqlCommand, and SqlDataReader objects. <%@ Import Namespace="System.Data"%> <%@ Import Namespace="System.Data.SqlClient"%> dim strConnect as String dim objCommand as SQLCommand dim con as SqlConnection dim rsUser as SQLDataReader dim strSQL as String strConnect="server=pc- p3793;uid=sa;pwd=;database=Stock_System" strSQL = "select CustomerName from Customer where Cus_ tomerID='" & Session("userID") & "' " con = new SqlConnection(strConnect) objCommand = new SQLCommand(strSQL, con) con.open() rsUser = objCommand.ExecuteReader() rsUser.read() The menu links on the page welcome.aspx are all changed to point to .aspx files. After clicking Buy Stocks, the user is taken to the page stockservice. aspx . In the ASP application, this page makes use of the component VBBuySellCOM . For the .NET application, we have exposed this component as a Web service using the SOAP ToolKit and used the Web service in this page. Exposing VBBuySellCOM as Web Service The SOAP ToolKit is run over the Visual Basic COM DLL VBBuySellCOM.dll , as explained in Chapter 11, and the corresponding WSDL and WSML files are generated. We have selected ISAPI as the listener, so no ASP listener file is generated. The generated files are named cBuySell.wsdl and cBuySell.wsml . The files are located in the root of the Web application, so the SOAP address location in the WSDL file points to its current location as follows: <soap:address location='http://localhost/Stock_Trading_NET/ cBuySell.WSDL' /> The proxy class file is generated using the command-line tool wsdl.exe with this WSDL file as its input. The following command is used to generate the proxy class: C:\wsdl /l:VB /namespace:VBBuySellCOM /out:BuySell.vb http:// localhost/Stock_Trading_NET/cBuySell.wsdl The proxy class BuySell.vb follows: Option Strict Off Option Explicit On Imports System Imports System.ComponentModel Imports System.Diagnostics Imports System.Web.Services Imports System.Web.Services.Protocols Imports System.Xml.Serialization ' 'This source code was auto-generated by wsdl, Ver- sion=1.0.3705.0. ' Namespace VBBuySellCOM '<remarks/> 'CODEGEN: The optional WSDL extension element 'binding' from namespace 'http://schemas.microsoft.com/soap-toolkit/ wsdl-extension' was not handled. <System.Diagnostics.DebuggerStepThroughAttribute(), _ System.ComponentModel. DesignerCategoryAttribute("code"), _ System.Web.Services. WebServiceBindingAttribute(Name:="cBuySellSoapBinding", [Namespace]:="http://tempuri.org/wsdl/")> _ Public Class cBuySell Inherits System.Web.Services. Protocols.SoapHttpClientProtocol '<remarks/> Public Sub New() MyBase.New Me.Url = "http://localhost/Stock_Trading_NET/cBuySell.WSDL" End Sub '<remarks/> <System.Web.Services.Protocols.SoapRpcMethodAt- tribute("http://t_ empuri.org/action/cBuySell.PlaceOrder", _ RequestNamespace:="http://tempuri.org/message/", _ ResponseNamespace:="http://tempuri.org/message/")> _ Public Function PlaceOrder(ByRef strCustomerID As _ String, ByRef StockId As String, ByRef StockPrice _ As Double, ByRef Quantity As Short, ByRef BuySell _ As String, ByRef strMessage As String) As _ <System.Xml.Serialization. SoapElementAttribute("Result")> Short Dim results() As Object = Me.Invoke("PlaceOrder",_ New Object() {strCustomerID, StockId, StockPrice,_ Quantity, BuySell, strMessage}) strCustomerID = CType(results(1),String) StockId = CType(results(2),String) StockPrice = CType(results(3),Double) Quantity = CType(results(4),Short) BuySell = CType(results(5),String) strMessage = CType(results(6),String) Return CType(results(0),Short) End Function '<remarks/> Public Function BeginPlaceOrder(ByVal strCustomerID_ As String, ByVal StockId As String, ByVal StockPrice_ As Double, ByVal Quantity As Short, ByVal BuySell As_ String, ByVal strMessage As String, ByVal callback_ As System.AsyncCallback, ByVal asyncState As Object)_ As System.IAsyncResult Return Me.BeginInvoke("PlaceOrder", New Object() {strCustomerID, StockId, StockPrice, Quantity, _ BuySell, strMessage}, callback, asyncState) End Function '<remarks/> Public Function EndPlaceOrder(ByVal asyncResult As_ System.IAsyncResult, ByRef strCustomerID As String,_ ByRef StockId As String, ByRef StockPrice As _ Double, ByRef Quantity As Short, ByRef BuySell As _ String, ByRef strMessage As String) As Short Dim results() As Object = Me.EndInvoke(asyncResult) strCustomerID = CType(results(1),String) StockId = CType(results(2),String) StockPrice = CType(results(3),Double) Quantity = CType(results(4),Short) BuySell = CType(results(5),String) strMessage = CType(results(6),String) Return CType(results(0),Short) End Function End Class End Namespace The proxy class shows the exposed method PlaceOrder of the component and the asynchronous methods BeginPlaceOrder and EndPlaceOrder . The proxy class is now compiled into a .NET assembly using a Visual Basic compiler provided by the command-line tool vbc.exe . The following command compiles the proxy class and creates an assembly: C:\vbc /t:library /r:System.dll /r:System.Web.dll /r:System.Web.Services.dll /r:System.Xml.dll BuySell.vb The newly created assembly BuySell.dll is placed in the /bin directory of the .NET Web application. Before calling the Web service on the page stock_service.aspx , the function createParameters returns an array of the parameters required for the Web Service method PlaceOrder . The array is then split up and the Web service method is called as follows: dim objTrade as VBBuySellCOM.cBuySell dim strParameters as String dim arrParameters dim intTrade as integer dim strMessage as String strParameters = createParameters() arrParameters = split(strParameters , ",") strMessage = "" 'To call the COM method objTrade = new VBBuySellCOM.cBuySell() intTrade = objTrade.PlaceOrder(cstr(arrParameters(0)), _ cstr(arrParameters(1)), cdbl(arrParameters(2)), _ cint(arrParameters(3)), "B", strMessage) This method is also called for selling of stocks, except that the query string value indicates that it is a sell transaction. The call to the method for selling stocks is as follows: dim objTrade as VBBuySellCOM.cBuySell dim strParameters as String dim arrParameters dim intTrade as integer dim strMessage as String strParameters = createParameters() arrParameters = split(strParameters , ",") strMessage = "" 'To call the COM method objTrade = new VBBuySellCOM.cBuySell() intTrade = objTrade.PlaceOrder(cstr(arrParameters(0)), _ cstr(arrParameters(1)), cdbl(arrParameters(2)), _ cint(arrParameters(3)), "S", strMessage) After clicking View Report, the user is taken to the page show_report.aspx to view the details of his or her portfolio. This page makes use of dataset to retrieve and display the records. The function createDataSet returns a dataset. An error flag is set if the dataset is empty and this is checked before displaying the contents of the dataset. Admin Module In this module we use the VBAdminCOM by adding it as a reference to the Web application. This creates the interoperability layer, which allows accessing the methods of the component as if it were a regular .NET assembly. The page login_Admin.aspx calls the COM methods for Login and ChangePassword. The function validateUser is not changed except that all the variables are declared explicitly: function validateUser() dim objLogin as VBAdminCom.cAdmin dim usrName as String dim usrPwd as String dim strMessage as String dim intLogin as Short usrName = trim(Request.Form("txtID")) if trim(Request.Form("txtNewPWD")) <> "" then usrPwd = trim(Request.Form("txtNewPWD")) else usrPwd = trim(Request.Form("txtPWD")) end if strMessage="" objLogin = new VBAdminCom.cAdmin() intLogin = objLogin.Login(cstr(usrName), _ cstr(usrPwd), strMessage) objLogin = nothing if intLogin <> 0 then validateUser = strMessage else validateUser = "" end if end function Thus, the COM calls remain unchanged across the module. Under the menu Approve Orders, all unapproved orders are listed based on the selection made by the user. The page approveorder.aspx calls a stored procedure sp_Get_Unapproved_Orders directly as follows: Dim Customer Dim buy_sell Dim order_id Dim strMessage Dim status Dim strConnect as String Dim objCommand as SQLCommand Dim Con as SQLConnection Dim rsOrder as SQLDataReader Dim strCommandText as String Dim objParam as SQLParameter Customer = TRIM(Request("lstCustomer")) buy_sell = TRIM(Request("lstType")) errMsg = false strConnect="server=pc-p3793;uid=sa;pwd=;database=Stock_System" strCommandText = "sp_Get_Unapproved_Orders" Con = new SqlConnection(strConnect) objCommand = new SQLCommand(strCommandText, Con) objCommand.CommandType = CommandType.StoredProcedure objParam = objCommand.Parameters.Add("@ReturnCode", SqlDb- Type.Int) objParam.Direction = ParameterDirection.ReturnValue objParam = objCommand.Parameters.Add("@p_CustomerID", SqlDb- Type.VarChar, 8) objParam.Direction = ParameterDirection.Input objParam.Value = Customer objParam = objCommand.Parameters.Add("@p_Buy_Sell", SqlDb- Type.Char, 1) objParam.Direction = ParameterDirection.Input objParam.Value = buy_sell objParam = objCommand.Parameters.Add("@p_Status", SqlDb- Type.Int) objParam.Direction = ParameterDirection.Output objParam = objCommand.Parameters.Add("@p_Message", SqlDb- Type.NVarChar, 1000) objParam.Direction = ParameterDirection.Output Con.open() rsOrder = objCommand.ExecuteReader() if not rsOrder.read() then strMsg = "There are no orders to be approved..." errMsg=true end if On the page addcustomer.aspx , the HTML controls are replaced with server-side controls. We have also used validation controls so that the server-side validation routine is eliminated. The following code shows a server-side text control for accepting Customer Name and its associated RequiredFieldValidator control. <asp:textbox id="txtCustName" Runat="server" MaxLength="20" TextMode="SingleLine"></asp:textbox> <asp:requiredfieldvalidator id="Requiredfieldvalidator3" Runat="server" Display="None" ErrorMessage="You must enter a Customer's Name." InitialValue="" ControlToValidate="txtCust- Name"></asp:requiredfieldvalidator> A validation summary control is used to list the validation errors: <asp:validationsummary id="ValidationSummary1" ShowSum- mary="True" DisplayMode="BulletList" HeaderText="List of Errors in submitting the page" Runat="server"></asp:valida- tionsummary> We have used the code-behind in this form to call the COM method for adding customer; this is done within the Page_Load event: Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load errMsg = False If Page.IsPostBack () Then Try strCustID = txtCustId.Text strPassword = txtCustPass.Text strCustomerName = txtCustName.Text strAddress = txtCustAddress.Text intPhone = Integer.Parse(txtCustPhone.Text) strEmail = txtCustEmail.Text intMarginAmount = Double.Parse(txtMargin.Text) intMinBal = Integer.Parse(txtBalance.Text) strMessage = "" objCustomer = New VBAdminCom.cAdmin() intCust = objCustomer.AddUser(strCustID, strPassword, strCustomerName, strAddress, intPhone, strEmail, intMarginAmount, intMinBal, strMessage) If intCust = 0 Then Server.Transfer("welcome.aspx", False) Else errMsg = True strMsg = strMessage End If Catch ex As Exception errMsg = True strMsg = "Failed to add customer." & _ ex.Message & " " End Try End If End Sub We have used structured exception handling to trap exceptions that arise if a wrong value is entered into the database. This is possible because we have used RequiredFieldValidator controls, which will only prevent blank values from being sent across. We use the variable errMsg as an error flag across the application. In case of an error, say if a data reader is empty, we set the flag to True and assign an appropriate error message to the variable strMsg . At the start of the page, we check for the error flag and display the error message as shown: if errMsg then Response.Write("<div class=errorMsg align=center>" & strMsg_ & "</div>") Response.Write("<div align=center><input type=button class=button value=Back onClick=""document.frmStockser- vice.action='welcome.aspx';document.frmStockservice.sub- mit();""></div>") Response.End end if To prevent the browser from caching, we use the following lines of code across the application: Response.Expires = 60 Response.AddHeader("pragma","no-cache") Response.AddHeader("cache-control","private") Response.CacheControl = "no-cache" In this code the following one line is missing when compared to the ASP code to prevent caching: Response.Expiresabsolute = Now() 1 This code uses an arithmetic operation over a function returning the date and time. Because there is no explicit type conversion involved here, this line throws an error for invalid character detected after Now . The rest of the code, as before, uses properties of the Response object to prevent caching. |