Reputation: 11095
I'm calling a SOAP web service from .NET 4.5 using C#, and I can't understand how to catch the detail of an untyped FaultException
.
If the web service experiences an error, I get a message like this one:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<S:Fault xmlns:ns4="http://www.w3.org/2003/05/soap-envelope">
<faultcode>GSX.SYS.003</faultcode>
<faultstring>Multiple error messages exist. Please check the detail section.</faultstring>
<detail>
<operationId>HqrCoRMbtKczWFH2WMuFJGe</operationId>
<errors>
<error>
<code>ENT.UPL.005</code>
<message>User ID is required for authentication.</message>
</error>
<error>
<code>ENT.UPL.005</code>
<message>Password is required for authentication.</message>
</error>
<error>
<code>ENT.UPL.005</code>
<message>Sold-To is required for authentication.</message>
</error>
</errors>
</detail>
</S:Fault>
</S:Body>
</S:Envelope>
To catch the error, I wrap the call in a try/catch block and catch the FaultException
:
try
{
// Do something.
}
catch (FaultException faultException)
{
var messageFault = faultException.CreateMessageFault();
// ???
throw;
}
The problem lies in the // ???
line: how can I access the detail of the FaultException
, given that I can't use messageFault.GetDetail<T>()
because the detail is untyped?
For the code and the reason I don't have a problem, I get them with messageFault.Code.Name
and messageFault.Reason.Translations.Single().Text
.
What I've been able to concoct is this:
var stringWriter = new StringWriter();
var xmlTextWriter = new XmlTextWriter(stringWriter);
var messageFault = faultException.CreateMessageFault();
messageFault.WriteTo(xmlTextWriter, EnvelopeVersion.Soap12);
var stringValue = Convert.ToString(stringWriter);
var nameTable = new NameTable();
var xmlNamespaceManager = new XmlNamespaceManager(nameTable);
xmlNamespaceManager.AddNamespace("soap", "http://www.w3.org/2003/05/soap-envelope");
var xmlDocument = XDocument.Parse(stringValue);
var operationId =
xmlDocument
.XPathSelectElement("/soap:Fault/soap:Detail/operationId", xmlNamespaceManager)
.Value;
var errors =
xmlDocument
.XPathSelectElements("/soap:Fault/soap:Detail/errors/error", xmlNamespaceManager)
.Select(element => new
{
Code = element.XPathSelectElement("code").Value,
Message = element.XPathSelectElement("message").Value
})
.ToArray();
But null reference and encoding issues aside, it's a monster.
There must be a saner way to get the details.
Upvotes: 6
Views: 6006
Reputation: 563
Here is some code that emulate how SoapException could provide SoapException.Detail.OuterXml:
private string GetDetailOuterXml(MessageFault mf)
{
StringBuilder sb = new StringBuilder();
sb.Append("<detail>");
using var r = mf.GetReaderAtDetailContents();
while (!r.EOF)
sb.Append(r.ReadOuterXml());
sb.Append("</detail>");
return sb.ToString();
}
Upvotes: 0
Reputation: 1
Based on the following response:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<s:Fault>
<faultcode xmlns:a="http://schemas.microsoft.com/net/2005/12/windowscommunicationfoundation/dispatcher">a:InternalServiceFault</faultcode>
<faultstring>One or more errors have occurred.</faultstring>
<detail>
<ExceptionDetail xmlns="http://schemas.datacontract.org/2004/07/System.ServiceModel" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<HelpLink i:nil="true"/>
<InnerException>
<HelpLink i:nil="true"/>
<InnerException i:nil="true"/>
<Message>...</Message>
<StackTrace>...</StackTrace>
<Type>System.ArgumentOutOfRangeException</Type>
</InnerException>
<Message>One or more errors have occurred.</Message>
<StackTrace>...</StackTrace>
<Type>System.AggregateException</Type>
</ExceptionDetail>
</detail>
</s:Fault>
</s:Body>
</s:Envelope>
You can access the inner exception with this code:
var messageFault = ex.CreateMessageFault();
var detail = messageFault.GetDetail<ExceptionDetail>();
var innerException = detail.InnerException;
In your case the body of detail should be serializable to get the data.
Upvotes: 0
Reputation: 131712
You can read the detail as an XML type, eg. XElement or XmlElement and process it using XPath, Linq to XML or just treat it as string.
In a similar situation I use the following code to get at the contents of an admittedly simple details element:
var detail = fault.GetDetail<XElement>();
return detail.Value;
Upvotes: 2