Click here to Skip to main content
Click here to Skip to main content

Implementing ASP.NET XML providers - Part 1 (Persistance)

, 29 Mar 2008
Rate this:
Please Sign up or sign in to vote.
In this series I'll go through the steps of my implementation of ASP.NET XmlProviders.

Introduction

The built-in ASP.NET Membership, Roles, Profile providers fits into many Web site use-cases, but not all.
While I was wokring on some of my projects I have experienced the need of having a light ASP.NET providers pack.
Providers that work without database or additional services. Providers that can easy deploy with a Web site under restricted hosting environment.

The obvious solution was to implement ASP.NET Membership, Role, Profile providers base on XML file storages.

In this series I'll go through the steps of my implementation of ASP.NET XmlProviders.

Background

This ASP.NET Xml Providers pack is an evolution of examples, articles and samples of code which I have packed and improved for the needs of some of the projects I was working on.
In fact I'm pretty satisfied of using it on my projects, thus I have decided to share it and uploaded on Codeplex.

The Persistable base class

This article is about the base class for all XML storage classes.
First of all this is not invented originally by me.
I grab it from the net, made it generic, did some fixed and prepare it for synchronized usage.
I have implemented a simple, but robust caching in the Persistable class, in order to improve the performance.
In fact I'm using it as a base class in all my projects when I need fast and robust objects' persistance to XML files.

Here is the source code of Persistable class:

/// <span class="code-SummaryComment"><summary></span>
/// Generic class for a perssitable object. 
/// Encapsulates the logic to load/save data from/to the filesystem. 
/// To speed up the acces, caching is used.
/// <span class="code-SummaryComment"></summary></span>
/// <span class="code-SummaryComment"><typeparam name="T">class or struct with all the data-fields that must be persisted</typeparam></span>
public class Persistable<T> where T : new() {

    #region Static Fields ///////////////////////////////////////////////////////////

    public static readonly TimeSpan DefaultSlidingExpiration = TimeSpan.FromMinutes(30);

    #endregion

    #region Fields  /////////////////////////////////////////////////////////////////

    string _fileName;
    ReaderWriterLock _lock = new ReaderWriterLock();
    XmlSerializer _serializer;
    TimeSpan _slidingExpiration;
    [NonSerialized]
    object _syncRoot;
    T _value;

    #endregion

    #region Properties  /////////////////////////////////////////////////////////////

    /// <span class="code-SummaryComment"><summary></span>
    /// Gets a value indicating whether this instance is empty.
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><value><c>true</c> if this instance is empty; otherwise, <c>false</c>.</value></span>
    protected virtual bool IsEmpty {
        get {
            return (_value == null || (_value is ICollection && (_value as ICollection).Count == 0));
        }
    }

    /// <span class="code-SummaryComment"><summary></span>
    /// Gets a value indicating whether this instance is synchronized.
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><value></span>
    ///     <span class="code-SummaryComment"><c>true</c> if this instance is synchronized; otherwise, <c>false</c>.</span>
    /// <span class="code-SummaryComment"></value></span>
    public virtual bool IsSynchronized {
        get { 
            return false; 
        }
    }

    /// <span class="code-SummaryComment"><summary></span>
    /// Gets the sync root.
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><value>The sync root.</value></span>
    public virtual object SyncRoot {
        get {
            if (_syncRoot == null)
                Interlocked.CompareExchange(ref this._syncRoot, new object(), null);
            return _syncRoot;
        }
    }

    /// <span class="code-SummaryComment"><summary></span>
    /// Gets or sets the data.
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><value>The data.</value></span>
    protected virtual T Value {
        get {
            if (IsEmpty) Load();
            return _value;
        }
        set { _value = value; }
    }
    #endregion

    #region Construct  //////////////////////////////////////////////////////////////

    /// <span class="code-SummaryComment"><summary></span>
    /// Creates a instance of Persistable. Also creates a instance of T
    /// <span class="code-SummaryComment"></summary></span>
    protected Persistable(string fileName, TimeSpan cacheSlidingExpiration) {
        _fileName = fileName;
        _serializer = new XmlSerializer(typeof(T));
        _slidingExpiration = cacheSlidingExpiration;
    }

    /// <span class="code-SummaryComment"><summary></span>
    /// 
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="fileName"></param></span>
    protected Persistable(string fileName)
        : this(fileName, DefaultSlidingExpiration) {
    }
    #endregion

    #region Methods /////////////////////////////////////////////////////////////////

    /// <span class="code-SummaryComment"><summary></span>
    /// Deletes the data from the cache and filesystem
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><returns></returns></span>
    public virtual bool Delete() {

        bool success = true;
        _lock.AcquireWriterLock(Timeout.Infinite);
        try {
            if (File.Exists(_fileName)) {
                try {
                    File.Delete(_fileName);
                }
                catch { success = false; }
            }
        }
        finally {
            _lock.ReleaseWriterLock();
        }
        return success;
    }

    /// <span class="code-SummaryComment"><summary></span>
    /// Loads the data from the filesystem. For deserialization a XmlSeralizer is used.
    /// <span class="code-SummaryComment"></summary></span>
    public virtual T Load() {

        _lock.AcquireReaderLock(Timeout.Infinite);
        try {
            if (System.IO.File.Exists(_fileName)) {
                using (FileStream reader = File.Open(_fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) {
                    _value = (T)_serializer.Deserialize(reader);
                }
            }
            AddFileNotifier();
        }
        catch (Exception ex) {
            throw new Exception(
                string.Format("Unable to load persitable object from file {0}", _fileName), ex);
        }
        finally {
            _lock.ReleaseReaderLock();
        }
        return _value;
    }

    /// <span class="code-SummaryComment"><summary></span>
    /// Persists the data back to the filesystem
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="fileName">Name of the file.</param></span>
    public virtual void Save() {

        if (!IsEmpty) {
            _lock.AcquireWriterLock(Timeout.Infinite);
            try {
                string directory = Path.GetDirectoryName(_fileName);
                if (!Directory.Exists(directory))
                    Directory.CreateDirectory(directory);
                T value = _value;
                using (FileStream writer = File.Create(_fileName)) {
                    _serializer.Serialize(writer, value);
                }
            }
            catch (Exception ex) {
                throw new Exception(
                    string.Format("Unable to save persitable object to file {0}", _fileName), ex);
            }
            finally {
                _lock.ReleaseWriterLock();
            }
        }
    }

    #region - Cache -

    /// <span class="code-SummaryComment"><summary></span>
    /// 
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="value"></param></span>
    void AddFileNotifier() {
        HttpRuntime.Cache.Insert(_fileName, new object(), new CacheDependency(_fileName),
            Cache.NoAbsoluteExpiration, _slidingExpiration, CacheItemPriority.Normal, new CacheItemRemovedCallback(OnFileChanged));
    }

    /// <span class="code-SummaryComment"><summary></span>
    /// 
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="key"></param></span>
    /// <span class="code-SummaryComment"><param name="value"></param></span>
    /// <span class="code-SummaryComment"><param name="reason"></param></span>
    void OnFileChanged(string key, object value, CacheItemRemovedReason reason) {
        // invalidate value
        if (key == _fileName) _value = default(T);
    }
    #endregion
    #endregion
}

Points of Interest

You can find ASP.NET XmlProviders project and all the source code at http://codeplex.com/aspnetxmlproviders

History

  • 22.Mar.2008 - Initial release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Velio Ivanov
Web Developer
Bulgaria Bulgaria
No Biography provided

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web03 | 2.8.140721.1 | Last Updated 30 Mar 2008
Article Copyright 2008 by Velio Ivanov
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid