|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
ContentsIntroductionI was tasked with implementing a .NET Web Service to provide access to our application data by outside entities. The Web Service must support clients written in .NET languages, Visual Basic 6.0, and non-Microsoft languages. All access to our database must be through stored procedures, so we did not need to support running direct SQL queries. The design was to use a single method that accepts an XML string that describes the stored procedure to run, its parameters, and how to return the data if any data is returned ( I knew going in that there would be some performance penalty for using ADODB in the .NET code, but I never dreamed it would be so pronounced. It seems that the penalty for using COM interop from .NET can be quite severe. Back to the drawing board. Now for the problem, I knew from the beginning that if I had to provide the XML node list that a I followed the instructions in the article, set up the code, and it worked! The only problem I had now was that the example code from Microsoft was writing the XML to a file and reading an XSL file. Since I need this to be a server solution, I needed to eliminate the file IO. No big deal, I altered the code to generate the XSL on the fly in a string (it was quite small), and used a The first problem I saw was that the only fields getting a data type were integers and strings, for all other fields the data type was blank. This was causing an error when attempting to load the Now I wrapped the code in a class and integrated it into the Web Service and began unit testing. I ran through a couple of calls, then hit another problem - binary fields. This one took a while to research, but I found that binary fields in a Now I needed to find or write a function to perform the binary hex encoding. I couldn't find any ready-made code on the internet, but, I did find information on binary hex encoding. 10010010111100011010110011010100
divide into octets (bytes): 10010010 11110001 10101100 11010100
divide the octets into 4 bit nibbles: 1001 0010 1111 0001 1010 1100 1101 0100
decimal values of nibbles: 9 2 15 1 10 12 13 4
hexadecimal values: 9 2 F 1 A C D 4
encoded string representing the original 32 bit binary value: "92F1ACD4
Armed with the above information, it was simple to write a small function to binary hex encode a byte array and return the resultant string. I added this to the class, modified the data transformation code to detect binary fields, and applied this new function to the data. Now I have a conversion class that I can use. The only thing that you may need to update is the function that determines the data type to put in the XML. If you need a data type that I'm not trapping, returning it as a string is not sufficient, just add a Now, on to the code! BackgroundRequirements:
This article assumes that you are familiar with the following topics:
Using the codeIt should be noted here that I started with the code in the above mentioned Microsoft Support article. The code presented, however, is a substantial update of that code. Note: You must call the The Public Function GetADORS(ByVal DS As DataSet, _
ByVal dbName As String) As String
Try
'Create a MemoryStream to contain the XML
Dim mStream As New MemoryStream
'Create an XmlWriter object, to write
'the formatted XML to the MemoryStream
Dim xWriter As New XmlTextWriter(mStream, Nothing)
'Additional formatting for XML
xWriter.Indentation = 8
xWriter.Formatting = Formatting.Indented
'call this Sub to write the ADONamespaces
WriteADONamespaces(xWriter)
'call this Sub to write the ADO Recordset Schema
WriteSchemaElement(DS, dbName, xWriter)
'Call this sub to transform
'the data portion of the Dataset
TransformData(DS, xWriter)
'Flush all input to XmlWriter
xWriter.Flush()
'Prepare the return value
mStream.Position = 0
Dim Buffer As Array
Buffer = Array.CreateInstance(GetType(Byte), mStream.Length)
mStream.Read(Buffer, 0, mStream.Length)
Dim TextConverter As New UTF8Encoding
Return TextConverter.GetString(Buffer)
Catch ex As Exception
'Returns error message to the calling function.
Err.Raise(100, ex.Source, ex.ToString)
End Try
End Function
First, I've added two lines that indicate to the Private Sub WriteADONamespaces(ByRef xWriter As XmlTextWriter)
'Uncomment the following line to change
'the encoding if special characters are required
'writer.WriteProcessingInstruction("xml",
' "version='1.0' encoding='ISO-8859-1'")
'Add XML start element
xWriter.WriteStartElement("", "xml", "")
'Append the ADO Recordset namespaces
xWriter.WriteAttributeString("xmlns", "s", Nothing, _
"uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882")
xWriter.WriteAttributeString("xmlns", "dt", Nothing, _
"uuid:C2F41010-65B3-11d1-A29F-00AA00C14882")
xWriter.WriteAttributeString("xmlns", "rs", Nothing, _
"urn:schemas-microsoft-com:rowset")
xWriter.WriteAttributeString("xmlns", "z", _
Nothing, "#RowsetSchema")
xWriter.Flush()
End Sub
The code in Private Sub WriteSchemaElement(ByVal DS As DataSet, _
ByVal dbName As String, ByRef xWriter As _
XmlTextWriter)
'write element Schema
xWriter.WriteStartElement("s", "Schema", _
"uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882")
xWriter.WriteAttributeString("id", "RowsetSchema")
'write element ElementType
xWriter.WriteStartElement("s", "ElementType", _
"uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882")
'write the attributes for ElementType
xWriter.WriteAttributeString("name", "", "row")
xWriter.WriteAttributeString("content", "", "eltOnly")
xWriter.WriteAttributeString("rs", "updatable", _
"urn:schemas-microsoft-com:rowset", "true")
WriteSchema(DS, dbName, xWriter)
'write the end element for ElementType
xWriter.WriteFullEndElement()
'write the end element for Schema
xWriter.WriteFullEndElement()
xWriter.Flush()
End Sub
The code in Private Sub WriteSchema(ByVal DS As DataSet, ByVal dbName _
As String, ByRef xWriter As XmlTextWriter)
Dim i As Int32 = 1
Dim DC As DataColumn
For Each DC In DS.Tables(0).Columns
DC.ColumnMapping = MappingType.Attribute
xWriter.WriteStartElement("s", "AttributeType", _
"uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882")
'write all the attributes
xWriter.WriteAttributeString("name", "", DC.ToString)
xWriter.WriteAttributeString("rs", "number", _
"urn:schemas-microsoft-com:rowset", i.ToString)
xWriter.WriteAttributeString("rs", "baseCatalog", _
"urn:schemas-microsoft-com:rowset", dbName)
xWriter.WriteAttributeString("rs", "baseTable", _
"urn:schemas-microsoft-com:rowset", _
DC.Table.TableName.ToString)
xWriter.WriteAttributeString("rs", "keycolumn", _
"urn:schemas-microsoft-com:rowset", _
DC.Unique.ToString)
xWriter.WriteAttributeString("rs", "autoincrement", _
"urn:schemas-microsoft-com:rowset", _
DC.AutoIncrement.ToString)
'write child element
xWriter.WriteStartElement("s", "datatype", _
"uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882")
'write attributes
xWriter.WriteAttributeString("dt", "type", _
"uuid:C2F41010-65B3-11d1-A29F-00AA00C14882", _
GetDatatype(DC.DataType.ToString))
xWriter.WriteAttributeString("dt", "maxlength", _
"uuid:C2F41010-65B3-11d1-A29F-00AA00C14882", _
DC.MaxLength.ToString)
xWriter.WriteAttributeString("rs", "maybenull", _
"urn:schemas-microsoft-com:rowset", _
DC.AllowDBNull.ToString)
'write end element for datatype
xWriter.WriteEndElement()
'end element for AttributeType
xWriter.WriteEndElement()
xWriter.Flush()
i = i + 1
Next
DC = Nothing
End Sub
The code in Private Function GetDatatype(ByVal DType As String) As String
Select Case (DType)
Case "System.Int32", "System.Int16", "System.Integer"
Return "int"
Case "System.DateTime"
Return "dateTime.iso8601tz"
Case "System.String"
Return "string"
Case "System.Byte[]"
Return "bin.hex"
Case "System.Boolean"
Return "boolean"
Case "System.Guid"
Return "guid"
Case Else
Return "string"
End Select
End Function
The Private Sub TransformData(ByVal DS As DataSet, _
ByRef xWriter As XmlTextWriter)
'Loop through DataSet and add data to XML
xWriter.WriteStartElement("", "rs:data", "")
Dim i As Long
Dim j As Integer
'For each row...
For i = 0 To DS.Tables(0).Rows.Count - 1
'Write the start element for the row
xWriter.WriteStartElement("", "z:row", "")
'For each field in the row...
For j = 0 To DS.Tables(0).Columns.Count - 1
'Write the attribute that describes
'this field and it's value
If DS.Tables(0).Columns(j).DataType.ToString_
= "System.Byte[]" Then
'Binary data must be properly encoded (bin.hex)
If Not IsDBNull(DS.Tables(0).Rows(i).Item(
DS.Tables(0).Columns(j).ColumnName)) Then
xWriter.WriteAttributeString(DS.Tables(0).
Columns(j).ColumnName, _
DataToBinHex(DS.Tables(0).Rows(i).Item(
DS.Tables(0).Columns(j).ColumnName)))
End If
Else
If Not IsDBNull(DS.Tables(0).Rows(i).Item(
DS.Tables(0).Columns(j).ColumnName)) Then
xWriter.WriteAttributeString(
DS.Tables(0).Columns(j).ColumnName, _
CType( _
DS.Tables(0).Rows(i).Item(DS.Tables(0).
Columns(j).ColumnName), String))
End If
End If
Next
'End the row element
xWriter.WriteEndElement()
Next
'Write the end element for rs:data
xWriter.WriteEndElement()
'Write the end element for xml
xWriter.WriteEndElement()
xWriter.Flush()
End Sub
Private Function DataToBinHex(ByVal thisData As Byte()) As String
Dim sb As New StringBuilder
Dim i As Integer = 0
For i = 0 To thisData.Length - 1
'First nibble of byte (4 most significant bits)
sb.Append(Hex((thisData(i) And &HF0) / 2 ^ 4))
'Second nibble of byte (4 least significant bits)
sb.Append(Hex(thisData(i) And &HF))
Next
Return sb.ToString
End Function
The ConclusionAs long as developers are faced with integrating .NET and VB 6.0, there will be a need to have the ability to pass data from one to the other. In the case where VB 6.0 is the client, the code presented here should help to alleviate the problem. While the code in the Microsoft article was a good starting point, it has a few shortcomings that prevent it from operating correctly in many real world situations. I believe that I have addressed the major concerns and shortfalls, and am presenting code that can be dropped into a project and used "as is" in most situations. History
|
||||||||||||||||||||||