VB.NET2008でWindowsフォームアプリケーションとして記述されたWCFクライアントアプリケーションがあります。このクライアントアプリは、別の会社が管理しているリモートの非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ファイルはどちらの場合も同じです。
ありがとう。
マイクのリクエストにより、ここにいくつかの詳細情報があります。クライアントプロキシは、VisualStudio2008の「サービス参照の追加」を使用して作成されました。
サービスに送信されるメッセージを作成するコードは、以下の3つの部分で示されています。
最初の部分は、AntechServiceReferenceと呼ばれるクラスです。これには2つの関連する方法があります。そのコンストラクターは、Webサービスとの対話に使用されるプロキシーを構築します。GetPendingDownloadsと呼ばれるもう1つのメソッドは、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内で実行した場合にのみ、理解できません。