domenica 31 ottobre 2010 #

Working with XML part 1

This is the first of a small series of posts dedicated to XML serialization, publishing the code I used for the last Workshop we did last month. I’ll start from the serialization Helper class, already discussed in the past, now revised for Framework 4.0.

To persist on a disk a C# (orVB) class, you need to create it with some simple tricks to make it serializable. Serialization is a word I’ve started to listen around 2003 at the first conferences on .NET I attended to. Maybe it’s self explanatory for an English mother tongue, but it wasn’t for me. I needed a while to understand that Serialization is the way to persist on mass storage the state of an object to be able to rebuild the same object with the exactly same state as the one we persisted on disk.

Entity classes are classes with no particular abilities, they are defined this way because they represent structured data, so entities are the classes representing a Data Model, it can be simple and persisted on a file, or complex, like a LinqToSql or EntityFramework model persisted on a database. Entities are the kind of class we will surely serialize someway.

In .NET, you can serialize data in 2 ways, binary serialization and XML serialization. All the base data types in .NET are serializable, so any class containing only this kind of data, can be directly serialized with no particular modification except it needs to implement a parameterless constructor.

What is automatically serialized by .NET in an object? The public properties. Why only these? I think it is because they represent the state of the object as it can be observed by who instantiated it, we can always modify the standard behaviour overriding the default serialization. In these articles we will however show only how to use the standard serialization and how to drive it how we desire just using a few simple functionalities given us by the framework itself.

SerializeHelper class

This class provides all serialization services for simple object and composite objects (collections or classes containing other classes). It uses the classes of the System.Xml and System.Xml.Serialization namespaces. Let’s see its methods.

BuildReader

        public static XmlTextReader BuildReader(string pXmlString)
        {
            NameTable nt = new NameTable();
            XmlNamespaceManager nsmgr = new XmlNamespaceManager(nt);
            nsmgr.AddNamespace("bk", "urn:sample");

            XmlParserContext context = new XmlParserContext(null, nsmgr, null, XmlSpace.None);

            return (new XmlTextReader(pXmlString, XmlNodeType.Element, context));
        }

This first method, as it's name says, creates an XmlTextReader, that together with the correspondent XmlTextWriter are the base streams for reading and writing XML data. The method above has been designed to allow us to transform a string (such as a database field) in an Xml stream. The method generates a “fake” context to build the XmlTextReader to transform the string in a stream.

DeserializeFromString

        public static object DeserializeFromString(Type pTypeToDeserialize, string pXmlString)
        {
            object ret = null;
            using (XmlReader xr = BuildReader(pXmlString))
            {
                XmlSerializer serializer = new XmlSerializer(pTypeToDeserialize);
                ret = serializer.Deserialize(xr);
                xr.Close();
            }
            return (ret);
        }

        public static object DeserializeFromString(Type pTypeToDeserialize, 
Type[] pExtraTypes, string pXmlString) { object ret = null; using (XmlReader xr = BuildReader(pXmlString)) { XmlSerializer serializer = new XmlSerializer(pTypeToDeserialize, pExtraTypes); ret = serializer.Deserialize(xr); xr.Close(); } return (ret); }

These two methods, one built for a class with only simple objects, another for a class with complex data types, use the previous method to create a stream that is then passed to the XmlSerializer class to get the object from the persisted string.

