Creating custom SOAP headers in VB.NET 

This is rather horrible, and the documentation isn't very good.  I was able to get this to log the soap messages; then to amend them. However you cannot both log and amend in the same stage of the outgoing message! (this tripped me up repeatedly)

A sample vb.net project connecting to the DPS web service is available for download:    

You need to create a new class within your project (in this case !TraceExtension, which writes the soap to a log file).  To enable the soap extension you need to add lines to your app.config. Details of these are in the vb project as comments. 

Note that there is little on the web.  Your best guide is the VB.Net help on soap extensions.  A VB.NET example for this is here: http://msdn2.microsoft.com/en-us/library/system.web.services.protocols.soapextension.aspx 

1. Create a standard web services client in VB.NET 

1. Start up VB.NET and  create a new WindowsApplication.   

2. Add a web reference to your WSDL in the usual way and let it generate stuff. 

3. Add a button, a Listbox and a textbox on the form, and double-click on the button to open the code window 

4. Stick the code into your method to call a method on your webservice (DPS is the web service I am accessing and hlwus006 is the name I gave it):

        '-- Instantiate the service
        Dim dps007 As hlwus006.DPS = New hlwus006.DPS
        Dim dps007reply As hlwus006.ListPropertiesReply
        Dim reply As hlwus006.PropertyDef
        Dim replies As hlwus006.PropertyDef()

        ListBox1.Items.Clear()
        TextBox1.Clear()

        '-- Run the list Properties method on the service
        dps007reply = dps007.ListProperties

        '-- Display the list of DPS models in a listbox
        replies = dps007reply.PropertyDefs
        For Each reply In replies
            ListBox1.Items.Add(reply.name + "    " + reply.desc + "   ")
        Next
(This particular web service is returning a series of strings) 

5.  Give it a run and check everything is working OK. 

2. Create a Soap Extension 

In VB.Net Express Edition, look up the helpfile section on Soap Extensions.  Basically this is a way to intercept and modify the soap message on its way in and out of your client. 

1.  Find the app.config for your application.  It will be in the base directory of your folder. Open it. 

2.  Insert a new section at the bottom, above the last tag:

<system.web>
     <webServices>
         <soapExtensionTypes>
               <add type="DPSModels.TraceExtension, DPSModels" group="0" priority="1"  />
         </soapExtensionTypes>
     </webServices>
</system.web>
where DPSModels is the name of your project.  TraceExtension can be anything, but is the name of the class which you are about to create. 

3.  Add a new class TraceExtension to your project.  Open it.  Now copy the example code from http://msdn2.microsoft.com/en-us/library/system.web.services.protocols.soapextension.aspx.   

This inspects the stream and writes them to a file.  We can then modify this to change the soap header.

Imports System
Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports System.IO

' Define a SOAP Extension that traces the SOAP request and SOAP response
' for the XML Web service method the SOAP extension is applied to.
Public Class TraceExtension
    Inherits SoapExtension

    Private oldStream As Stream
    Private newStream As Stream
    Private m_filename As String

    ' Save the Stream representing the SOAP request or SOAP response into
    ' a local memory buffer.
    Public Overrides Function ChainStream(ByVal stream As Stream) As Stream
        oldStream = stream
        newStream = New MemoryStream()
        Return newStream
    End Function

    ' When the SOAP extension is accessed for the first time, the XML Web
    ' service method it is applied to is accessed to store the file
    ' name passed in, using the corresponding SoapExtensionAttribute.    
    Public Overloads Overrides Function GetInitializer(ByVal methodInfo As _
        LogicalMethodInfo, _
    ByVal attribute As SoapExtensionAttribute) As Object
        Return CType(attribute, TraceExtensionAttribute).Filename
    End Function

    ' The SOAP extension was configured to run using a configuration file
    ' instead of an attribute applied to a specific XML Web service
    ' method.  Return a file name based on the class implementing the Web
    ' Service's type.
    Public Overloads Overrides Function GetInitializer(ByVal WebServiceType As _
      Type) As Object
        ' Return a file name to log the trace information to, based on the
        ' type.
        Return "C:\" + WebServiceType.FullName + ".log"
    End Function

    ' Receive the file name stored by GetInitializer and store it in a
    ' member variable for this specific instance.
    Public Overrides Sub Initialize(ByVal initializer As Object)
        m_filename = CStr(initializer)
    End Sub

    ' If the SoapMessageStage is such that the SoapRequest or SoapResponse
    ' is still in the SOAP format to be sent or received over the network,
    ' save it out to file.
    Public Overrides Sub ProcessMessage(ByVal message As SoapMessage)
        Select Case message.Stage
            Case SoapMessageStage.BeforeSerialize
            Case SoapMessageStage.AfterSerialize
                WriteOutput(message)
            Case SoapMessageStage.BeforeDeserialize
                WriteInput(message)
            Case SoapMessageStage.AfterDeserialize
        End Select
    End Sub

    ' Write the SOAP message out to a file.
    Public Sub WriteOutput(ByVal message As SoapMessage)
        newStream.Position = 0
        Dim fs As New FileStream(m_filename, FileMode.Append, _
                                 FileAccess.Write)
        Dim w As New StreamWriter(fs)
        w.WriteLine("-----Request at " + DateTime.Now.ToString())
        w.Flush()
        Copy(newStream, fs)
        w.Close()
        newStream.Position = 0
        Copy(newStream, oldStream)
    End Sub

    ' Write the SOAP message out to a file.
    Public Sub WriteInput(ByVal message As SoapMessage)
        Copy(oldStream, newStream)
        Dim fs As New FileStream(m_filename, FileMode.Append, _
                                 FileAccess.Write)
        Dim w As New StreamWriter(fs)

        w.WriteLine("----- Response at " + DateTime.Now.ToString())
        w.Flush()
        newStream.Position = 0
        Copy(newStream, fs)
        w.Close()
        newStream.Position = 0
    End Sub

    Sub Copy(ByVal fromStream As Stream, ByVal toStream As Stream)
        Dim reader As New StreamReader(fromStream)
        Dim writer As New StreamWriter(toStream)
        writer.WriteLine(reader.ReadToEnd())
        writer.Flush()
    End Sub
