views:

769

answers:

2

In order to return useful information in SoapException.Detail for an asmx web service, I took an idea from WCF and created a fault class to contain said useful information. That fault object is then serialised to the required XmlNode of a thrown SoapException.

I'm wondering whether I have the best code to create the XmlDocument - here is my take on it:

var xmlDocument = new XmlDocument();
var serializer = new XmlSerializer(typeof(T));
using (var stream = new MemoryStream())
{
    serializer.Serialize(stream, theObjectContainingUsefulInformation);
    stream.Flush();
    stream.Seek(0, SeekOrigin.Begin);

    xmlDocument.Load(stream);
}

Is there a better way of doing this?

UPDATE: I actually ended up doing the following, because unless you wrap the XML in a <detail> xml element, you get a SoapHeaderException at the client end:

var serialiseToDocument = new XmlDocument();
var serializer = new XmlSerializer(typeof(T));
using (var stream = new MemoryStream())
{
    serializer.Serialize(stream, e.ExceptionContext);
    stream.Flush();
    stream.Seek(0, SeekOrigin.Begin);

    serialiseToDocument.Load(stream);
}

// Remove the xml declaration
serialiseToDocument.RemoveChild(serialiseToDocument.FirstChild);

// Memorise the node we want
var serialisedNode = serialiseToDocument.FirstChild;

// and wrap it in a <detail> element
var rootNode = serialiseToDocument.CreateNode(XmlNodeType.Element, "detail", "");
rootNode.AppendChild(serialisedNode);

UPDATE 2: Given John Saunders excellent answer, I've now started using the following:

private static void SerialiseFaultDetail()
{
    var fault = new ServiceFault
                    {
                        Message = "Exception occurred",
                        ErrorCode = 1010
                    };

    // Serialise to the XML document
    var detailDocument = new XmlDocument();
    var nav = detailDocument.CreateNavigator();

    if (nav != null)
    {
        using (XmlWriter writer = nav.AppendChild())
        {
            var ser = new XmlSerializer(fault.GetType());
            ser.Serialize(writer, fault);
        }
    }

    // Memorise and remove the element we want
    XmlNode infoNode = detailDocument.FirstChild;
    detailDocument.RemoveChild(infoNode);

    // Move into a root <detail> element
    var rootNode = detailDocument.AppendChild(detailDocument.CreateNode(XmlNodeType.Element, "detail", ""));
    rootNode.AppendChild(infoNode);

    Console.WriteLine(detailDocument.OuterXml);
    Console.ReadKey();
}
A: 

overall it looks good to me, though I think I'd use strongly typed objects rather than vars in this case.

Also, I don't know that the stream.Seak(0, SeekOrigin.Begin) is really necessary.

MasterMax1313
var is still strongly typed, just saves having to write out the full type.
Steven
Yes - "var" is *implicit typing*, not *weak typing*. The seek is necessary I found, because the steam is at the end and so not ready for the XmlDocument to start reading.
Neil Barnwell
Whilst it may save writing out the full type, it makes readability a potential nightmare. I wouldn't want to maintain someone else's code that is full of vars. It's also a problem when posting code on the net as you don't have intellisense to help you. Besides intellisense makes typing the full type barely any more than typing var.
tjmoore
+1  A: 

EDIT: Creates output inside of detail element

public class MyFault
{
    public int ErrorCode { get; set; }
    public string ErrorMessage { get; set; }
}

public static XmlDocument SerializeFault()
{
    var fault = new MyFault
                    {
                        ErrorCode = 1,
                        ErrorMessage = "This is an error"
                    };

    var faultDocument = new XmlDocument();
    var nav = faultDocument.CreateNavigator();
    using (var writer = nav.AppendChild())
    {
        var ser = new XmlSerializer(fault.GetType());
        ser.Serialize(writer, fault);
    }

    var detailDocument = new XmlDocument();
    var detailElement = detailDocument.CreateElement(
        "exc", 
        SoapException.DetailElementName.Name,
        SoapException.DetailElementName.Namespace);
    detailDocument.AppendChild(detailElement);
    detailElement.AppendChild(
        detailDocument.ImportNode(
            faultDocument.DocumentElement, true));
    return detailDocument;
}
John Saunders
+1 for being a really neat solution. However, given that I need to wrap the serialised object's XML in a root "<detail>" element, what's the best approach? I tried creating a node, then using the new node's navigator but got an InvalidOperationException with "WriteStartDocument cannot be called on writers created with ConformanceLevel.Fragment."
Neil Barnwell
Ahh, now I understand. Using the return value of ImportNode for the AppendChild call was the link in the chain I was missing. I really hate these messy XML classes in .net, I must say. Thanks for your help.
Neil Barnwell