views:

299

answers:

4

I have a set of data transfer objects (e.g. a lot of reqest, response message classes, like MainRequest, MainResponse, ShutDownRequest, ShutDownResponse) Where new classes keep coming as the project evolves. These classes must be (de)serialized from and to various XML formats with different public XSDs. New XML formats come as the project evolves too.

My question here is how I would design my classes and interfaces around these two requirement, especially where I should put the actual (de)serilization logic. Should I write a static service that can take the various DTO instances an knows how to serialize each of them? When new classes come I have to touch each FormatXSeriaizer and add new overrides. As new formats come I just have to write new FormatXSerializer classes.

FormatASerializer.Serialize(XmlWriter writer, MainResponse r);
FormatASerializer.Serialize(XmlWriter writer, ShutDownResponse r);
FormatBSerializer.Serialize(XmlWriter writer, MainResponse r);
FormatBSerializer.Serialize(XmlWriter writer, ShutDownResponse r);

or should the DTOs themself know how to do it. So I have it all in one place - for each DTO class. As new DTO classes come, they just have to implement the serialization for the various formats. As new formats come, I have to touch each DTO class.

myMainRequestInstace.Serialize(FormatTypeEnum type, XmlWriter writer);

Or is there a complete different approach? Should I introduce a common inteface for serialization and have some inversion of control, so I could load new format serializers at runtime?

What design pattern can guide me here?

What open source code in the .NET world could I study to see different approaches on this subject?

EDIT: I know about the general serialization techniques existing in the framework. My question is more geared towards class design that respects the two requirements: multiple xml formats and multiple DTOs (message types) that keep coming as the project evolves.

A: 

Read up about Custom XML Serializers, they should be able to do everything that you require.

http://geekswithblogs.net/marcel/archive/2006/05/19/78989.aspx

Burt
Please provide a link to "Custom XML Serializers"
John Saunders
+1  A: 

Part of the consideration will be how different the XML formats are. In particular, can they all be accomplished by using the overrides feature of XML Serialization? That allows you to provide an array of entries overriding features like element or attribute name / whether to serialize as elements or attributes, etc.

This could permit you to have your various formats differ only in terms of the override array passed to the serialization process. That would leave you with the question of how to determine the array to pass.

John Saunders
Thats the type of answers I am looking for.
bitbonk
+2  A: 

There are already some built in mechanisms for doing xml serialization in .NET.

Attribute based serialization - overview. All you have to do is tag your class's members with attributes and use the XmlSerializer class to serialize / deserialize the type.


    [XmlRoot("myXmlClass")]
    public class MyXmlClass
    {
        [XmlAttribute("myAttribtue")]
        public string MyAttribute { get; set; }
        [XmlElement("myElement")]
        public string MyElement { get; set; }
    }

    var output = new MemoryStream();
    var serializer = new XmlSerializer(typeof(MyXmlClass));
    serializer.Serialize(output, new MyXmlClass { MyAttribute = "foo", MyElement = "bar" });

Output -

<myXmlClass myAttribute="foo">
    <myElement>bar</myElement>
</myXmlClass>

Or you can make all of your xml serializable classes implement IXmlSerializable.

Edit - Now since I misunderstood your original question I'll amend this with a technique you could use to serialize the same object into multiple different xml formats.

Now that your data transfer object is serializable into at least one xml based format you could to a post translation step to get it into the format you want using xslt transforms. Xslt is a way to take xml and translate it into anything using an xslt file for instructions. In this case you would be translating into another xml format.

Here's how (assuming you've already written your xslt file) -


    // output stream from before
    output.Seek(0, SeekOrigin.Begin);
    var reader = XmlReader.Create(output);
    // cache this for performance reasons
    XslCompiledTransform transform = new XslCompiledTransform();
    transform.Load("c:\myTransforms\commonToFormatA.xslt");
    var writer = XmlWriter.Create(Console.Out); // write final output to console.
    transform.Transform(reader, writer);