End Class

' Create a SoapExtensionAttribute for our SOAP Extension that can be
' applied to an XML Web service method.
 _
Public Class TraceExtensionAttribute
    Inherits SoapExtensionAttribute

    Private m_filename As String = "c:\log.txt"
    Private m_priority As Integer

    Public Overrides ReadOnly Property ExtensionType() As Type
        Get
            Return GetType(TraceExtension)
        End Get
    End Property

    Public Overrides Property Priority() As Integer
        Get
            Return m_priority
        End Get
        Set(ByVal Value As Integer)
            m_priority = value
        End Set
    End Property

    Public Property Filename() As String
        Get
            Return m_filename
        End Get
        Set(ByVal Value As String)
            m_filename = value
        End Set
    End Property
End Class
Now give this a run and check that c:\yourprojectname....txt does contain some soap message and that it all works OK. 

4.  Add the following class to the end of the TraceExtension file:

'-- Define a SOAP header by deriving from the SoapHeader base class.
'-- Specify in it the fields that we want
Public Class MyHeader
    Inherits SoapHeader

    Public Userid As String
    Public ApplicationName As String
End Class
5.  The ProcessMessage method looks like this:
    Public Overrides Sub ProcessMessage(ByVal message As SoapMessage)
        Select Case message.Stage
            Case SoapMessageStage.BeforeSerialize
            Case SoapMessageStage.AfterSerialize
                WriteOutput(message)
            Case SoapMessageStage.BeforeDeserialize
                WriteInput(message)
            Case SoapMessageStage.AfterDeserialize
        End Select
    End Sub
Modify it to add the header to the message:
    Public Overrides Sub ProcessMessage(ByVal message As SoapMessage)

        Select Case message.Stage
            Case SoapMessageStage.BeforeSerialize

                MsgBox("In ProcessMessage.beforeserialize")
                '-- Add header
                Dim hdr As New MyHeader
                hdr.ApplicationName = "Roger's test client"
                hdr.Userid = "wrp43058"
                message.Headers.Add(hdr)

            Case SoapMessageStage.AfterSerialize
                MsgBox("In ProcessMessage.afterserialize")
                WriteOutput(message)
            Case SoapMessageStage.BeforeDeserialize
                MsgBox("In ProcessMessage.beforedeserialize")
                Copy(oldStream, newStream)
                newStream.Position = 0
            Case SoapMessageStage.AfterDeserialize
            Case Else
                Throw New Exception("invalid stage")
        End Select
    End Sub
You may find it helpful to scatter some msgbox("...") in the code so you can see where you are. Now give it a run.  

You may get a 'request is not well formed XML error'....  This means that you haven't handled the stream coming back as above (the stuff in BeforeDeserialize is mysterious but necessary). 

If you look at the log file, your soap message should now have a header! --

-----Request at 11/10/2007 11:26:07
<?xml version="1.0" encoding="utf-8"?><soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Header><MyHeader
xmlns="http://gsk.com/cix/"><Userid>wrp43058</Userid><ApplicationName>Roger's
test
client</ApplicationName></MyHeader></soap:Header><soap:Body><ListProperties
xmlns="http://gsk.com/cix/" /></soap:Body></soap:Envelope>

Constructive feedback is welcomed to Roger Pearse.

Written 12th October 2007.

This page has been accessed by  ****** people since 12th October 2007.

Return to Roger Pearse's Pages