Add detail to SoapException

Topics: General Discussion Forum, Service Factory Modeling Edition Forum
Jan 15, 2008 at 4:22 PM
Hi,

In the generated service in the XXX.ServiceImplementation namespace (asmx), there is a commented note telling that "The Fault associated to this operation should be returned serialized into a SoapException, ...".

I followed that advice and used:

ServiceFault fault = new ServiceFault();
fault.Message = "The web services application has not started correctly. Check the log file on the web server.";
throw GenerateSoapException("Login error", fault);

For what I've read on the web, the XmlNode resulting of the fault class serialization can then be retrieved in the detail field of the SoapException. Unfortunately, I noted that the detail field is null when I catched the exception on a web page I created for test.

My ServiceFault class contains just a Message property.

The GenerateSoapException method is:

public static SoapException GenerateSoapException(string message, object fault) {
XmlSerializer serializer = new XmlSerializer(fault.GetType());
XmlDocument doc = new XmlDocument();
MemoryStream stream = new MemoryStream();
using (XmlTextWriter writer = new XmlTextWriter(stream, Encoding.Default)) {
serializer.Serialize(writer, fault);
stream.Position = 0;
doc.Load(stream);
}

Have I missed something? Suggestions?
Thanks
Developer
Jan 15, 2008 at 7:27 PM
Did you add the last line with the return statement?

return new SoapException(message, SoapException.ServerFaultCode, "", (XmlNode)doc.DocumentElement);
CF.
Jan 16, 2008 at 7:58 AM

charlyfriend wrote:
Did you add the last line with the return statement?
return new SoapException(message, SoapException.ServerFaultCode, "", (XmlNode)doc.DocumentElement);
CF.

Sorry my fault, yes I added the return statement:
return new SoapException(message, SoapException.ServerFaultCode, "", (XmlNode)doc.DocumentElement);
What could I be doing wrong?
Developer
Jan 17, 2008 at 2:41 PM
Are you getting back the error massage "Login Error" that you set in the SoapException or you are getting another error? Perhaps you got another error that overlaps the original message and therefore the Detail property is being overwritten.
Jan 17, 2008 at 4:34 PM
Yes I can see the message "Login error" in the SoapException. All seem quite normal except that detail property is null.
I verified this by debugging a test web client application.
Developer
Jan 17, 2008 at 5:32 PM
Try updating the return line with these new lines and return statement:

XmlNode detailNode = doc.CreateNode(XmlNodeType.Element, "detail", "");
detailNode.AppendChild((XmlNode)doc.DocumentElement);
return new SoapException(message, SoapException.ServerFaultCode, "Operation1", detailNode);
That should wrap the serialized type with <detail> element and therefore may be decoded by the SoapException deserializr/handler process.
Jan 18, 2008 at 9:36 AM
Yes it works! :-)

I added the code as you told me, and on the client side, the SoapException has now a non null Detail property of type System.Xml.XmlElement. When debugging I could see the following members in Detail:
+		[System.Xml.XmlElement]	{Element, Name="detail"}
+		Attributes	{System.Xml.XmlAttributeCollection}
		BaseURI	""
+		ChildNodes	{System.Xml.XmlChildNodes}
+		FirstChild	{Element, Name="ServiceFault"}
		HasChildNodes	true
		InnerText	"The web services application has not started correctly. Check the log file on the web server."
		InnerXml	"<ServiceFault xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns=\"http://polyright.com/pws/consultation/types/2008/01\"><Message>The web services application has not started correctly. Check the log file on the web server.</Message></ServiceFault>"
		IsReadOnly	false
+		LastChild	{Element, Name="ServiceFault"}
		LocalName	"detail"
		Name	"detail"
		NamespaceURI	""
		NextSibling	null
		NodeType	Element
		OuterXml	"<detail><ServiceFault xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns=\"http://polyright.com/pws/consultation/types/2008/01\"><Message>The web services application has not started correctly. Check the log file on the web server.</Message></ServiceFault></detail>"
+		OwnerDocument	{Document}
		ParentNode	null
		Prefix	""
		PreviousSibling	null
+		SchemaInfo	{System.Xml.XmlName}
		Value	null
+		Non-Public members

I can retrieve the fault message by writing: se.Detail.InnerText, where se is my SoapException object.

Now I made another test, I added a Code property in my ServiceFault class:
        [XmlElement(ElementName = "Code", IsNullable = false)]
        public string Code {
            get { return code; }
            set { code = value; }
        }
Its value was: fault.Code = "ERR-CODE 123";

The SoapException detail property contains now the following items:
InnerText	"The web services application has not started correctly. Check the log file on the web server.ERR-CODE 123"
InnerXml	"<ServiceFault xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns=\"http://polyright.com/pws/consultation/types/2008/01\"><Message>The web services application has not started correctly. Check the log file on the web server.</Message><Code>ERR-CODE 123</Code></ServiceFault>"

Note that the Code value is appended to the Message resulting in a less useful InnerText.
The only formatting is in InnerXML where the message is in a <Message> tag and code is in a <Code> tag.

This means that there will be more work in order to retrieve the distinct values by parsing the InnerXml property.
Any suggestions?

Thank you very much for your help.
Developer
Jan 18, 2008 at 3:08 PM
Edited Jan 18, 2008 at 3:24 PM
If you want to get your original type serialized inside the Detail property then you can 'reverse' the process that you did on the server side and 'deserialize' the type with all the stored values in it. You can call this function to do that:

private T DeserializeDetail<T>(XmlNode detail)
{
    if (detail == null)
    {
        return default(T);
    }
    XmlSerializer serializer = new XmlSerializer(typeof(T));
    using (XmlNodeReader reader = new XmlNodeReader(detail.FirstChild))
    {
        return (T)serializer.Deserialize(reader);
    }
}
Now you can call this way, assuming you have a type named 'ServiceFault' and you already added a reference in your client to the library that have that type defined.

catch (SoapException se)
{
    ServiceFault fault = DeserializeDetail<ServiceFault>(se.Detail);
    // get stored data from fault.
}