2

私は WCF を使い始めたばかりで、JSON と XML の両方をサポートする WCF レスト サービスで障害処理を検証しようとしています。私のテスト サービスはエラーを生成しますが、何を試しても、クライアントにエラーの詳細を取得させることができません (動作は、要求の形式と HTTP ステータス コードによって異なります)。

私のテスト サービスは、次のようにエラーを生成します。

public Data GetResponse()
{
    throw new WebFaultException<ErrorDetails>(
        new ErrorDetails {ErrorMessage = "Server Config Value not set"},
        HttpStatusCode.OK
        );
}

これは非常に合理的にネットワークを介して送信されます。

{"ErrorMessage":"Server Config Value not set"}

と:

<ErrorDetails xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <ErrorMessage>Server Config Value not set</ErrorMessage>
</ErrorDetails>

私のクライアントは次のように定義されていますFaultContract:

[OperationContract]
[WebInvoke(
    UriTemplate="/response",
    Method="GET",
    RequestFormat = WebMessageFormat.Xml, // or .Json
    ResponseFormat = WebMessageFormat.Xml // or .Json
    )]
[FaultContract(typeof(ErrorDetails), Namespace="")]
Data GetResponse();

(フォーマット/ステータス コード) の完全なエラー メッセージは次のとおりです。

XML/競合:

応答を要求しています CommunicationException: リモート サーバーが予期しない応答を返しました: (409) Conflict., System.Collections.ListDictionary Internal, System.ServiceModel.ProtocolException Press a key to exit...

XML/OK:

要求応答例外: DataContractSerializer を使用して、ルート名 'ErrorDetails' およびルート名前空間 '' (操作 'GetResponse' および d コントラクト ('IClient', '') の場合) を持つ XML 本体を逆シリアル化できません。XML に対応するタイプが、サービスの既知の n タイプ コレクションに追加されていることを確認してください。System.Collections.ListDictionaryInternal Press a key to exit...

JSON/競合:

応答を要求しています CommunicationException: リモート サーバーが予期しない応答を返しました: (409) Conflict., System.Collections.ListDictionary Internal, System.ServiceModel.ProtocolException Press a key to exit...

JSON/OK:

応答を要求しています 応答: 要求が完了しました 終了するにはキーを押してください...

クライアント コードは、適切な順序で例外をキャッチします。

try
{
    Console.WriteLine("Requesting response");
    Console.WriteLine("Response: " + client.GetResponse().Message);
    Console.WriteLine("Request complete");
}
// sanity check, just in case...
catch (WebFaultException<ErrorDetails> ex)
{
    Console.WriteLine("WebFaultException<ErrorDetails>: " + ex.Detail.ErrorMessage + ", " + ex.Reason);
}
catch (FaultException<ErrorDetails> ex)
{
    Console.WriteLine("FaultException<ErrorDetails>: " + ex.Detail.ErrorMessage + ", " + ex.Reason);
}
catch (FaultException ex)
{
    Console.WriteLine("FaultException: " + ex.Message + ", " + ex.Reason);
}
catch (CommunicationException ex)
{
    Console.WriteLine("CommunicationException: " + ex.Message + ", " + ex.Data + ", " + ex.GetType().FullName);
}
catch (Exception ex)
{
    Console.WriteLine("Exception: " + ex.Message + ", " + ex.Data);
}

FaultException<ErrorDetails>スローされてにアクセスできるようにするには、どうすればよいErrorDetailsですか?

注: Gistは完全にコンパイル可能で実行可能でなければなりません。

4

2 に答える 2

4

WCF REST クライアント (WCF バージョン 4.0 以降) サービスのエラー処理メカニズムとして Faults/FaultContract を使用することはできません。

WCFリストの1 つのスレッドあたり:

WCF Rest サービスには SOAP メッセージがないため、クライアントに FaultException を返すことはできません。実際には、適切なステータス コードが HTTP ヘッダーとしてリクエスタに返され、リクエスタが呼び出しの結果を判断できるようになります。

そして、StackOverflow の投稿から:

障害は SOAP プロトコルの一部であり、REST シナリオでは使用できません

考慮されるオプション

FaultContract - FaultContractAttributeSOAP エンベロープでは機能しますが、WCF REST サービスでは機能しません。SOAP エンベロープとしてフォーマットされた XML を送信すると、動作する可能性がありますが、妥当なカスタム エラー メッセージを使用していません。

IErrorHandler - ドキュメントをざっと読むと、これはサービス用であり、初心者向けのエラーであることがわかります。

Message Inspectors -が最初にスローされるAfterReceiveRequestため、 は実行されません。CommunicationException

回避策

かなり掘り下げた後、例外オブジェクトに埋め込まれた応答ストリームから情報を抽出するためのヘルパー クラスの作成を推奨する1 人のブロガーを見つけました。彼の実装は次のとおりです。

public static void HandleRestServiceError(Exception exception, Action<TServiceResult> serviceResultHandler, Action<TServiceFault> serviceFaultHandler = null, Action<Exception> exceptionHandler = null)
{
  var serviceResultOrServiceFaultHandled = false;

  if (exception == null) throw new ArgumentNullException("exception");
  if (serviceResultHandler == null) throw new ArgumentNullException("serviceResultHandler");

  // REST uses the HTTP procol status codes to communicate errors that happens on the service side.
  // This means if we have a teller service and you need to supply username and password to login
  // and you do not supply the password, a possible scenario is that you get a 400 - Bad request.
  // However it is still possible that the expected type is returned so it would have been possible 
  // to process the response - instead it will manifest as a ProtocolException on the client side.
  var protocolException = exception as ProtocolException;
  if (protocolException != null)
  {
    var webException = protocolException.InnerException as WebException;
    if (webException != null)
    {
      var responseStream = webException.Response.GetResponseStream();
      if (responseStream != null)
      {
        try
        {
          // Debugging code to be able to see the reponse in clear text
          //SeeResponseAsClearText(responseStream);

          // Try to deserialize the returned XML to the expected result type (TServiceResult)
          var response = (TServiceResult) GetSerializer(typeof(TServiceResult)).ReadObject(responseStream);
          serviceResultHandler(response);
          serviceResultOrServiceFaultHandled = true;
        }
        catch (SerializationException serializationException)
        {
          // This happens if we try to deserialize the responseStream to type TServiceResult
          // when an error occured on the service side. An service side error serialized object 
          // is not deserializable into a TServiceResult

          // Reset responseStream to beginning and deserialize to a TServiceError instead
          responseStream.Seek(0, SeekOrigin.Begin);

          var serviceFault = (TServiceFault) GetSerializer(typeof(TServiceFault)).ReadObject(responseStream);

          if (serviceFaultHandler != null && serviceFault != null)
          {
            serviceFaultHandler(serviceFault);
            serviceResultOrServiceFaultHandled = true;
          }
          else if (serviceFaultHandler == null && serviceFault != null)
          {
            throw new WcfServiceException<TServiceFault>() { ServiceFault = serviceFault };
          }
        }
      }
    }
  }

  // If we have not handled the serviceResult or the serviceFault then we have to pass it on to the exceptionHandler delegate
  if (!serviceResultOrServiceFaultHandled && exceptionHandler != null)
  {
    exceptionHandler(exception);
  }
  else if (!serviceResultOrServiceFaultHandled && exceptionHandler == null)
  {
    // Unable to handle and no exceptionHandler passed in throw exception to be handled at a higher level
    throw exception;
  }
}
于 2012-08-30T22:20:39.573 に答える