我有一个用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中运行时,这是我不理解的部分。
答案 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子句,如上所示。)