Serialisering av data till XML

Inledning

Serialisering av data, av engelskans serialize, är ett viktigt begrepp i datorvärlden. Begreppet innebär en översättning av data för att underlätta lagring eller överföring. Data är i detta fall objekt. När sedan datan läses så ska det gå att kunna skapa upp objekten så som de var innan serialiseringen. Den vanligaste formen av serialisering är en översättning till 1'or och 0'or som inte är så lätt att läsa.

I slutet av 90-talet så började behovet av ett standardiserat text-baserat format att öka. XML (Extensible Markup Language) gjorde entré. Detta var ett format som var mer mänskligt läsbart och ej kopplat till något programmeringsspråk. XML har många likheter med HTML och ofta så är faktiskt HTML (xhtml) just XML. Andra populära användningsområden för XML är inom webbutveckling och AJAX där kommunikationen i AJAX använder XML eller det nya word-formatet (.docx) som är XML-baserat. Mer info om XML hittar du här.

I denna artikel ska du få lära dig hur du använder XML i dina program för att enkelt både läsa och spara data. C# har mycket bra stöd för integrering av XML som du strax ska få se. En av de största fördelarna blir att ditt program lättare kan integreras med andra program som vill läsa din data.

Exempel på XML

Vi ska snabbt ta en titt på hur XML kan se ut. Vi börjar med det mest vanliga, nämlihen en HTML sida:

Exempel: HTML

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="sv" lang="sv">
<head>
  <title>Sidan har flyttats</title>
</head>
<body>
<p>Denna sida har flyttats till <a href="http://example.org">example.org</a>.</p>
</body>
</html>

Reglerna för hur XML skrivs är rätt enkla. Varje element (tagg) kan ha ett innehåll, dvs. det som står mellan start- och sluttaggen. Har taggen ingen sluttagg så måste den ändå avslutas med ett /-tecken precis som elementet bok i exemplet nedan. Varje element kan ha ett valfritt antal attribut också, vilka skrivs inne i elementet.

Exempel: XML grunder

<name attribute="value">content</name>
<bok sprak="engelska" />

Vi kan också skriva .dtd filer till vår XML som talar om hur datan kan vara lagrad. Vi kan på så sätt specificera vilka attribut som är giltiga på vilka element eller t.ex. vilka element som får innehålla andra element etc etc. Med en dtd-fil kan sedan vår data valideras och vi kan avgöra om en fil följer specifikationerna. I exemplet oxan med XHTML så kan en hänvisning skymta till den dtd som bestämmer vilka regler som gäller för XHTML 1.0.

Vi kommer inte att gå in på hur dtd-filer skrivs eftersom vi inte kommer att använda några i artikeln.

Skapa hjälpklassen ObjectXMLSerializer.cs

Vi kommer att använda oss av en hjälpklass för serialiseringen som avsevärt kommer att underlätta för oss. Den kommer innehålla två stycken publika statiska metoder; ObjectXMLSerializer<T>.Load(path) och ObjectXMLSerializer<T>.Save(object, path). Klassen använder templates för att kunna vara så pass generell så att vi kan använda den till alla sorters klasser. Den går att jämföra med lagringsklassen List<T> som också är skriven som en template.

ObjectXMLSerializer.cs

using System;
using System.Xml.Serialization;
using System.IO;
using System.IO.IsolatedStorage;
using System.Collections.Generic;

namespace XMLSerialisering
{
    public static class ObjectXMLSerializer<T> where T : class
    {
        #region Publika medlemmar
        public static T Load(string path)
        {
            T serializableObject = LoadFromDocumentFormat(null, path, null);
            return serializableObject;
        }

        public static void Save(T serializableObject, string path)
        {
            SaveToDocumentFormat(serializableObject, null, path, null);
        }
        #endregion

