Oude artikelen van mijzelf: Serializable objects

C

En een tweede. Vandaag nog maar een ander veel gevraagd, maar niet gevonden, artikel teruggehaald dankzij de “Way Back Machine“.
Using the power of the .Net framework to save your data objects as XML.

[Serializable] attribute

The “System.Xml.Serialization.XmlSerializer” object (in the .Net framework) can create an XML document of any class that implements the [Serializable] attribute.

With some basic code, you can create your own project specific Object Oriented data store within a few minutes. The code described here can act as an easy data store with an XML document as back-end.

Advantage/Upside

The data stored is easy to access and easily addressed. Also the data is available as xml.

Drawbacks/Downside

If the stored data is changed often, or you have a lot of data to store (>1mb), this solution will perform badly. This example includes a basic type of caching, but with many changes or a lot of data this solution can perform slowly, even with the caching implemented.
“Think before you use, but that goes for any solution”

Usage

If I use this solution, it will be mostly used for “semi”-static content or small “datasets”.

The dataclass

This example will use a dataclass, which we all have probably build one or twice, containing a user object and a collection.

In our case, a user has the following properties:

  • Guid ID,
  • string Username,
  • string Password,
  • string Email

This will result in the following code:

[code lang=”csharp”]
[Serializable]
public class user
{
private Guid id;
private string username;
private string password;
private string email;

public Guid ID
{
get {return this.id;}
set {this.id = value;}
}
public string Username
{
get {return this.username;}
set {this.username = value;}
}
public string Password
{
get {return this.password;}
set {this.password = value;}
}
public string Email
{
get {return this.email;}
set {this.email = value;}
}
}
[/code]

If you want you can implement some logic for the Guid, some hashing logic for the password, and maybe more. Notice the [Serializable] attribute before the class definition. This allows the System.Xml.Serialization.XmlSerializer” object to “read” the class.

The data collection

Now we need a collection of users, the following code will create our collection class:

[code lang=”csharp”]
[Serializable]
public class users : CollectionBase
{
public user this[int index]
{
get {return (user)base.List[index];}
set {base.List[index] = value;}
}
// add a user to base.List
public void Add(user item)
{
base.List.Add(item);
}
// remove a user from base.List
public void Remove(user item)
{
base.List.Remove(item);
}
// remove a user (by Guid) from base.List
public void Remove(Guid g)
{
this.Remove(this.Find(g));
}
// find a user by Guid
public user Find (Guid g)
{
foreach(user u in base.List)
if (u.ID == g)
return u;
return null;
}
}
[/code]

This just is a basic simple collection, but notice the [Serializable] attribute before the class definition again. These two classes provide us with a simple collection of users which we can use from our applications to access the users.

Adding data

To create a new user, we just have to create a new user object, set its properties and add it to our user collection. The code in figure 3 will add two users to a usercollection. After running the code the collection will contain the following users:
[code]
Guid Username Email Password
Randomly created Guid Username_first Email_first Password_first
Randomly created Guid Username_second Email_second Password_second
[/code]

[code lang=”csharp”]
public users userCollection = new users();
public void Page_Load(object sender, System.EventArgs e)
{
//add some users
setUser("username_first", "email_first", "password_first");
setUser("username_second", "email_second", "password_second");
}

public void setUser(string username, string email, string password)
{
// Create first user
user newUser = new user();
newUser.ID = Guid.NewGuid();
newUser.Username = username;
newUser.Email = email;
newUser.Password = password;

//add the user to the user collection
userCollection.Add(newUser);
}
[/code]

The collection created in figure 3 will be stored in the memory of the computer running the code, and will exist as long as our usercollection object exists.

Working with data

Working with the collection is simple. Because we inherited the “CollectionBase” class in our usercollection class, we can loop trough all the items in the collection. The following example will loop trough all users and checks if a user can be authenticated using some credentials.

   
   1:      private bool AuthenticateUser(string username, string password)
   2:      {
   3:          foreach (user u in userCollection)
   4:              if (u.Username == username)
   5:                  if (u.Password == password)
   6:                     return true;
   7:          return false;
   8:      }

Finally, persisting the data

Accessing the data using these kinds of collections is easy, but if the data cannot be stored it will not be usable. That’s where the “System.Xml.Serialization.XmlSerializer” comes in.

   1:      public void SaveXml(string path, object obj)
   2:      {
   3:          //Create a file stream to the target file
   4:          FileStream outputFile =
   5:                  new FileStream(path, FileMode.Create, FileAccess.Write);
   6:
   7:          //Create a streamwriter
   8:          StreamWriter sw = new StreamWriter(outputFile);
   9:
  10:          //Create the serializer object
  11:          System.Xml.Serialization.XmlSerializer serializer =
  12:              new System.Xml.Serialization.XmlSerializer(obj.GetType());
  13:
  14:          //Serialize the collection to the streamwriter
  15:          serializer.Serialize(sw, obj);
  16:
  17:          //Save the data to the filestream
  18:          sw.Flush();
  19:          outputFile.Close();
  20:      }

To save an object to XML an XMLSerializer object is created. This object contains a method “Serialize” which converts any object (which implements the [Serializable] attribute) and saves its output as XML in a Stream. In this method the stream is saved to file. But it is possible to extend the method to use the xml stream to save the data in a database or use any other data storage.

   1:      public users userCollection = new users();
   2:      public void Page_Load(object sender, System.EventArgs e)
   3:      {
   4:          //add some users
   5:          setUser("username_first", "email_first", "password_first");
   6:          setUser("username_second", "email_second", "password_second");
   7:
   8:          //save the data collection
   9:          SaveXml(@"C:Tempusers.xml", userCollection);
  10:      }

