2

我有一个用 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 部分- 从 <HttpRequest> 到 </HttpRequest> 的所有内容

谁能告诉我为什么在 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 中运行时,这是我不理解的部分。

4

1 回答 1

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 子句,如上所示。)

于 2012-10-09T20:36:06.733 回答