        #region Privata medlemmar
        private static T LoadFromDocumentFormat(System.Type[] extraTypes, string path, 
            IsolatedStorageFile isolatedStorageFolder)
        {
            T serializableObject = null;

            using (TextReader textReader = CreateTextReader(isolatedStorageFolder, path))
            {
                XmlSerializer xmlSerializer = CreateXmlSerializer(extraTypes);
                serializableObject = xmlSerializer.Deserialize(textReader) as T;
            }
            return serializableObject;
        }

        private static TextReader CreateTextReader(IsolatedStorageFile isolatedStorageFolder, string path)
        {
            TextReader textReader = null;

            if (isolatedStorageFolder == null)
                textReader = new StreamReader(path);
            else
                textReader = new StreamReader(new IsolatedStorageFileStream(path, 
                    FileMode.Open, isolatedStorageFolder));

            return textReader;
        }

        private static void SaveToDocumentFormat(T serializableObject, 
            System.Type[] extraTypes, string path, IsolatedStorageFile isolatedStorageFolder)
        {
            using (TextWriter textWriter = CreateTextWriter(isolatedStorageFolder, path))
            {
                XmlSerializer xmlSerializer = CreateXmlSerializer(extraTypes);
                xmlSerializer.Serialize(textWriter, serializableObject);
            }
        }

        private static TextWriter CreateTextWriter(IsolatedStorageFile
                            isolatedStorageFolder, string path)
        {
            TextWriter textWriter = null;

            if (isolatedStorageFolder == null)
                textWriter = new StreamWriter(path);
            else
                textWriter = new StreamWriter(new IsolatedStorageFileStream(path,
                                  FileMode.OpenOrCreate, isolatedStorageFolder));

            return textWriter;
        }

        private static XmlSerializer CreateXmlSerializer(System.Type[] extraTypes)
        {
            Type ObjectType = typeof(T);

            XmlSerializer xmlSerializer = null;

            if (extraTypes != null)
                xmlSerializer = new XmlSerializer(ObjectType, extraTypes);
            else
                xmlSerializer = new XmlSerializer(ObjectType);

            return xmlSerializer;
        }
        #endregion
    }
}

Hjälpklassen sköter alla detaljer med I/O såsom att skapa läs- och skriv-objekt för filer som kopplas till de metoder för XML-serialisering som .NET erbjuder. Det vi är intresserade av är att kunna spara och ladda data så smidigt som möjligt därför går vi inte in i detalj hur klassen fungerar.

De första testerna

  • Börja med att skapa ett nytt Console Application och döp det till "XMLSerialisering".
  • Lägg till ObjectXMLSerializer.cs till ditt projekt. Du kan ladda ned filen, placera den i din projektkatalog och högerklicka på projektet och välja "Add"->"Existing item". Eller så kan du skapa en ny klass, döpa den till ObjectXMLSerializer.cs och klista in koden ovan.
  • Lägg nu till en ny klass som vi kallar TestKlass. Din solution borde likna detta:

bild

Vi skapar nu några medlemmar i klassen TestKlass samt använder oss av namnutrymmet System.Xml.Serialization. Det enda som vi behöver göra nu för att tala om att klassen ska gå att serialisera är att ange det med ett s.k. attribut på klassdefinitionen. Se koden nedan:

TestKlass.cs (1)

using System;
using System.Xml.Serialization;

[XmlRoot()]
public class TestKlass
{
    public string Namn = "Test";
    public int Tal = 5;
    public bool Status = true;

    protected string Skyddad = "Test protected";
    private string Privat = "Test private";
}

Attributet på rad 4 ska inte förväxlas med alla andra sorters attribut. Med attribut kan man annars mena XML-attribut eller medlemsvariabler till en klass. I detta fall är det en konstruktion med hakparenteser som anger en slags extra information som kan användas under körning. Metoden kallas reflection och är ett mycket kraftfullt sätt att dynamiskt undersöka information om klasser under körning. Vi kommer inte att gå in mer på reflection men du ska veta att det är den tekniken som ligger bakom XML-serialiseringen och dess enkelhet genom att vi bara anger attribut på våra klasser.

En viktigt sak är att klassen som ska serialiseras MÅSTE deklareras som public annars kommer programmet att krascha med ett InvalidOperationException.

