我有一个用 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 中运行时,这是我不理解的部分。