在Visual Studio外部运行时,来自WCF客户端的传出消息不完整

时间:2012-09-21 23:10:21

标签: wcf-client

我有一个用VB.NET 2008编写的WCF客户端应用程序作为Windows窗体应用程序。此客户端应用程序成功与另一家公司维护的远程非WCF服务进行通信。问题是 - 只有在Visual Studio(VS2008)中运行客户端应用程序时,通信才会成功,而不是在作为构建的可执行文件运行时。当客户端应用程序作为可执行文件运行时,远程服务将返回以下消息:

“HTTP请求未经授权,客户端身份验证方案为'匿名'。从服务器收到的身份验证标头为''。远程服务器返回错误:(401)未经授权。”

深入挖掘,我注意到了这个错误的原因。当客户端应用程序在VS外运行时发送到远程服务的消息缺少在VS内部运行时包含的部分。应用程序在VS内部运行时发送的消息(即正常工作的消息)如下所示,敏感信息替换为“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>

当作为独立可执行文件运行时,客户端应用程序发送与上面相同的内容,除了缺少整个HttpRequest部分 - 来自&lt; HttpRequest&gt;的所有内容。到&lt; / HttpRequest&gt;

有人能告诉我为什么在Visual Studio外部运行客户端应用程序会导致消息的HttpRequest部分丢失吗? app.config文件在两种情况下都是相同的。

感谢。


根据迈克的要求,这里有更多信息。客户端代理是使用Visual Studio 2008中的“添加服务引用”创建的。

创建发送到服务的消息的代码在下面分三部分显示。

第一部分是一个名为AntechServiceReference的类。它有两个相关的方法。它的构造函数构建了用于与Web服务交互的代理。另一种名为GetPendingDownloads的方法调用Web服务方法。

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

除了创建代理之外,上面的构造函数还为其添加了端点行为。该行为在下面显示的类中定义。此行为的目的是添加自定义消息检查器以在发送消息之前将授权信息注入HTTP标头:

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

最后一段代码是自定义消息检查器本身:

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

最后,这是配置文件:

<?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>

正如我前面提到的,这是一个正常运行的WCF客户端,它成功调用服务并下载数据 - 但仅限于在Visual Studio中运行时,这是我不理解的部分。

1 个答案:

答案 0 :(得分:1)

这是解决这个问题所必须做的。在MessageInspector类的BeforeSendRequest函数中,我不得不添加下面指出的代码(即 感叹号行 - !!!!!!)

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

由于某些原因,我仍然不清楚,当我将应用程序作为可执行文件运行时,传递给“BeforeSendRequest”函数的request.properties中不存在“HttpRequestMessageProperty.Name”属性。我必须显式创建它 - 不像我在Visual Studio中以调试模式运行应用程序时。 (感谢Mike Parkhill建议“If”条件可能没有像我预期的那样执行。事实证明我需要一个额外的ELSE子句,如上所示。)