Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Serializing Data Transfer Objects in .NET

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.

like image 219
bitbonk Avatar asked Jan 23 '10 00:01

bitbonk


People also ask

What is Data Transfer Objects in C#?

A DTO is an object that defines how the data will be sent over the network. Let's see how that works with the Book entity. In the Models folder, add two DTO classes: C# Copy.

What is Data Transfer Object in MVC?

In the field of programming a data transfer object (DTO) is an object that carries data between processes. The motivation for its use is that communication between processes is usually done resorting to remote interfaces (e.g., web services), where each call is an expensive operation.

Why we use Data Transfer Object?

A data transfer object (DTO) is an object that carries data between processes. You can use this technique to facilitate communication between two systems (like an API and your server) without potentially exposing sensitive information.


2 Answers

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 do 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);
like image 34
Brandon Cuff Avatar answered Sep 22 '22 00:09

Brandon Cuff


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

like image 187
t0mm13b Avatar answered Sep 20 '22 00:09

t0mm13b