5

我对正确处理 WCF REST 服务客户端中的故障感兴趣。像这样使用任何 WebClient、WebRequest 或 HttpWebRequest 时:

   try 
   {
      HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(uri);
      req.Method = "GET";
      HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
      // ...process...
   } 
   catch (WebException wex)
   {
      string exMessage = wex.Message;
      if (wex.Response != null)
      {
         using (StreamReader r = new StreamReader(wex.Response.GetResponseStream()))
            exMessage = r.ReadToEnd();

         // the fault xml is available here, really need to parse? and how?
      }
   }

我可以在 Fiddler 中看到我收到了格式良好的 XML“故障”消息(默认是因为 includeExceptionDetailInFaults=true,或者是通过 IErrorHandler::ProvideFault 的自定义故障)。但是,仅抛出 500 内部错误 WebException。

我宁愿在客户端上抛出一个 FaultException,或者至少能够解析故障。我们没有使用“服务参考”,因此没有代理(如果有更好的方法来为 REST WCF 客户端执行此操作,请纠正我)。不管它的实际类型 T (FaultException) 甚至是特定类型作为起点,是否有一种通用的方法来解析该故障?谢谢!

基于 degorolls 的回答:

public SomeContract ThrowErrorTest()
{
    try
    {
        return TryCatchExtractAndRethrowFaults<SomeContract>(() =>
        {
            // Call web service using WebClient, HttpWebRequest, etc.
            return SomeContract;
        });                
    }
    catch (FaultException<CustomFault> fexCustom)
    {
        Dbg.WriteLine(fexCustom.Message);
    }
    catch (FaultException fex)
    {
        Dbg.WriteLine(fex.Message);
    }
    catch (WebException wex)
    {
        Dbg.WriteLine(wex.Message);
    }
    catch (Exception ex)
    {
        Dbg.WriteLine(ex.Message);
    }
    return null;
}        

static public T TryCatchExtractAndRethrowFaults<T>(Func<T> doWebRequest)
{
     try
     {
         return doWebRequest();
     }
     catch (WebException wex)
     {
         FaultException fe = ConvertWebExceptionIntoFault(wex);
         if (fe != null)
             throw fe;
         throw;      // not a fault, just re-throw
     }
 }

 static protected FaultException ConvertWebExceptionIntoFault(WebException wex)
 {
     if (wex.Response == null)
         return null;

     XmlDictionaryReader xdr = XmlDictionaryReader.CreateTextReader(
         wex.Response.GetResponseStream(),
         new XmlDictionaryReaderQuotas());

     Message msg = Message.CreateMessage(MessageVersion.None, "ParseFaultException", xdr);

     // If the start element of the message is "Fault" convert it into a FaultException
     //
     using (MessageBuffer msgBuffer = msg.CreateBufferedCopy(65536))
         using (Message msgCopy = msgBuffer.CreateMessage())
             using (XmlDictionaryReader reader = msgCopy.GetReaderAtBodyContents())
                 if (reader.IsStartElement("Fault"))
                 {
                     // Must make a copy for the converter
                     msg.Close();
                     msg = msgBuffer.CreateMessage();
                     return ConvertMessageToFault(msg);
                 }

     return null;
}

static FaultException ConvertMessageToFault(Message msg)
{
    EnvelopeVersion ev = msg.Version.Envelope;
    var fault = MessageFault.CreateFault(msg, 65536);

    if (fault.HasDetail)
    {
        string faultName = fault.GetReaderAtDetailContents().Name;
        switch (faultName)
        {
            case "ExceptionDetail": // handle the default WCF generated fault 
                ExceptionDetail exDetail = fault.GetDetail<ExceptionDetail>();
                return new FaultException<ExceptionDetail>(exDetail, fault.Reason, fault.Code);

            case "CustomFault":     // handle custom faults
                CustomFault cstmDetail = fault.GetDetail<CustomFault>();
                return new FaultException<CustomFault>(cstmDetail, fault.Reason, fault.Code);

            default:
                throw new Exception("Unrecognized fault detail '" + faultName + 
                                    "' while re-constructing fault.");
        }
    }
    return null;
}
4

1 回答 1

6

故障是 SOAP 协议的一部分,在 REST 场景中不可用。我不相信任何 WCF 基础结构都支持您开箱即用的操作。

您可以在 WebHttp 行为配置中设置 FaultExceptionEnabled=true 以获得 FaultException 而不是 500 错误。

但是,您也可以这样做(我在某些测试场景中已经这样做了)。此方法依赖于提前了解故障中预期的 FaultDetail 类型。

        bool isFault;
        if (message.Version == MessageVersion.None)
        {
            //Need to determine for ourselves if this is a fault;
            using (MessageBuffer buffer = message.CreateBufferedCopy(65536))
            {
                message.Close();
                message = buffer.CreateMessage();
                using (Message message2 = buffer.CreateMessage())
                {
                    using (XmlDictionaryReader reader = message2.GetReaderAtBodyContents())
                    {
                        isFault = reader.IsStartElement("Fault", "http://schemas.microsoft.com/ws/2005/05/envelope/none");
                    }
                }
            }
        }
        else
        {
            // For SOAP messages this is done for us
            isFault = message.IsFault;
        }

        if (isFault)
        {
            var fault = MessageFault.CreateFault(message, 65536);
            MyServiceFault detail = null;
            if (fault.HasDetail)
            {
                // The only thing we can possible have as detail is an MyServiceFault
                detail = fault.GetDetail<MyServiceFault>();
            }
            FaultException ex = new FaultException<MyServiceFault>(detail, fault.Reason, fault.Code);
            throw ex;
        }
于 2012-01-12T05:57:48.727 回答