Outgoing message from WCF client is incomplete when run outside of Visual Studio

StackOverflow https://stackoverflow.com/questions/12539256

  •  03-07-2021
  •  | 
  •  

Question

I have a WCF client application written in VB.NET 2008 as a Windows forms application. This client app successfully communicates with a remote non-WCF service maintained by another company. The problem is - the communication is only successful when the client app is run from within Visual Studio (VS2008), not when it is run as a built executable. When the client app is run as an executable, the remote service returns this message:

"The HTTP request is unauthorized with client authentication scheme 'Anonymous'. The authentication header received from the server was ''. The remote server returned an error: (401) Unauthorized."

Digging a little deeper, I noticed the reason for this error. The message being sent to the remote service when the client app runs outside of VS is missing a section that it contains when run inside VS. The message sent when the app runs inside VS (i.e. the one that works properly) is shown below with sensitive information replaced with "x":

<HttpRequest     xmlns="http://schemas.microsoft.com/2004/06/ServiceModel/Management/MessageTrace">
  <Method>POST</Method>
  <QueryString></QueryString>
  <WebHeaders>
    <VsDebuggerCausalityData>uIDPo6ppHQnHmDRGnZfDLPni6RYAAAAAaEkfl5VJXUauv5II8hPneT1AMwBfkoZNgfxEAZ2x4zQACQAA</VsDebuggerCausalityData>
    <AUTHORIZATION>xxxxxxxxxxxxxxxxxxxxxxxxxxxx</AUTHORIZATION>
  </WebHeaders>
</HttpRequest>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
    <Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none"></Action>
  </s:Header>
  <s:Body s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <q1:getNewEvents_PPHS xmlns:q1="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxins">
      <loginObject href="#id1" xmlns=""></loginObject>
    </q1:getNewEvents_PPHS>
    <q2:LoginObject id="id1" xsi:type="q2:LoginObject" xmlns:q2="java:zoasis.ws.datamodel.general">
      <clinicId xsi:type="xsd:int" xmlns="">xxxxx</clinicId>
      <corporateId xsi:type="xsd:int" xmlns="">x</corporateId>
      <password xsi:type="xsd:string" xmlns="">xxxxx</password>
      <userName xsi:type="xsd:string" xmlns="">xxxx</userName>
    </q2:LoginObject>
  </s:Body>
</s:Envelope>

When running as a standalone executable, the client app sends out the same as above except that the entire HttpRequest section is missing - everything from <HttpRequest> to </HttpRequest>

Can anyone tell me why running the client app outside Visual Studio would cause the HttpRequest portion of the message to drop off? The app.config file is identical in both cases.

Thanks.


Per Mike's request, here is some more information. The client proxy was created using "Add Service Reference" in Visual Studio 2008.

The code that creates the message that is sent to the service is shown below in three parts.

The first part is a class called AntechServiceReference. It has two relevant methods. Its constructor builds the proxy that will be used to interact with the web service. The other method, called GetPendingDownloads, calls the web service method.

Imports WebServiceInterface.AntechServiceReference
Imports System.Configuration.ConfigurationManager
Imports System.ServiceModel
Imports System.ServiceModel.Security
Imports System.Text
Imports System.IO
Imports System.Xml