Brandon Cuff
I know about the techniques existing in the framework. My question is more geared towards class design that respects the two requirements: multiple xml formants and multiple DTOs that keep coming as the project evolves.Also using "your" approach doesn't really work if there are multiple xml formats for the same DTO instances, wich is a requirement.
bitbonk
Ahh I see what you're saying. I don't know of a way to serialize into different formats with the existing .NET xml serialization framework but I believe I do have a possible solution for you so I edited my response to include it.
Brandon Cuff
A: 

The best approach would be something like this, this is my favourite approach:

public class SomeClass : ISerializable{
   private float _fVersion;
   ....
   public float Version {
       get { return this._fVersion; }
   }

   private SomeClass(SerializationInfo info, StreamingContext context) {
      bool bOk = false;
      this._fVersion = info.GetSingle("versionID");
      if (this._fVersion == 1.0F) bOk = this.HandleVersionOnePtZero(info, context);
      if (this._fVersion == 1.1F) bOk = this.HandleVersionOnePtOne(info, context);

      if (!bOk) throw new SerializationException(string.Format("SomeClass: Could not handle this version {0}.", this._fVersion.ToString()));
   }
   public void GetObjectData(SerializationInfo info, StreamingContext context) {
      info.AddValue("versionID", this._fVersion);
      if (this._fVersion == 1.0F) {
         info.AddValue("someField", ...);
         info.AddValue("anotherField", ...);
      }
      if (this._fVersion == 1.1F) {
         info.AddValue("someField1", ...);
         info.AddValue("anotherField2", ...);
      }
   }
   private bool HandleVersionOnePtZero(SerializationInfo info, StreamingContext context) {
      bool rvOk = false;
      ... = info.GetValue("someField");
      ... = info.GetValue("anotherField");
   }

   private bool HandleVersionOnePtOne(SerializationInfo info, StreamingContext context) {
      bool rvOk = false;
      ... = info.GetValue("someField1");
      ... = info.GetValue("anotherField2");
   }

}

This is how I enforce a tighter grain of control over the serialization of binary data and bump up the version. Now, those of you will point out that there is already a feature available to do this, but coming from .NET 1.1, well, old habits die hard.

Notice how in the code sample above I used two different methods HandleVersionOnePtZero and HandleVersionOnePtOne to handle the different versions of the serialized stream. By doing it this way, I have a greater degree of flexibility, say what if the field someField needs to be changed? Also, notice how the _fVersion field is the first thing the serializable routine does firstly, then checks the version of the field and decide which one to use.

The only thing about this, is if you change the namespace, then you will have difficulty deserializing the data, but you can use the SerializationBinder class as an example:

public class SomeClassBinder : System.Runtime.Serialization.SerializationBinder {
    public override Type BindToType(string assemblyName, string typeName) {
      Type typeToDeserialize = null;
      try {
         // For each assemblyName/typeName that you want to deserialize to
         // a different type, set typeToDeserialize to the desired type.
         string assemVer1 = System.Reflection.Assembly.GetExecutingAssembly().FullName;
         if (assemblyName.StartsWith("foobar")) {
             assemblyName = assemVer1;
             typeName = "fubar" + typeName.Substring(typeName.LastIndexOf("."), (typeName.Length - typeName.LastIndexOf(".")));
         }
         typeToDeserialize = Type.GetType(String.Format("{0}, {1}", typeName, assemblyName));
      } catch (System.Exception ex1) {
          throw ex1;
      } finally {
    }
    return typeToDeserialize;
  }
}

it would be called like this:
BinaryFormatter bf = new BinaryFormatter();
bf.Binder = new SomeClassBinder();
SomeClass sc = bf.Deserialize(stream); // assume stream is declared and open

Hope this helps, Best regards, Tom.

tommieb75
OOOOPPPPS My bad...When i was writing this, I did not realize you meant for XML.... sorry for the rambling and for those who will downvote this...
tommieb75