Save your UserSettings in an Isolated Store

By Fons Sonnemans (February 2003)

Download UserSettings.zip

Introduction

I like applications who remember my settings the next time I use it. This is an easy feature which is often forgotten. Most of the time because it is quite some work. With this article I want to help you with this and saving you a lot off work.

Then there is always the question 'where do we store the settings?'. I see a lot of applications using the Registry, ini-files or xml-files. All these solutions are causing a security risk. Especially when you want your application to be downloaded from the web using the 'no touch' deployment features of .NET.

Microsoft has solved this problem for me by introducing an isolated stores. With these stores, you can read and write data that less trusted code cannot access and prevent the exposure of sensitive information that can be saved elsewhere on the file system. Data is stored in compartments that are isolated by the current user and by the assembly in which the code exists.

The downloadable zipfile contains a UserSettingLibrary which can be used to store user settings into an isolated store.

IsolatedUserSettings class

The IsolatedUserSettings class can be used to store and retrieve user settings in and from an IsolatedStorageFile. The RegisterForm() method can be used to register a form. The location, size and windowstate of the form will be saved and restored. The SaveSetting() method saves an serializable object which can be restored using the GetSetting() method.


using System;
using System.IO;
using System.IO.IsolatedStorage;
using System.Collections;
using System.Windows.Forms;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Reflection;

namespace ReflectionIT.UserSettings
{
    /// <summary>
    /// The IsolatedUserSettings class can be used to store and retrieve user settings
    /// in and from an IsolatedStorageFile.
    /// </summary>
    public class IsolatedUserSettings : IDisposable
    {
        private Hashtable _settings;
        private bool _closed = false;
        private string _fileName;

        /// <summary>
        /// Constructor
        /// </summary>
        public IsolatedUserSettings(string fileName)
        {
            _fileName = fileName;
            IsolatedStorageFile isoFile = GetStore();
            string[] files = isoFile.GetFileNames(fileName);
            if (files.Length == 1) {
                // file exists -> Deserialize
                using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(
                         fileName, FileMode.Open, isoFile)) {
                    BinaryFormatter formatter = new BinaryFormatter();
                    try {
                        _settings = (Hashtable)formatter.Deserialize(stream);
                    }
                    catch (SerializationException) {
                        _settings = new Hashtable();
                    }
                }
            } else {
                // start fresh
                _settings = new Hashtable();
            }
        }

        /// <summary>
        /// ND Destructor
        /// </summary>
        ~IsolatedUserSettings() {
            Dispose(false);
        }

        /// <summary>
        /// Close the IsolatedUserSettings
        /// </summary>
        public void Dispose() {
            Dispose(true);
            System.GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Close the IsolatedUserSettings
        /// </summary>
        /// <param key="disposing"></param>
        protected virtual void Dispose(bool disposing) {
            this.Close();
        }

        /// <summary>
        /// Write the UserSettings to the IsolatedStorage
        /// </summary>
        public virtual void Close() {
            if (!_closed) {
                IsolatedStorageFile isoFile = GetStore();

                using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(
                         FileName, FileMode.Create, isoFile)) {
                    BinaryFormatter formatter = new BinaryFormatter();
                    formatter.Serialize(stream, _settings);
                }
                _closed = true;
            }
        }

        /// <summary>
        /// Get the fileName
        /// </summary>
        public string FileName {
            get { return _fileName; }
        }

        /// <summary>
        /// Return the Store
        /// </summary>
        /// <returns></returns>
        private IsolatedStorageFile GetStore() {
            return IsolatedStorageFile.GetStore(IsolatedStorageScope.Assembly |
                IsolatedStorageScope.User | IsolatedStorageScope.Domain |
                IsolatedStorageScope.Roaming, null, null);
        }

        /// <summary>
        /// Register a Form
        /// </summary>
        /// <param key="key"></param>
        /// <param key="form"></param>
        public virtual void RegisterForm(string key, Form form) {
            FormSetting f;
            if (_settings.ContainsKey(key)) {
                f = (FormSetting)_settings[key];
                f.Restore(form);
            } else {
                f = new FormSetting(key);
                f.Backup(form);
                _settings.Add(key, f);
            }
            form.SizeChanged += new EventHandler(f.FormMovedOrResized);
            form.LocationChanged += new EventHandler(f.FormMovedOrResized);
        }

