views:

622

answers:

3

I have an XML document in a database, which is the XML-serialized representation of an instance of a certain class Foo:

<?xml version="1.0" encoding="utf-16"?>
<Foo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  <StringProp>hello</StringProp>
  <IntProp>5</IntProp>
</Foo>

Suppose now I want to write an ASP.NET web service method that returns an instance of Foo, like so:

[WebMethod]
public Foo GimmeFoo()
{
}

The question: I want to return the serialized Foo, but without deserializing it first within the GimmeFoo() method (Foo in real life is fairly large, and it would feel silly to deserialize it from the DB to then have it reserialized automatically shortly thereafter by the SOAP fairies).

Is there any attribute I can slap on the web service (or some code I can write in its body) that allows me to send the instance of Foo without needing to deserialize it first?

+1  A: 

This presupposes that only a known client will call this service, as other languages may not be able to deserialize this xml file.

In my mind, if that is the case, then why not just have a REST service that will pass the data back, since you aren't using a web service correctly, IMO. For example, if I use GSOAP (in C) or PHP to serve as a client, would your xml file be useful, as I will need to deserialize it myself.

It may be a pain to deserialize and then have the web service reserialize, but IMO, that is the correct way, again, if you have to use a webservice.

Besides, you have a very strong coupling between the client and server if you pass back this serialized XML file, which is, again IMO, a bad design.

James Black
The XML file I have is serialized using System.Xml.Serialization.XmlSerializer, which is the same used by the SOAP infrastructure. Is there really no way to tell ASP.NET "look, this stuff is pre-serialized for you, just use it"?
Guido Domenici
You may want to look at this, for more info on XmlSerializer and differences between regular serialization and SOAP serialization.http://www.codeproject.com/KB/XML/Serialization_Samples.aspxIt would seem that you may need to use your own class to return the soap response, as I don't know of anyone of pregenerating the soap response and then return it.
James Black
A: 

You can return the XmlElement or XmlDocument types from the web service. Just return your serialized XML.

John Saunders
A: 

I found a way that might not be the most elegant, but gets the job done. The idea is to delegate all the work to a custom SoapExtension-derived class, and actually do nothing in the WebMethod itself - the WebMethod is just there as an endpoint for the call:

[WebMethod]
public Foo GimmeFoo()
{
     return null;
}

But here's the magic: you write a SoapExtension that intercepts all SOAP traffic and, when the SoapMessageStage.AfterSerialize stage arrives, you stick in there your already-serialized payload:

public class SerializationPassThrough : SoapExtension
{
    private Stream oldStream;
    private Stream newStream;

    // Other overrides...

    public override void ProcessMessage(SoapMessage message)
    {
        switch (message.Stage)
        {
            case SoapMessageStage.BeforeSerialize:
                // ...
                break;
            case SoapMessageStage.AfterSerialize:
                string newOutput = ReadPreCookedResponseFromDB();
                RewriteOutput(newOutput);
                break;
            case SoapMessageStage.BeforeDeserialize:
                // ...
                break;
            case SoapMessageStage.AfterDeserialize:
                // ...
                break;
            default:
                throw new Exception("invalid stage");
        }
    }

    private void RewriteOutput(string output)
    {
        newStream.Position = 0;
        StreamWriter sw = new StreamWriter(newStream);
        sw.Write(output);
        sw.Flush();
        newStream.Position = 0;
        Copy(newStream, oldStream);
        newStream.Position = 0;
    }

    private void Copy(Stream from, Stream to)
    {
        TextReader reader = new StreamReader(from);
        TextWriter writer = new StreamWriter(to);
        string toWrite = reader.ReadToEnd();

        writer.WriteLine(toWrite);
        writer.Flush();
    }

}

The final touch: you need to instruct the ASP.NET runtime to use your SoapExtension. You can do so via an attribute on the WebMethod, or in web.config (which is what I did):

<system.web>
  <webServices>
    <soapExtensionTypes>
       <add type="SoapSerializationTest.SerializationPassThrough, SoapSerializationTest" priority="1" />
    </soapExtensionTypes>
  </webServices>
<system.web>

The original code samples from which I derived these snippets are at http://msdn.microsoft.com/en-us/library/7w06t139(VS.85).aspx.

Guido Domenici