如何处理/解析使用WebClient调用的WCF Rest的故障



我对在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"Fault"消息(要么是默认的,因为includeExceptionDetailInFaults=true,要么是通过IErrorHandler:ProviderFault的自定义错误)。但是,只抛出了一个500内部错误WebException。

我宁愿在客户端上抛出FaultException,或者至少能够解析Fault。我们没有使用"服务引用",因此没有代理(如果有更好的方法可以为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;
}

故障是SOAP协议的一部分,在REST场景中不可用。我不相信任何WCF基础设施支持您开箱即用的功能。

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

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

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

最新更新