        /// <summary>
        /// UnRegister a Form
        /// </summary>
        /// <param key="key">Name of the Form. A forms with an existing key will
        /// automatically get the stored settings.</param>
        /// <param key="form"></param>
        public virtual void UnRegisterForm(string key, Form form) {
            if (_settings.ContainsKey(key)) {
                FormSetting f = (FormSetting)_settings[key];
                f.Backup(form);
                form.SizeChanged -= new EventHandler(f.FormMovedOrResized);
                form.LocationChanged -= new EventHandler(f.FormMovedOrResized);
            }
        }

        /// <summary>
        /// Save a setting with a given key
        /// </summary>
        /// <param key="key">Name of the setting by which it is saved an can be
        /// restored using GetSetting().</param>
        /// <param key="setting"></param>
        public void SaveSetting(string key, object setting) {

            // Check Serializable attribute
            TypeAttributes attributes = setting.GetType().Attributes;
            if ((attributes & TypeAttributes.Serializable) == 0) {
                throw new ArgumentException("Setting argument must be marked Serializable.");
            }

            // Add or Overwrite setting
            if (_settings.ContainsKey(key)) {
                _settings[key] = setting;
            } else {
                _settings.Add(key, setting);
            }
        }

        /// <summary>
        /// Return the setting with the given key
        /// </summary>
        /// <param key="key"></param>
        /// <returns>
        /// The setting object or null when the the given key does not exists
        /// </returns>
        public object GetSetting(string key) {
            return GetSetting(key, null);
        }

        /// <summary>
        /// Return the setting with the given key
        /// </summary>
        /// <param key="key"></param>
        /// <returns></returns>
        public object GetSetting(string key, object defaultValue) {
            if (_settings.ContainsKey(key)) {
                return _settings[key];
            }
            return defaultValue;
        }
    }
}

The FormSetting class is used to store a form's location, size and windowstate in a serializable object. The next time you register a form with the same key the location, size and windowstate is restored to the saved values. So if you resize your form, close it and open it again the size will be the same as it was when you last closed it.

using System;
using System.Drawing;
using System.Windows.Forms;

namespace ReflectionIT.UserSettings
{
    /// <summary>
    /// Summary description for FormSetting.
    /// </summary>
    [Serializable]
    public class FormSetting
    {
        private Point _location;
        private string _name;
        private Size _size;
        private FormWindowState _windowState;

        public FormSetting(string name)
        {
            this._name = name;
        }

        /// <summary>
        /// Get the name used for this FormSetting
        /// </summary>
        public string Name {
            get { return (this._name); }
        }

        /// <summary>
        /// Backup the Size, Location and WindowState (only when not Minimized)
        /// </summary>
        /// <param name="form"></param>
        public virtual void Backup(Form form) {
            if (form.WindowState != FormWindowState.Minimized) {
                // Only when not Maximized
                if (form.WindowState != FormWindowState.Maximized) {
                    this._location = form.Location;
                    this._size = form.Size;
                }
                this._windowState = form.WindowState;
            }
        }

        /// <summary>
        /// Eventhandler
        /// </summary>
        /// <param name="sender">Form which is Moved or Resized</param>
        /// <param name="e"></param>
        public virtual void FormMovedOrResized(object sender, EventArgs e) {
            Backup((Form)sender);
        }

        /// <summary>
        /// Restore the Size, Location and WindowState for the given form
        /// </summary>
        /// <param name="form"></param>
        public virtual void Restore(Form form) {
            form.Location = this._location;
            form.Size = this._size;
            form.WindowState = this._windowState;
        }
    }
}

Sample

The included sample uses the IsolatedUserSettings class to store and restore the user settings. In this case the Form (Location, Size and WindowState), the Checked property of a CheckBox and a MostRecentlyUsedList object. The settings are restored in the constructor and saved in the Closing event handler, the Form properties are saved automatically by the internally used FormSetting class. You must set the StartPosition property of the form to 'Manual', otherwise the location will not be restored.

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using ReflectionIT.UserSettings;

