Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Persisting Application data using HashTable and IsolatedStorage

0.00/5 (No votes)
14 Apr 2004 1  
This article demonstrates a custom class that can be used to store and retrieve application data in an easy and reliable manner. It uses an extended HashTable to persist data into Isolated Storage. You can download a sample project which demonstrates the use of this simple, but very useful class.

Sample Image - appstorage.jpg

Introduction

The .NET Framework provides the concept of 'application configuration file' to store application settings. The config file is a regular XML file and need to be shipped with the application. Since this is a simple XML file, users can make changes to the configuration data using any simple text editor, without the need to recompile the application.

Limitations of Application Configuration File

Config files are very handy and easy to use. But there are a few limitations:

  1. This is a read only mechanism. You cannot use the .NET ConfigurationSettings class to update the app.config file.

    Sicne the config file is a simple XML file, you can write your own class to update the data from your application. But in Windows environment, until you restart the application, the changes to the config file will not be recognized by the application. In web applications, if you update the web.config, the ASP.NET worker thread will be recycled.

  2. Security threat - The config file is accessible to everyone who has access to the file system. It is possible that the user may edit the config file manually and mess up with the content or provide invalid settings.

Alternate approaches

  1. Registry
  2. File
  3. Isolated Storage
  4. Serialize Custom classes

Each of the above approaches have advantages and disadvantages.

Writing into registry may need more permissions than otherwise required by the application. Also, it is possible that any other application can accidentally or deliberately overwrite the registry data.

Writing into File also requires specific permissions. Also, the question of where to store the file, how to secure it etc. raises.

What is Isolated Storage?

.NET introduces a concept called Isolated Storage. Isolated Storage is a kind of Virtual Folder. Users never need to know where exactly the file is stored. All you do is, tell the .NET framework to store your file in Isolated Storage. The physical location of Isolated Storage varies for each Operating System. But your application simply uses the .NET classes to create and access files, without bothering where it is physically located. And you can have Isolated Storage specific to each Assembly or each Windows user. Since Isolated Storage can be specific to each user or assembly, you don't need to worry about other users accidentally changing your files.

Persisting Application Data - Overview

In this article, I will demonstrate a custom class that can be used to easily store and retrieve application data. This custom class uses various features including hashtable, serialization and isolated storage to manipulate and store application data. The application data is stored in a hashtable as Key-Value pairs. Hashtable supports inserting any kind of objects into it. So this class, which is derived from HashTable, will support saving any object into the persistent storage. When the Save() method is called, the hashtable data will be serialized into Isolated Storage. In the constructor of the class, it will load data by deserializing itself from the data stored in Isolated Storage.

Concepts

I have written a custom class to store and retrieve custom application data in an easy and reliable manner, combining the above approaches. My approach makes use of the following .NET Framework concepts:

  1. HashTable - used to store data as key value pairs.
  2. Serialization - to persist the HashTable into the file system.
  3. Isolated Storage - to protect data from other users and assemblies.

Code Sample

using System;
using System.Collections;
using System.IO;
using System.IO.IsolatedStorage;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace CustomStorage
{
    [Serializable]
    public class ApplicationStorage : Hashtable
    {
        // File name. Let us use the entry assembly

        // name with .dat as the extension.

        private string settingsFileName = 
          System.Reflection.Assembly.GetEntryAssembly().GetName().Name + ".dat";
    
        // The default constructor.

        public ApplicationStorage()
        {
            LoadData();
        }
        // This constructor is required for deserializing

        // our class from persistent storage.

        protected ApplicationStorage (SerializationInfo info, 
          StreamingContext context): base(info, context)
        {
        }
        private void LoadData()
        {
            IsolatedStorageFile isoStore = 
              IsolatedStorageFile.GetStore( IsolatedStorageScope.User 
              | IsolatedStorageScope.Assembly, null, null );
            if ( isoStore.GetFileNames( settingsFileName ).Length == 0 )
            {
                // File not exists. Let us NOT try to DeSerialize it.

                return;
            }
            // Read the stream from Isolated Storage.

            Stream stream = new 
              IsolatedStorageFileStream(settingsFileName, 
              FileMode.OpenOrCreate, isoStore );
            if ( stream != null )
            {
                try
                {
                    // DeSerialize the Hashtable from stream.

                    IFormatter formatter = new BinaryFormatter();
                    Hashtable appData = ( Hashtable ) formatter.Deserialize(stream);
                    
                    // Enumerate through the collection and load our base Hashtable.

                    IDictionaryEnumerator enumerator = appData.GetEnumerator();
                    while ( enumerator.MoveNext() )
                    {
                        this[enumerator.Key] = enumerator.Value;
                    }
                }
                finally
                {
                    // We are done with it.

                    stream.Close();
                }
            }
        }
        public void ReLoad()
        {
            LoadData();
        }
        // Saves the configuration data to the persistent storage.

        public void Save()
        {
            // Open the stream from the IsolatedStorage.

            IsolatedStorageFile isoStore = 
              IsolatedStorageFile.GetStore( IsolatedStorageScope.User 
              | IsolatedStorageScope.Assembly, null, null );
            Stream stream = new 
              IsolatedStorageFileStream(settingsFileName, 
              FileMode.Create, isoStore );
        
            if ( stream != null )
            {
                try
                {
                    // Serialize the Hashtable into the IsolatedStorage.

                    IFormatter formatter = new BinaryFormatter();
                    formatter.Serialize( stream, (Hashtable)this );
                }
                finally
                {
                    stream.Close();
                }
            }
        }
    }
}