Public Class AntechLabDataAccess
    ' This class controls all data interaction with the remote Antech web service.

    Private ClassName As String = "AntechLabDataAccess"
    Private mErrText As String
    Private mAntServProxy As ZoasisGroupServicesPortClient
    Private mLoginObject As WebServiceInterface.AntechServiceReference.LoginObject
    Private mLabEventIDs As WebServiceInterface.AntechServiceReference.LabAccessionIdObject()

    Public Sub New()
        Dim Action As String = ""
        Dim CustomBehavior As MessageEndPointBehavior

        Try
            mErrText = ""
            Action = "Creating proxy for web service. "

            ' Create a proxy to the remote web service for use in this object. Supply client credentials
            ' from app.config

            mAntServProxy = New ZoasisGroupServicesPortClient("ZoasisGroupServicesPort")

            ' Retrieve access credentials for this web service from app.config.
            Action = "Setting up login object. "
            mLoginObject = New WebServiceInterface.AntechServiceReference.LoginObject
            If Not AppSettings("ClinicID") Is Nothing Then
                mLoginObject.clinicId = Integer.Parse(AppSettings("ClinicID"))
            End If
            If Not AppSettings("CorporateID") Is Nothing Then
                mLoginObject.corporateId = Integer.Parse(AppSettings("CorporateID"))
            End If
            If Not AppSettings("Password") Is Nothing Then
            mLoginObject.password = AppSettings("Password")
            End If
            If Not AppSettings("UserName") Is Nothing Then
                mLoginObject.userName = AppSettings("UserName")
            End If

            ' Add our custom behavior to the proxy. This handles creation of the message credentials
            ' necessary for web service authorization.
            Action = "Adding custom behavior to the proxy. "
            CustomBehavior = New MessageEndPointBehavior
            mAntServProxy.Endpoint.Behaviors.Add(CustomBehavior)

        Catch ex As Exception
            mErrText = "Error caught in class [" & ClassName & "], method [New]. Action = " & Action & " Message = " & ex.Message & ". "
            If Not ex.InnerException Is Nothing Then
                mErrText &= "Additional Info: " & ex.InnerException.ToString & ". "
            End If
            Throw New Exception(mErrText)
        End Try

    End Sub

    Public Sub GetPendingDownloads()
        Dim Action As String = ""

        Try
            mErrText = ""
            Action = "Calling getNewEvents_PPHS. "
            mLabEventIDs = mAntServProxy.getNewEvents_PPHS(mLoginObject)

        [catches are here]

        End Try

    End Sub

End Class

In addition to creating the proxy, the constructor above adds an endpoint behavior to it. That behavior is defined in the class shown next. The purpose of this behavior is to add a custom message inspector to inject authorization information into the HTTP header before the message is sent out:

Imports System.ServiceModel.Description

Public Class MessageEndPointBehavior
    Implements IEndpointBehavior
    ' This class is used to make our custom message inspector available to the system.

    Public Sub AddBindingParameters(ByVal endpoint As System.ServiceModel.Description.ServiceEndpoint, ByVal bindingParameters As System.ServiceModel.Channels.BindingParameterCollection) Implements System.ServiceModel.Description.IEndpointBehavior.AddBindingParameters
        ' Not Implemented
    End Sub

    Public Sub ApplyClientBehavior(ByVal endpoint As System.ServiceModel.Description.ServiceEndpoint, ByVal clientRuntime As System.ServiceModel.Dispatcher.ClientRuntime) Implements System.ServiceModel.Description.IEndpointBehavior.ApplyClientBehavior
        ' Add our custom message inspector to the client runtime list of message inspectors.
        clientRuntime.MessageInspectors.Add(New MessageInspector())
    End Sub

    Public Sub ApplyDispatchBehavior(ByVal endpoint As System.ServiceModel.Description.ServiceEndpoint, ByVal endpointDispatcher As System.ServiceModel.Dispatcher.EndpointDispatcher) Implements System.ServiceModel.Description.IEndpointBehavior.ApplyDispatchBehavior
        ' Not Implemented
    End Sub

    Public Sub Validate(ByVal endpoint As System.ServiceModel.Description.ServiceEndpoint) Implements System.ServiceModel.Description.IEndpointBehavior.Validate
        ' Not Implemented
    End Sub
End Class

The last piece of code is the custom message inspector itself:

Imports System.ServiceModel.Dispatcher
Imports System.ServiceModel.Channels
Imports System.Configuration.ConfigurationManager
Imports System.Text