Test 1

Dags att se hur det kommer att bli. I vårt huvudprogram skapar vi ett objekt av typen TestKlass och serialiserar den.

Program.cs (1)

using System;
using System.Collections.Generic;
using System.Text;

namespace XMLSerialisering
{
    class Program
    {
        static void Main(string[] args)
        {
            TestKlass t = new TestKlass();

            ObjectXMLSerializer<TestKlass>.Save(t, "test.xml");
        }
    }
}

Filen test.xml kommer att sparas där din .exe-fil ligger. Vi tar en titt på den.

test.xml (1)

<?xml version="1.0" encoding="utf-8"?>
<TestKlass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Namn>Test</Namn>
  <Tal>5</Tal>
  <Status>true</Status>
</TestKlass>

Vi kan genast konstatera att skyddade (protected) och privata medlemmar i klassen har inte serialiserats. Tyvärr är det så med XML-serialisering att det bara är publika medlemmar och properties som kan serialiseras. Vi kan även konstatera att medlemmarna automatiskt har blivit element med samma namn som variabelnamnet.

Vi testar även att ladda in objektet igen och konstaterar att det fungerar.

Program.cs (2)

using System;
using System.Collections.Generic;
using System.Text;

namespace XMLSerialisering
{
    class Program
    {
        static void Main(string[] args)
        {
            TestKlass t = new TestKlass();

            ObjectXMLSerializer<TestKlass>.Save(t, "test.xml");
			TestKlass t2 = ObjectXMLSerializer<TestKlass>.Load("test.xml");
            Console.WriteLine("{0} {1} {2}", t2.Namn, t2.Status, t2.Tal);
        }
    }
}

bild

Det måste påpekas att klassen som ska de-serialiseras MÅSTE ha en standardkonstruktor. I vårt fall har vi inga konstruktorer alls i TestKlass så en standardkonstruktor kommer att förutsättas trots att vi inte har någon. Finns det ingen standardkonstruktor vid de-serialisering så kommer det att uppstå ett InvalidOperationException när programmet försöker ladda data.

Test 2

Nu är det dags att leka lite med XML-inställningarna. Vi ändrar i TestKlass.

TestKlass.cs (2)

using System;
using System.Xml.Serialization;

[XmlRoot("RootNod")]
public class TestKlass
{
    [XmlElement("Namnet")]
    public string Namn = "Test";

    [XmlAttribute]
    public int Tal = 5;

    [XmlAttribute("Nytt namn")]
    public bool Status = true;
}

Resultatet blir nu:

test.xml (2)

<?xml version="1.0" encoding="utf-8"?>
<RootNod xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
	Tal="5" Nytt_x0020_namn="true">
  <Namnet>Test</Namnet>
</RootNod>

Som synes så har vi nu lagt till en massa attribut på klassmedlemmarna. Namn har bytt namn men är fortfarande ett element. Övriga medlemmar har blivit attribut till "föräldranoden", dvs "RootNod". Lägg märke till att du inte behöver ha parenteser på de attribut som du ändå inte tänker skicka med några parametrar (rad 10). Lägg också märke till hur attribut/element med mellanslag i namnet hanteras i XML.

Test 3

Vi testar lite till genom att ändra i TestKlass:

TestKlass.cs (3)

using System;
using System.Xml.Serialization;

public class TestKlass
{
    [XmlIgnore]
    public string Namn = "Test";
    public int Tal = 5;
    public bool Status = true;
}

Resultatet i XML blir:

test.xml (3)

<?xml version="1.0" encoding="utf-8"?>
<TestKlass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Tal>5</Tal>
  <Status>true</Status>
</TestKlass>

Attributet XmlIgnore förhindrar att publika medlemmar serialiseras. Vi kan även konstatera att vi egentligen inte behöver ange några attribut alls för att en klass ska kunna serialiseras till XML.

Finalen

Vi ska nu titta på hur listor kan användas för att spara ett okänt antal objekt. Vi ska även definiera standardvärden på en del av vår data. Mer om detta senare.

