Exposing Existing Code as a Web Service Using the .NET Framework
Steve Kirk and Priya Dhawan
Microsoft Developer Network
Revised February 2001
Summary: This article covers data transformations made to expose existing Microsoft Visual Basic 6.0 code as a Web Service using ASP .NET. (8 printed pages).
The Microsoft® .NET Framework simplifies the task of exposing .NET code as a Web Service. One reason for this simplicity is that the .NET Framework includes provisions for transforming complex .NET data types into XML (serialization) and reversing the process (de-serialization).
Existing code written as pre-.NET classes passing data in language-specific data types or COM objects is not able to rely on the same standard provisions for serializing these data types as XML. This article covers data transformations required to expose existing Microsoft Visual Basic® 6.0 code as an ASP .NET Web Service.
The data transformation issues covered in this article are not the only issues to consider when evaluating the suitability of existing code for exposure as a Web Service. Other considerations include object and state models, return data sizes, how success is indicated, how error information is returned, the security model (including access control, authentication, and encryption), the execution model (synchronous or asynchronous), how the code is to be distributed, and the transaction model (COM+ transactions or declarative transactions). These issues will be covered in upcoming Architectural Topics articles.
It would be quite a project to cover transformation of all of the data types passed by existing code, so some of the most widely used are covered here, along with XML (as a string), which can cover just about any other data type providing that the existing code is extended with XML interfaces. Transformation techniques for the following data types are discussed:
- ADO 2x Command object
- ADO2x Recordset object
- Stream object
- XMLDOM object
The Microsoft ActiveX® Data Objects (ADO) Command object is often exposed by existing code that accesses a database directly. Although the Command object cannot be passed between tiers of an application operating in separate processes, it can be passed within a process. Returning data through Command object output parameters is more efficient than returning data through an ADO recordset for single-row data entities. This makes the ADO Command object useful for returning single row entity data.
Existing code in the following example returns an ADO Command object containing data as output parameters. The Parameters collection of the Command object is transformed to XML and returned to the Web Service consumer:
' Existing code returns ADO Command object Cmd = EC.Example1() ' Transform Parameters collection of Command object using XmlTextWriter and StringWriter ' Instantiate stringwriter and xmlwriter for returning xml string strWriter = New StringWriter() xmlWriter = New XmlTextWriter(strWriter) ' Iterate through Parameters collection writing name and value For i = 0 To Cmd.Parameters.Count - 1 xmlwriter.WriteElementString(Cmd.Parameters(i).Name.Substring(1),_ Cmd.Parameters(i).Value.ToString) Next ' Return xml as a string Example1 = strWriter.GetStringBuilder.ToString()
Note See Example 1 in the BDAdotNetWebServices1.vb sample code (see top of article for download).
Passing data as Command object parameters is an efficient way to pass data. It is also extensible and offers some type checking.
' Read the XML data submitted to the Web service into the xmlreader strReader = New StringReader(commandXML) xmlreader = New XmlTextReader(strReader) ' Move to the Root node xmlreader.MoveToContent() ' Move to the first Element xmlreader.MoveToElement() ' Read the CommandType Element Cmd.CommandType = xmlreader.ReadElementString() ' Read the CommandText Element Cmd.CommandText = xmlreader.ReadElementString() ' Move to Parameters Element xmlreader.MoveToElement() ' Read the first Parameter Element xmlreader.Read() Dim i As Integer ' Build the Parameters Collection of the Command object While xmlreader.Name = "Parameter" Param = New ADODB.Parameter() With Param xmlreader.MoveToAttribute("Name") .Name = "@" + xmlreader.Value xmlreader.MoveToAttribute("Type") .Type = xmlreader.Value xmlreader.MoveToAttribute("Size") .Size = xmlreader.Value xmlreader.MoveToAttribute("Direction") .Direction = xmlreader.Value If .Direction = ADODB.ParameterDirectionEnum.adParamInput Then xmlreader.MoveToAttribute("Value") .Value = xmlreader.Value End If End With Cmd.Parameters.Append(Param) xmlreader.Read() End While ' Call the Existing code and pass the Command object EC.Example2(Cmd)
Note See Example 2 in the BDAdotNetWebServices1.vb sample code (see top of article for download).
ADO 2x disconnected recordsets are commonly used to pass data between tiers of a multitier application. Data can be single row, sets of rows, or hierarchical rows.
In this example, existing code returns an ADO Recordset object containing hierarchical row data that is transformed to XML before being returned by the Web Service:
' Existing code returns Recordest RS = EC.Example3() ' Instantiate a stream to receive Recordset data Stream = New ADODB.Stream() ' Write XML representation of recordset to stream RS.Save(Stream, ADODB.PersistFormatEnum.adPersistXML) ' Return XML as string from stream Example3 = Stream.ReadText
Note See Example 3 in the BDAdotNetWebServices1.vb sample code (see top of article for download).
In the following example, XML representing hierarchical row data is used to populate an ADO Recordset object, which is passed to existing code:
' Instantiate a recordset object RS = New ADODB.Recordset() ' Instantiate a stream object Stream = New ADODB.Stream() ' Open the stream object Stream.Open() ' Write XML to the stream Stream.WriteText(RsXML) ' Position the pointer to the begining of the stream Stream.Position = 0 ' Open the recordset from the stream XML data RS.Open(Stream) ' Pass recordset to existing code EC.Example4(RS)
Note See Example 4 in the BDAdotNetWebServices1.vb sample code (see top of article for download).
A stream can provide an efficient way to pass data between native tiers of an application. It is the primary means for reading XML from Microsoft SQL Server™ 2000.
In the following example, existing code returns a stream of XML representing hierarchical row data, which is read as a string to be returned by the Web Service:
Dim Stream As ADODB.Stream Stream = EC.Example5() Example5 = Stream.ReadText
Note See Example 5 in the BDAdotNetWebServices1.vb sample code (see top of article for download).
An XMLDOM object is a good way to pass data between native tiers of a multi-tier application. It provides interface extensibility, type checking, and schema validation.
In the following example, existing code returns an XML Document Object Model (XMLDOM), which is transformed to a string of XML and returned by the Web Service:
Dim Doc As MSXML2.DOMDocument ' Existing code returnins XMLDOM object Doc = EC.Example6() ' Return XML from DOM object Example6 = Doc.xml
Note See Example 6 in the BDAdotNetWebServices1.vb sample code (see top of article for download).
In the following example, XML representing hierarchical row data is used to populate an XMLDOM object and passed to existing code:
Dim Doc As MSXML2.DOMDocument ' Instantiate an XMLDOMDocument object Doc = New MSXML2.DOMDocument() ' Load XML into DOM Doc.loadXML(orderXML) ' Pass DOM to existing code EC.Example7(Doc)
Note See Example 7 in the BDAdotNetWebServices1.vb sample code (see top of article for download).
XML is a simple way to pass data between tiers. It also pushes conversion of data into XML to the "existing code" side of the COM Interop boundary, which, depending on the interface, may be more efficient than translating data into XML across the COM Interop boundary.
In the following example, existing code returns a string containing XML data, which is then passed by the Web Service to the consumer:
EC = New ExCode.ExClass() ' Pass XML string directly from existing code to consumer Example8 = EC.Example8()
Note See Example 8 in the BDAdotNetWebServices1.vb sample code (see top of article for download).
In the following example, an XML representation of hierarchical row data is passed as a string to existing code:
EC = New ExCode.ExClass() ' Pass XML string directly from consumer to existing code EC.Example9(orderXML)
Note See Example 9 in the BDAdotNetWebServices1.vb sample code (see top of article for download).
This article and the accompanying examples cover data transformations that enable existing code to be exposed as a Web Service with ASP .NET. Some widely used interface objects are discussed here, including XML as a string, which can cover most data provided that existing code is extended with the appropriate interface.
The performance of these solutions varies and is also affected by the size of the data being passed. Look for comparisons of these implementations in an upcoming article in this series.
Interface is only one of many things to consider when evaluating the suitability of existing code as a Web Service. Other considerations are security (including authorization, authentication, and encryption), transaction model, state model, the way errors and results are returned, and whether the code executes synchronously or asynchronously.