Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle/parse Faults for a WCF Rest called using WebClient

I'm interested in properly handling Faults within a WCF REST Service client. While using any of WebClient, WebRequest, or HttpWebRequest like so:

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

I can see in Fiddler that I am getting a nicely formatted XML "Fault" message (either the default because includeExceptionDetailInFaults=true, or a custom fault via IErrorHandler::ProvideFault). However, only a 500 Internal error WebException is being thrown.

I would rather get an FaultException thrown on the client or at least be able to parse the Fault. We are not using "Service reference" so there is no proxy (please correct me if there is a better way to do this for a REST WCF client). Is there a generic way to parse that fault regardless of its actual type T (FaultException) or even for specific type as starting point? Thanks!

Building on answer from 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;
}
like image 273
crokusek Avatar asked Jan 12 '12 01:01

crokusek


1 Answers

Faults are part of SOAP protocol and are not available in REST scenarios. I don't believe any of the WCF infrastructure supports what you are doing out of the box.

You can set FaultExceptionEnabled=true in your WebHttp behavior configuration to get a FaultException rather than 500 error.

However, you can also do something like this (I have done this in some testing scenarios). This method relies on knowing ahead of time what kind of FaultDetail to expect in the fault.

        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;
        }
like image 197
Phil Degenhardt Avatar answered Oct 23 '22 06:10

Phil Degenhardt