views:

2397

answers:

4

I've got a web app that sends a request to a WCF service. The WCF service gets a LINQ resultset (anon. ilist) and sends that in reply, back to the web app. In order to get it working quickly, inside the WCF app, i'm using copytodatatable and sending it to my web app in a DataSet.

My web app then takes the DataSet and writes it to xml, some xslt is performed and the resulting data is shown on screen. Perfect. ... Well, not really.

I'm still (relatively) new to WCF. I understand that sending DataTables/DataSets is a bit cumbersome. The web app wants the data in xml format (for the xslt operation), so I figured I'd get the WCF web service to do the DataTable -> xml work for me, and simply reply with a nice XmlDocument to the client webapp.
However a XmlDocument cannot be serialized.

What's the best way of sending the XML data down to the client over wcf?

  • The datatable contains lots of columns, and will be changing quite freqently so i don't want to create my own object class (with like a bazillion properties) and send that (as this is what i've done most of the time previously in wcf, it works well... but isn't suited to this scenario).

  • EDIT: Sending it down as a string works too.... but this surely can't be the best solution?

A: 

Why don't you try to send xml as a string ?

ArsenMkrt
occurred to me, but i had just assumed that since the data was xml, that an xmldocument would be the best data-type.... i've already tried a couple of data types which do work, and my app does work. I'm just looking for the best, best practice way of doing this.
GordonB
A: 

The class returned by the method needs to implement IXmlSerializable.

public XmlContent XmlContent()
{
 return new XmlContent();
}

[XmlRoot(ElementName = "div")]
public class XmlContent : IXmlSerializable
{

 #region IXmlSerializable Members

 public System.Xml.Schema.XmlSchema GetSchema()
 {
  return null;
 }

 public void ReadXml(XmlReader reader)
 {
  throw new NotImplementedException();
 }

 public void WriteXml(XmlWriter writer)
 {

 writer.WriteRaw("<p>some text</p>");

 }
 #endregion
}

I still haven't found how to avoid serializing the root element (e.g., "div").

I tried sending XML as a string too, but it is not output as expected, that is, the "string" is HTML encoded and wrapped in tags. This make parsing very difficult.

Doug D
+3  A: 

The only problem with sending datasets over WCF is that it isn't interoperable - i.e. you wouldn't be able to use the dataset from a non-.NET client.

I've worked in several large applications that send Datasets across the wire via WCF just fine. As long as both the sender and receiver run on .NET you should be fine.

If you still don't want to use datasets, then consider creating a DataContract class to hold your data to send across the wire. This is the classic way to transmit data across WCF. Unless your XML is pretty complex (which it may well be) this might be a good alternative.

If you don't already have it, I strongly suggest picking up a copy of Juval Lowy's excellent "Programming WCF Services 2nd Edition." He has a whole chapter on transmitting information across WCF. It's the WCF Bible.

Terry Donaghe
I've got the book in front of me... ;)Nicked it off my colleages desk...Thanks for the tip.
GordonB
:) It's a super dense book. I suggest reading Chapter 1 a couple of times, though I think Chapter 3 is the one you're interested in. Don't have my copy in front of me though. :)
Terry Donaghe
A: 

You can simply return an XmlElement - possibly the DocumentElement property of an XmlDocument instance.

You do not have to use the XmlSerializer to do this. The DataContractSerializer is the default, and will send back an XmlElement just fine. You do not need to implement IXmlSerializable.

some sample code follows

service interface :

using System;
using System.Xml;
using System.ServiceModel;
using System.Runtime.Serialization;

namespace Cheeso.Samples.Webservices
{
    [ServiceContract(Namespace="urn:Cheeso.Samples" )]
    public interface IService
    {
        [OperationContract]
        XmlElement Register(String request);
    }
}

Notice I have no DataContract (and thus no DataMembers), because I am sending back an instance of a predefined class (XmlElement).

This is the service implementation:

using System;
using System.Xml;
using System.ServiceModel;

namespace Cheeso.Samples.Webservices._2009Jun01
{

    public class Results
    {
        public int Id;
        public Int64 WorkingSet;
        public String Name;
        public String Title;
    }

    [ServiceBehavior(Name="WcfXmlElementService",
                     Namespace="urn:Cheeso.Samples",
                     IncludeExceptionDetailInFaults=true)]

    public class WcfXmlElementService : IService
    {
        int index = 0;
        public XmlElement Register(string request)
        {
            XmlDocument doc = new XmlDocument();

            // can get the doc from anywhere. We use a LINQ-to-Objects result.

       // do the LINQ thing
       var processInfo =
    from p in System.Diagnostics.Process.GetProcesses()
    select new Results {
        Id = p.Id,
        WorkingSet = p.WorkingSet64,
        Name = p.ProcessName,
        Title = p.MainWindowTitle
     };

       // Note: cannot use an anonymous ilist if we will use XmlSerializer

       // serialize that list into the XmlDocument
       using (XmlWriter writer = doc.CreateNavigator().AppendChild())
       {
    var L = processInfo.ToList();
    XmlSerializer s1 = new XmlSerializer(L.GetType());
    s1.Serialize(writer, L);
       }

       index++;

       // Append some additional elements to the in-memory document.
       XmlElement elem = doc.CreateElement("id");
       elem.InnerText = System.Guid.NewGuid().ToString();
       doc.DocumentElement.AppendChild(elem);

       elem = doc.CreateElement("stamp");
       elem.InnerText = DateTime.Now.ToString("G");
       doc.DocumentElement.AppendChild(elem);

       elem = doc.CreateElement("in-reply-to");
       elem.InnerText = request;
       doc.DocumentElement.AppendChild(elem);

            return doc.DocumentElement;
        }
    }
}

The client, if you are using .NET, gets an XmlElement. If you are using some other stack, it will just be an XmlElement or XmlNode in that stack.

The XSD for the reply message is generic, like this:

<xs:element name="RegisterResponse">
  <xs:complexType>
    <xs:sequence>
      <xs:element minOccurs="0" name="RegisterResult" nillable="true">
        <xs:complexType>
          <xs:sequence>
            <xs:any minOccurs="0" processContents="lax" /> 
          </xs:sequence>
        </xs:complexType>
      </xs:element>
    </xs:sequence>
  </xs:complexType>
</xs:element>
Cheeso