Public Class MessageInspector
    Implements IClientMessageInspector
    ' This class gives access to the outgoing SOAP message before it is sent so it can
    ' be customized.

    Private mUserName As String
    Private mPassword As String
    Private mErrText As String

    Public Sub New()
        Dim CredentialsProvided As Boolean

        CredentialsProvided = False
        mUserName = AppSettings("CliCredUserName")
        If Not mUserName Is Nothing Then
            If mUserName.Trim <> "" Then
                CredentialsProvided = True
            End If
        End If

        If CredentialsProvided Then
            CredentialsProvided = False
            mPassword = AppSettings("CliCredPassword")
            If Not mPassword Is Nothing Then
                If mPassword.Trim <> "" Then
                    CredentialsProvided = True
                End If
            End If
        End If

        If CredentialsProvided Then
            mUserName = mUserName.Trim
            mPassword = mPassword.Trim
        Else
            Throw New Exception("This class (MessageInspector) requires information from the app.config file - specifically " _
            & "AppSettings values for CliCredUserName and CliCredPassword. One or both of these is missing. ")
        End If

    End Sub

    Public Sub AfterReceiveReply(ByRef reply As System.ServiceModel.Channels.Message, ByVal correlationState As Object) Implements System.ServiceModel.Dispatcher.IClientMessageInspector.AfterReceiveReply
        ' Not Implemented
    End Sub

    Public Function BeforeSendRequest(ByRef request As System.ServiceModel.Channels.Message, ByVal channel As System.ServiceModel.IClientChannel) As Object Implements System.ServiceModel.Dispatcher.IClientMessageInspector.BeforeSendRequest

        Dim HTTPMsgHdr As HttpRequestMessageProperty
        Dim objHTTPRequestMsg As Object = Nothing
        Dim Auth As String = ""
        Dim Action As String = ""
        Dim BinaryData As Byte()

        Try
            Action = "Checking HTTP headers. "
            If request.Properties.TryGetValue(HttpRequestMessageProperty.Name, objHTTPRequestMsg) Then
                Action = "Changing existing HTTP header. "
                HTTPMsgHdr = CType(objHTTPRequestMsg, HttpRequestMessageProperty)
                If Not HTTPMsgHdr Is Nothing Then
                    If String.IsNullOrEmpty(HTTPMsgHdr.Headers("AUTHORIZATION")) Then
                        Auth = mUserName & ":" & mPassword
                        ReDim BinaryData(Auth.Length)
                        BinaryData = Encoding.UTF8.GetBytes(Auth)
                        Auth = Convert.ToBase64String(BinaryData)
                        Auth = "Basic " & Auth
                        HTTPMsgHdr.Headers("AUTHORIZATION") = Auth
                    End If
                Else
                    Throw New Exception("Received unexpected empty object HTTPMsgHdr from request properties. " _
                    & "This error occurred in class ""MessageInspector"" and function ""BeforeSendRequest."" ")
                End If
            End If

        Catch ex As Exception
            mErrText = "Error caught in BeforeSendRequest function of MessageInspector class: Action = " _
            & Action & "; Message = " & ex.Message & " "
            If Not ex.InnerException Is Nothing Then
                mErrText &= "Additional Information: " & ex.InnerException.ToString & " "
            End If
            Throw New Exception(mErrText)
        End Try

        Return Convert.DBNull

    End Function
End Class