namespace Test
{
    /// <summary>
    /// Summary description for Form1.
    /// </summary>
    public class Form1 : System.Windows.Forms.Form
    {
        private System.Windows.Forms.GroupBox groupBox1;
        private System.Windows.Forms.Button buttonMRUAdd;
        private System.Windows.Forms.TextBox textBoxMRU;
        private System.Windows.Forms.ListBox listBoxMRU;

        private MostRecentlyUsedList _mru;
        private IsolatedUserSettings _settings = new IsolatedUserSettings("settings.dat");
        private System.Windows.Forms.CheckBox checkBox1;

        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.Container components = null;

        public Form1()
        {
            //
            // Required for Windows Form Designer support
            //
            InitializeComponent();

            // Get UserSettings
            _settings.RegisterForm(this.Name, this);
            _mru=(MostRecentlyUsedList)_settings.GetSetting("mru",new MostRecentlyUsedList(5));
            checkBox1.Checked = (bool)_settings.GetSetting("checked", true);
            listBoxMRU.DataSource = _mru;
        }

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        protected override void Dispose( bool disposing )
        {
            if( disposing )
            {
                if (components != null)
                {
                    components.Dispose();
                }
            }
            base.Dispose( disposing );
        }

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.Run(new Form1());
        }

        /// <summary>
        /// Add the Text to the MRU and bind it to the datasource
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void buttonMRUAdd_Click(object sender, System.EventArgs e) {
            _mru.Add(textBoxMRU.Text);
            textBoxMRU.Clear();
            textBoxMRU.Focus();

            listBoxMRU.DataSource = null; // force update
            listBoxMRU.DataSource = _mru;
        }

        /// <summary>
        /// Save the MRU and checkBox1.Checked to the UserSettings
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e) {
            _settings.SaveSetting("mru", _mru);
            _settings.SaveSetting("checked", checkBox1.Checked);
            _settings.Close();
        }
    }
}

The MostRecentlyUsedList class has a Serializable attribute which makes it possible to store it in an IsolatedUserSetting.

 

using System;
using System.Collections;

namespace Test
{
    /// <summary>
    /// Summary description for MostRecentlyUsedList.
    /// </summary>
    [Serializable]
    public class MostRecentlyUsedList : IList
    {
        private int _capacity;
        private ArrayList _list;

        /// <summary>
        /// Initializes a new instance of the MostRecentlyUsedList class
        /// that is empty and has the specified capacity
        /// </summary>
        /// <param name="capacity">The number of elements that the new
        /// MostRecentlyUsedList is capable of storing. </param>
        public MostRecentlyUsedList(int capacity)
        {
            _capacity = capacity;
            _list = new ArrayList(capacity);
        }

        /// <summary>
        /// Add a object to the beginning of the list. If the value already
        /// exists it is removed first. The length of the list will never
        /// exceed the capacity.
        /// </summary>
        /// <param name="value">The Object to be added to the beginning
        /// of the MostRecentlyUsedList</param>
        public void Add(object value)
        {
            if (_list.Contains(value)) {
                _list.Remove(value);
            } else {
                if (this.Count == this.Capacity) {
                    _list.RemoveAt(this.Count - 1);
                }
            }
            _list.Insert(0, value);
        }

        /// <summary>
        /// Get or sets the number of elements that the MostRecentlyUsedList
        /// is capable of storing.
        /// </summary>
        public int Capacity {
            get { return (this._capacity);    }
            set {
                this._capacity = value;
                while (this.Count >= this.Capacity) {
                    _list.RemoveAt(this.Count - 1);
                }
            }
        }
        ....

Conclusion

The IsolatedUserSettings is an simple class which uses an isolated store to serialize and deserialize objects. With it you can enhance your applications without much effort.

Any suggestions and feedback for improving this article is most welcome. Send your suggestions and feedback to Fons.Sonnemans@reflectionit.nl