DeserializeFromFile

        public static object DeserializeFromFile(Type pTypeToDeserialize, string pPath)
        {
            object ret = null;
            using (XmlTextReader reader = new XmlTextReader(pPath))
            {
                // Occorre un'istanza della classe XmlSerializer
                XmlSerializer serializer = new XmlSerializer(pTypeToDeserialize);
                // e questo é tutto ciò che serve per leggere i dati dal formato XML
                ret = serializer.Deserialize(reader);
                reader.Close();
            }
            return (ret);
        }

        public static object DeserializeFromFile(Type pTypeToDeserialize, 
Type[] pExtraTypes, string pPath) { object ret = null; using (XmlTextReader reader = new XmlTextReader(pPath)) { // Occorre un'istanza della classe XmlSerializer XmlSerializer serializer = new XmlSerializer(pTypeToDeserialize, pExtraTypes); // e questo é tutto ciò che serve per leggere i dati dal formato XML ret = serializer.Deserialize(reader); reader.Close(); } return (ret); }

The deserialization from a file is simpler, because XmlTextReader has a constructor that builds it from a file path. The two overloads of this method allow us to specify to the XmlSerializer the classes used if the objects involved are not simple classes. We will see it’s use when we will deserialize collections, but collections are not the only complex classes we will see.

SerializeToFile

        public static void SerializeToFile(string pPath, object pObjToSerialize, 
Type pTypeToSerialize, bool pNoNamespaces, string pPrefix) { FileInfo fInfo = new FileInfo(pPath); if (!Directory.Exists(fInfo.DirectoryName)) { Directory.CreateDirectory(fInfo.DirectoryName); } using(XmlTextWriter writer = new XmlTextWriter(pPath, Encoding.UTF8)) { // write a readable file writer.Formatting = Formatting.Indented; writer.Indentation = 4; // Occorre un'istanza della classe XmlSerializer XmlSerializer serializer = new XmlSerializer(pTypeToSerialize); XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); string prefix = pPrefix != null ? pPrefix : string.Empty; if (pNoNamespaces) { ns.Add(prefix, TXT_Namespace); } // e questo é tutto ciò che serve per persistere i dati serializer.Serialize(writer, pObjToSerialize, ns); writer.Close(); } } public static void SerializeToFile(string pPath, object pObjToSerialize, Type pTypeToSerialize, Type[] pExtraTypes, bool pNoNamespaces, string pPrefix) { FileInfo fInfo = new FileInfo(pPath); if (!Directory.Exists(fInfo.DirectoryName)) { Directory.CreateDirectory(fInfo.DirectoryName); } using(XmlTextWriter writer = new XmlTextWriter(pPath, Encoding.UTF8)) { // write a readable file writer.Formatting = Formatting.Indented; writer.Indentation = 4; //writer.Settings.OutputMethod = XmlOutputMethod. // Occorre un'istanza della classe XmlSerializer XmlSerializer serializer = new XmlSerializer(pTypeToSerialize, pExtraTypes); XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); string prefix = pPrefix != null ? pPrefix : string.Empty; if (pNoNamespaces) { ns.Add(prefix, TXT_Namespace); } // e questo é tutto ciò che serve per persistere i dati serializer.Serialize(writer, pObjToSerialize, ns); writer.Close(); } }

For the serialization, we made the two overloads with informations on the classes and we added a few parameters that allow us to format the classes, pNoNamespaces and pPrefix.

In the code we can see how, creating the XmlTextWriter we can decide the Encoding used for the generated string, our method uses UTF8 standard serialization but if we need something else we can use another encoding. We can also control the Xml File formatting both using the Formatting Property and some other properties to determine indentation and indentation character.

The use of XmlSerializerNamespaces allows us to avoid the verbose series of the namespaces inserted automatically in the first element of a serialized class:

<Serializzabile xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

Replacing this with:

<Competences xmlns="http://www.dotnetwork.it">

Or, using the Nonamespace flag:

<Competences>

“killing” the namespaces is opportune to create neutral classes for data exchange between different companies and softwares, while if we want to be sure that our own classes are marked specifically to make them unique, we can add our namespace (usually it is the domain name of our website).

the pPrefix parameter has instead the purpouse to allow us to set a prefix on the name of our classes to obtain the following effect:

<dnw:Programmer xmlns:dnw="http://www.dotnetwork.it">

<dnw:Skills>

It allows us to specify in detail the use of our own objects without a full namespace attribute on each of them.

SerializeToString

        public static string SerializeToString(object pObjToSerialize, 
Type[] pExtraTypes, bool pNoNamespaces, string pPrefix) { string ret = string.Empty; using (MemoryStream stream = new MemoryStream()) { XmlSerializer serializer = new XmlSerializer(pObjToSerialize.GetType(), pExtraTypes); XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); string prefix = pPrefix != null ? pPrefix : string.Empty; if (pNoNamespaces) { ns.Add(prefix, TXT_Namespace); } serializer.Serialize(stream, pObjToSerialize, ns); ret = Encoding.UTF8.GetString(stream.ToArray()); stream.Close(); } return (ret); } public static string SerializeToString(object pObjToSerialize, bool pNoNamespaces, string pPrefix) { string ret = string.Empty; using (MemoryStream stream = new MemoryStream()) { XmlSerializer serializer = new XmlSerializer(pObjToSerialize.GetType()); XmlSerializerNamespaces ns = new XmlSerializerNamespaces(); string prefix = pPrefix != null ? pPrefix : string.Empty; if (pNoNamespaces) { ns.Add(prefix, TXT_Namespace); } serializer.Serialize(stream, pObjToSerialize, ns); ret = Encoding.UTF8.GetString(stream.ToArray()); stream.Close(); } return (ret); }

The last method, with two overloads, allows us the Xml serialization of an object on a string instead of serializing it on a file stream. To be able to do so we need however a stream, we use the MemoryStream to avoid a file on disk, the Xml generation remain identical to the one that builds a file on disk.

Technorati Tag: ,,,

posted @ domenica 31 ottobre 2010 23.46 | Feedback (0)

Copyright © Sabrina C.

Design by Bartosz Brzezinski

Design by Phil Haack Based On A Design By Bartosz Brzezinski