2

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内で実行した場合にのみ、理解できません。

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でデバッグモードでアプリケーションを実行する場合とは異なり、明示的に作成する必要があります。(「If」条件が期待どおりに実行されない可能性があることを示唆してくれたMike Parkhillに感謝します。上記のように追加のELSE句が必要であることがわかりました。)

于 2012-10-09T20:36:06.733 に答える