Let us analyze the code now.

public class ApplicationStorage : Hashtable

The class definition shows that it inherits the HashTable. So, you can use all features of a HashTable. This will help add and remove key value pairs easily. Also, the HashTable provides a very efficient mechanism to handle objects.

Constructor:

public ApplicationStorage()

{
    LoadData();

}

Inside the constructor, we will call the LoadData() method. When this class is instantiated, it will load the previously stored data into its HashTable. Let us see the implementation of the method LoadData().

private void LoadData()
{
    IsolatedStorageFile isoStore = 
      IsolatedStorageFile.GetStore( IsolatedStorageScope.User | 
      IsolatedStorageScope.Assembly, null, null );
    if ( isoStore.GetFileNames( settingsFileName ).Length == 0 )
    {
        // File not exists. Let us NOT try to DeSerialize it.

        return;
    }
    
    // Read the stream from Isolated Storage.

    Stream stream = new IsolatedStorageFileStream(settingsFileName, 
                    FileMode.OpenOrCreate, isoStore );
    if ( stream != null )
    {
        try
        {
            // DeSerialize the Hashtable from stream.

            IFormatter formatter = new BinaryFormatter();
            Hashtable appData = ( Hashtable ) formatter.Deserialize(stream);
            
            // Enumerate through the collection and load our base Hashtable.

            IDictionaryEnumerator enumerator = appData.GetEnumerator();
            while ( enumerator.MoveNext() )
            {
                this[enumerator.Key] = enumerator.Value;
            }
        }
        finally
        {
            // We are done with it.

            stream.Close();
        }
    }
}

We are using the IsolatedStorageFile class provided by the .NET Framework to get access to the 'Store'. The parameters specify that this store is specific to the 'current Windows user' and also to the 'current assembly'. Next, we are checking if the file we are looking for already exists in store. If it doesn't exists, we do not process it.

In the next few steps, we will read data from the stream and deserialize a HashTable. After deserializing the HashTable, we will enumerate through the entries and assign to the base class HashTable.

In short, we are loading the hashtable by deserializing the previously serialized hashtable data in isolated storage.

Next, let us see the Save() method. As the name indicates, it saves the data in HashTable into the Isolated Storage.

public void Save()
{
    // Open the stream from the IsolatedStorage.

    IsolatedStorageFile isoStore = 
      IsolatedStorageFile.GetStore( IsolatedStorageScope.User | 
      IsolatedStorageScope.Assembly, null, null );
    Stream stream = new 
      IsolatedStorageFileStream( settingsFileName, 
      FileMode.Create, isoStore );
    
    if ( stream != null )
    {
        try
        {
            // Serialize the Hashtable into the IsolatedStorage.

            IFormatter formatter = new BinaryFormatter();
            formatter.Serialize( stream, (Hashtable)this );
        }
        finally
        {
            stream.Close();
        }
    }
}

In the first step, we are opening the 'Store' in Isolated Storage. The parameters specify that this store is specific to the 'current Windows user' and also to the 'current assembly'. Next, we create a file in this store. The parameter FileMode.Create will ensure that the file will be created if it doesn't exist.

In the next step, we will deserialize the HashTable entries into the stream. This will save all our data in the HashTable into the Isolated Storage, which can be later deserialized in the LoadData() method.

Let us see some sample code to consume this little handy tool.

CustomStorage.ApplicationStorage storage = 
              new CustomStorage.ApplicationStorage();

storage["name"] = "john";

storage["age"] = 23;

storage["address"] = "#10, Vasant Nagar";

storage.Save();

In the above sample code, we are creating an instance of our class ApplicationStorage and adding key/value pairs to it. Since our class inherits the HashTable, we can use all methods of HashTable including Add(...), indexer (this), Remove(), Clear() etc. The Save() method will serialize the data into Isolated Storage.

Since HashTable supports any objects, you can use this class to store any kind of serializable objects.

The following sample code demonstrates how you can retrieve data.

CustomStorage.ApplicationStorage storage = 
            new CustomStorage.ApplicationStorage();

string name = storage["name"].ToString();

int age = int.parse(storage["age"].ToString());

string address = storage["address"].ToString();

When the class is instantiated, it will load the already saved data. Then you can use the indexer property of the HashTable to retrieve all key/value pairs from it.

Disadvantages

Our class uses Isolated Storage and it has a few limitations.

First thing is, Administrator can set quota per user and per assembly. This will be a limitation and our utility will fail if it exceeds the limit.

Also, smart users can find the location of Isolated Storage. Even though it is a hidden location, it is possible that someone can locate it and remove or corrupt our files. So, it doesn't give very high security for data.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here