c# - WCF:设置了FaultContract,但未导致REST 服务中捕获FaultException<T>

标签 c# xml json wcf rest

我刚刚开始使用 WCF,并尝试验证支持 JSON 和 XML 的 WCF Rest 服务中的错误处理。 My test service生成错误,但无论我尝试什么,我都无法得到 my client获取故障的详细信息(行为因请求格式和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/冲突:

Requesting response CommunicationException: The remote server returned an unexpected response: (409) Conflict., System.Collections.ListDictionary Internal, System.ServiceModel.ProtocolException Press a key to exit...

和 XML/OK:

Requesting response Exception: Unable to deserialize XML body with root name 'ErrorDetails' and root namespace '' (for operation 'GetResponse' an d contract ('IClient', '')) using DataContractSerializer. Ensure that the type corresponding to the XML is added to the know n types collection of the service., System.Collections.ListDictionaryInternal Press a key to exit...

和 JSON/冲突:

Requesting response CommunicationException: The remote server returned an unexpected response: (409) Conflict., System.Collections.ListDictionary Internal, System.ServiceModel.ProtocolException Press a key to exit...

和 JSON/OK:

Requesting response Response: Request complete Press a key to exit...

客户端代码以正确的顺序捕获异常:

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应该是完全可编译和可运行的。

最佳答案

无法在 WCF REST 客户端(从 WCF 版本 4.0 开始)服务中使用Faults/FaultContract 作为错误处理机制:

one thread在 WCF 列表中:

In WCF Rest service there is not SOAP message, so you can't return a FaultException to the client. Actually the appropriate status code is returned to the requestor as an HTTP header, allowing the requestor to determine the result of the call.

来自 StackOverflow post :

Faults are part of SOAP protocol and are not available in REST scenarios

考虑的选项

FaultContract - FaultContractAttribute 适用于 SOAP 信封,但不适用于 WCF REST 服务。如果您发送格式化为 SOAP 信封的 XML,您可能可以让它工作,但您没有使用合理的自定义错误消息。

IErrorHandler - 快速阅读文档表明这是针对服务的,并认为这是一个新手错误:“允许实现者控制返回给调用者的错误消息,并可选择执行自定义错误处理,例如日志记录。”

Message Inspectors - AfterReceiveRequest 不会执行,因为先抛出 CommunicationException

解决方法

经过大量挖掘后,我 found one blogger谁建议创建一个帮助器类来从嵌入异常对象的响应流中提取信息。这是他的实现,如下:

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;
  }
}

关于c# - WCF:设置了FaultContract,但未导致REST 服务中捕获FaultException<T>,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12015067/

相关文章:

c# - 中继器的项目命令事件未在链接按钮单击时触发

sql - Hive:使用大量小型 XML 文件的最佳方法

javascript - 将工作表添加到 Jquery 数据表的 excel 输出中

xml - 通过动态名称引用 XSLT 变量

javascript - 无法将数组从php输入到javascript

json - WCF - 泛型类的 DataMember 的自定义名称

c# - 计算两次之间相隔多少分钟

c# - 对于 C# 任务,在检查 .Result 之前是否需要 Wait()?

c# - 在功能区上重复使用图像会导致 XP 崩溃

javascript - 显示 JSON 数据中的图像?