Exemplet kommer att efterlikna en telefonbok så vi börjar med att göra en klass för en Kontakt.

Kontakt.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Xml.Serialization;

namespace XMLSerialisering
{
    public enum Kön
    {
        Man,
        Kvinna
    }
    public class Kontakt
    {
        [XmlAttribute, DefaultValue(Kön.Man)]
        public Kön Roll;
        [XmlAttribute]
        public string Förnamn;
        [XmlAttribute]
        public string Efternamn;

        public Kontakt()
        {
        }
        public Kontakt(Kön k, string f, string e)
        {
            this.Roll = k;
            this.Förnamn = f;
            this.Efternamn = e;
        }
        public override string ToString()
        {
            return "Namn: " + Förnamn + " " + Efternamn + ", " + Roll;
        }
    }
}

Vi lagrar tre olika saker; förnamn, efternamn och könsroll. Eftersom man kan vara man eller kvinna så passar vi även på att använda en enum för detta. Observera att både klassen och enum'en måste deklareras public. Vidare måste vi även ha en standardkonstruktor. På variabeln Roll sätter vi attributet DefaultValue med värdet Man. DefaultValue kommer från namnutrymmet System.ComponentModel. Med ett standardvärde definierat så behös ej standardmässiga värden serialiseras. På så sätt kan vi få renare och mindre XML.

Vi skriver också en klass för en KontaktBok.

KontaktBok.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace XMLSerialisering
{
    public class KontaktBok
    {
        public string Ägare = "csharpskolan.se";
        public List<Kontakt> Kontakter = new List<Kontakt>();
    }
}

Slutligen skriver vi ett test som först skapar en kontaktbok och några kontakter, sparar den till kontaktbok.xml, läser in den igen och skriver ut alla kontakter.

Program.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace XMLSerialisering
{
    class Program
    {
        static void Main(string[] args)
        {
            KontaktBok b = new KontaktBok();

            b.Kontakter.Add(new Kontakt(Kön.Man, "Kalle", "Karlsson"));
            b.Kontakter.Add(new Kontakt(Kön.Kvinna, "Sara", "Svensson"));
            b.Kontakter.Add(new Kontakt(Kön.Man, "Erik", "Eriksson"));

            ObjectXMLSerializer<KontaktBok>.Save(b, "kontaktbok.xml");
            KontaktBok ny = ObjectXMLSerializer<KontaktBok>.Load("kontaktbok.xml");

            foreach (Kontakt k in ny.Kontakter)
                Console.WriteLine(k);

            Console.ReadLine();
        }
    }
}

Resultatet blir:

kontaktbok.xml

<?xml version="1.0" encoding="utf-8"?>
<KontaktBok xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Ägare>csharpskolan.se</Ägare>
  <Kontakter>
    <Kontakt Förnamn="Kalle" Efternamn="Karlsson" />
    <Kontakt Roll="Kvinna" Förnamn="Sara" Efternamn="Svensson" />
    <Kontakt Förnamn="Erik" Efternamn="Eriksson" />
  </Kontakter>
</KontaktBok>

Avslutning

Jag hoppas att du har fått tillräckliga kunskaper för att börja använda serialisering i C#. Just XML är trevligt då det inte behövs något specialutvecklat program för att ändra datan. Det räcker med en vanlig textredigerare. Du behöver heller inte ägna en massa tid åt att skriva speciell kod för att spara och ladda data.

Vi avslutar med en liten övning. Försök skapa ett program med klasser så att du kan läsa in följande data (nedan) och skriva ut på skärmen:

Övning

<?xml version="1.0" encoding="iso-8859-1"?>
<boksamling>
    <bok sprak="engelska">
        <titel>XSLT Cookbook</titel>
        <forfattare>Sal Mangano</forfattare>
    </bok>
    <bok sprak="svenska">
        <titel>Skriv med XML</titel>
        <forfattare>Åsa Blom</forfattare>
   </bok>
</boksamling>

..

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *

Scroll to top