To retrieve the data the XMLSerializer object is used again, but this time the method “Deserialize” will be used:

   1:      public object RetrieveXml (string path, System.Type t)
   2:      {
   3:          object col;
   4:          //Create a streamreader
   5:          TextReader reader = new StreamReader(path);
   6:
   7:          //Create the serializer object
   8:          System.Xml.Serialization.XmlSerializer serializer =
   9:              new System.Xml.Serialization.XmlSerializer(t);
  10:
  11:          //Deserialize the TextReader
  12:          col = (object)serializer.Deserialize(reader);
  13:
  14:          // Close the file stream and return the new object
  15:          reader.Close();
  16:          return col;
  17:      }

The important line of code is “col = (object)serializer.Deserialize(reader);” The object col (which will be returned as output of the RetrieveXML Method) will contain the data which is read from the xml document and will be of type “t”.

   1:      public users userCollection;
   2:      public void Page_Load(object sender, System.EventArgs e)
   3:      {
   4:          //Retrieve the collection
   5:          userCollection = (users)RetrieveXml(@"C:Tempusers.xml", typeof(users));
   6:
   7:          //add some users
   8:          setUser("username_first", "email_first", "password_first");
   9:          setUser("username_second", "email_second", "password_second");
  10:
  11:          //save the data collection
  12:          SaveXml(@"C:Tempusers.xml", userCollection);
  13:      }

One important detail, the output of the “RetrieveXml” method is of type “object” instead of “users”. Before setting the userCollection to the output, the output is cast to type “users”.

The XML document

The xml document created by the “SaveXML” method will be stored in C:tempusers.xml. The xml document will look like this, when the code above is run twice:

    8da9ab98-dee7-47dc-a8c4-3d5773cfd2d6
    username_first
    password_first
    email_first

    88249354-cc77-4d41-abf7-18ee5fc79b90
    username_second
password_second
    email_second

    284929d9-bc4a-4fd7-8e34-b9a9f2a53081
    username_first
password_first
    email_first

    c8d4f0a0-d297-46f3-a9ba-7bd0d1bf6b4c
    username_second
password_second
    email_second

Helper class

The Save and Retrieve methods can be placed in a helper object. The folowing class is a basic version, which includes a basic form of caching (which will increase performance dramaticly):

   1:  using System;
   2:  using System.IO;
   3:  using System.Xml.Serialization;
   4:
   5:  namespace markberck.Helpers
   6:  {
   7:      public class XmlSerializer
   8:      {
   9:          private string cacheName = "markberck.Helpers.DataCollection.CachedObject";
  10:          private bool _useCache = false;
  11:
  12:          public void SaveXml(string path, object col)
  13:          {
  14:              SaveXml(path, col, this._useCache) ;
  15:          }
  16:          public void SaveXml(string path, object col, bool useCache)
  17:          {
  18:              // save Col to Cache?
  19:              if (useCache)
  20:              {
  21:                  System.Web.HttpContext httpC = System.Web.HttpContext.Current;
  22:                  httpC.Cache.Remove(cacheName + col.GetType().ToString());
  23:              }
  24:
  25:              FileStream outputFile =
  26:                        new FileStream(path, FileMode.Create, FileAccess.Write);
  27:              System.Xml.Serialization.XmlSerializer serializer =
  28:                        new System.Xml.Serialization.XmlSerializer(col.GetType());
  29:              StreamWriter sw = new StreamWriter(outputFile);
  30:
  31:              serializer.Serialize(sw, col);
  32:
  33:              sw.Flush();
  34:              outputFile.Close();
  35:
  36:              if (useCache)
  37:              {
  38:                  System.Web.HttpContext httpC = System.Web.HttpContext.Current;
  39:                  httpC.Cache[cacheName + col.GetType().ToString()] = col;
  40:              }
  41:          }
  42:
  43:
  44:          public object RetrieveXml (string path, object t)
  45:          {
  46:              t = RetrieveXml(path, t.GetType(), this._useCache);
  47:              return t;
  48:          }
  49:          public object RetrieveXml (string path, System.Type t)
  50:          {
  51:              return RetrieveXml(path, t, this._useCache);
  52:          }
  53:          public object RetrieveXml (string path, System.Type t, bool useCache)
  54:          {
  55:              //try to get the collection from the cache...
  56:              if (useCache)
  57:              {
  58:                  System.Web.HttpContext httpC = System.Web.HttpContext.Current;
  59:                  object outCol =
  60:                        (object)httpC.Cache[cacheName + t.ToString()];
  61:                  if (outCol != null)
  62:                      return outCol;
  63:              }
  64:
  65:              // cache object wasn't available or wasn't used (useCache=false),
  66:              // so get it by path;
  67:
  68:              object col;
  69:              TextReader reader = new StreamReader(path);
  70:              System.Xml.Serialization.XmlSerializer serializer =
  71:                                  new System.Xml.Serialization.XmlSerializer(t);
  72:              col = (object)serializer.Deserialize(reader);
  73:              reader.Close();
  74:
  75:              // if cache is on, save it to the cache
  76:              if (useCache)
  77:              {
  78:                  System.Web.HttpContext httpC = System.Web.HttpContext.Current;
  79:                  httpC.Cache[cacheName + col.GetType().ToString()] = col;
  80:              }
  81:
  82:              //always return the collection
  83:              return col;
  84:          }
  85:
  86:          public XmlSerializer()
  87:          {
  88:              this._useCache = false;
  89:          }
  90:          public XmlSerializer(bool useCache)
  91:          {
  92:              this._useCache = useCache;
  93:          }
  94:          public XmlSerializer(bool useCache, string cacheObjectName)
  95:          {
  96:              this._useCache = useCache;
  97:              this.cacheName = cacheObjectName;
  98:          }
  99:      }
 100:  }