Finally, here is the config file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
   <configSections>
   </configSections>
   <appSettings>
      <!-- Client Credentials -->
      <add key="CliCredUserName" value="xxxxxxx"/>
      <add key="CliCredPassword" value="xxxxxxx"/>

      <!-- Login Object Fields -->
      <add key="ClinicID" value="xxxxx"/>
      <add key="CorporateID" value="x"/>
      <add key="Password" value="xxxxx"/>
      <add key="UserName" value="xxxx"/>

    </appSettings>

   <system.serviceModel>
      <diagnostics>
         <messageLogging logEntireMessage="false" logMalformedMessages="false"
            logMessagesAtServiceLevel="false" logMessagesAtTransportLevel="false" />
      </diagnostics>
      <bindings>
         <basicHttpBinding>
            <binding name="ZoasisGroupServicesPort" closeTimeout="00:01:00"
               openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
               allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
               maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
               messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
               useDefaultWebProxy="true">
               <readerQuotas maxDepth="32" maxStringContentLength="118192" maxArrayLength="16384"
                  maxBytesPerRead="4096" maxNameTableCharCount="16384" />
               <security mode="Transport">
                  <transport clientCredentialType="None" proxyCredentialType="None"
                     realm="" />
                  <message clientCredentialType="UserName" algorithmSuite="Default" />
               </security>
            </binding>
         </basicHttpBinding>
      </bindings>
      <client>
         <endpoint address="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
            binding="basicHttpBinding" bindingConfiguration="ZoasisGroupServicesPort"
            contract="AntechServiceReference.ZoasisGroupServicesPort" name="ZoasisGroupServicesPort" />
      </client>
   </system.serviceModel>
    <system.net>
        <!-- Important: The following setting strips off the "HTTP/1.1 100 Continue" banner from incoming 
             messages. Unless this is done, incoming XML messages are not recognized as XML. -->
        <settings>
            <servicePointManager expect100Continue="false"/>
        </settings>
    </system.net>
</configuration>

As I mentioned earlier, this is a functioning WCF client that successfully calls the service and downloads data - but only when run within Visual Studio, which is the part I don't understand.

Was it helpful?

Solution

This is what I had to do to fix this problem. In the BeforeSendRequest function of the MessageInspector class, I had to add the code indicated below (i.e. the lines between the rows of exclamation points - !!!!!!)

Action = "Checking HTTP headers. "
If request.Properties.TryGetValue(HttpRequestMessageProperty.Name, objHTTPRequestMsg) Then
   Action = "Changing existing HTTP header. "
   HTTPMsgHdr = CType(objHTTPRequestMsg, HttpRequestMessageProperty)
   If Not HTTPMsgHdr Is Nothing Then
      If String.IsNullOrEmpty(HTTPMsgHdr.Headers("AUTHORIZATION")) Then
         Auth = mUserName & ":" & mPassword
         ReDim BinaryData(Auth.Length)
         BinaryData = Encoding.UTF8.GetBytes(Auth)
         Auth = Convert.ToBase64String(BinaryData)
         Auth = "Basic " & Auth
         HTTPMsgHdr.Headers("AUTHORIZATION") = Auth
      End If
   Else
      Throw New Exception("Received unexpected empty object HTTPMsgHdr from request properties. " _
         & "This error occurred in class ""MessageInspector"" and function ""BeforeSendRequest."" ")
   End If
' !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
' Added this section
Else
   Action = "Creating new HTTP header. "
   HTTPMsgHdr = New HttpRequestMessageProperty
   If String.IsNullOrEmpty(HTTPMsgHdr.Headers("AUTHORIZATION")) Then
      Auth = mUserName & ":" & mPassword
      ReDim BinaryData(Auth.Length)
      BinaryData = Encoding.UTF8.GetBytes(Auth)
      Auth = Convert.ToBase64String(BinaryData)
      Auth = "Basic " & Auth
      HTTPMsgHdr.Headers("AUTHORIZATION") = Auth
   End If
   request.Properties.Add(HttpRequestMessageProperty.Name, HTTPMsgHdr) 
' End of Added section
' !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
End If

For some reason still not clear to me, when I run the application as an executable, the "HttpRequestMessageProperty.Name" property does not exist in the request.properties that are passed into the "BeforeSendRequest" function. I have to explicitly create it - unlike when I run the application in Debug mode in Visual Studio. (Thanks to Mike Parkhill, for suggesting that the "If" conditions might not be executing as I expected. It turns out I needed an extra ELSE clause as shown above.)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top