Click here to Skip to main content
15,893,190 members
Articles / Programming Languages / C#

Read/Write XML files, Config files, INI files, or the Registry

Rate me:
Please Sign up or sign in to vote.
4.92/5 (256 votes)
20 Feb 200513 min read 1.5M   44.3K   616  
A class library for reading/writing XML files, config files, INI files, or the Registry using one simple interface.
/*
 * AMS.Profile Class Library
 * 
 * Written by Alvaro Mendez
 * Copyright (c) 2005. All Rights Reserved.
 * 
 * The AMS.Profile namespace contains interfaces and classes that 
 * allow reading and writing of user-profile data.
 * This file contains the helper classes for the Xml-based Profile classes.
 * 
 * The code is thoroughly documented, however, if you have any questions, 
 * feel free to email me at alvaromendez@consultant.com.  Also, if you 
 * decide to this in a commercial application I would appreciate an email 
 * message letting me know.
 *
 * This code may be used in compiled form in any way you desire. This
 * file may be redistributed unmodified by any means providing it is 
 * not sold for profit without the authors written consent, and 
 * providing that this notice and the authors name and all copyright 
 * notices remains intact. This file and the accompanying source code 
 * may not be hosted on a website or bulletin board without the author's 
 * written permission.
 * 
 * This file is provided "as is" with no expressed or implied warranty.
 * The author accepts no liability for any damage/loss of business that
 * this product may cause.
 *
 * Last Updated: Feb. 17, 2005
 */


using System;
using System.Xml;
using System.Text;
using System.IO;
using System.Security;

namespace AMS.Profile
{
	/// <summary>
	///   Abstract base class for all XML-based Profile classes. </summary>
	/// <remarks>
	///   This class provides common methods and properties for the XML-based Profile classes 
	///   (<see cref="Xml" />, <see cref="Config" />). </remarks>
	public abstract class XmlBased : Profile
	{
		private Encoding m_encoding = Encoding.UTF8;
		internal XmlBuffer m_buffer;

		/// <summary>
		///   Initializes a new instance of the XmlBased class by setting the <see cref="Profile.Name" /> to <see cref="Profile.DefaultName" />. </summary>
		protected XmlBased()
		{
		}

		/// <summary>
		///   Initializes a new instance of the XmlBased class by setting the <see cref="Profile.Name" /> to the given file name. </summary>
		/// <param name="fileName">
		///   The name of the file to initialize the <see cref="Profile.Name" /> property with. </param>
		protected XmlBased(string fileName) :
			base(fileName)
		{
		}

		/// <summary>
		///   Initializes a new instance of the XmlBased class based on another XmlBased object. </summary>
		/// <param name="profile">
		///   The XmlBased profile object whose properties and events are used to initialize the object being constructed. </param>
		protected XmlBased(XmlBased profile) :
			base(profile)
		{
			m_encoding = profile.Encoding;
		}

		/// <summary>
		///   Retrieves an XmlDocument object based on the <see cref="Profile.Name" /> of the file. </summary>
		/// <returns>
		///   If <see cref="Buffering" /> is not enabled, the return value is the XmlDocument object loaded with the file, 
		///   or null if the file does not exist. If <see cref="Buffering" /> is enabled, the return value is an 
		///   XmlDocument object, which will be loaded with the file if it already exists.</returns>
		/// <exception cref="InvalidOperationException">
		///	  <see cref="Profile.Name" /> is null or empty. </exception>
		/// <exception cref="XmlException">
		///	  Parse error in the XML being loaded from the file. </exception>
		protected XmlDocument GetXmlDocument()
		{
			if (m_buffer != null)
				return m_buffer.XmlDocument;

			VerifyName();
			if (!File.Exists(Name))
				return null;

			XmlDocument doc = new XmlDocument();
			doc.Load(Name);
			return doc;
		}

		/// <summary>
		///   Saves any changes pending on an XmlDocument object, unless <see cref="Buffering" /> is enabled. </summary>
		/// <exception cref="XmlException">
		///	  The resulting XML document would not be well formed. </exception>
		/// <remarks>
		///   If <see cref="Buffering" /> is enabled, this method sets the <see cref="XmlBuffer.NeedsFlushing" /> property to true 
		///   and the changes are not saved until the buffer is flushed (or closed).  If the Buffer is not active
		///   the contents of the XmlDocument object are saved to the file. </remarks>
		protected void Save(XmlDocument doc)
		{
			if (m_buffer != null)
				m_buffer.m_needsFlushing = true;
			else
				doc.Save(Name);

		}

		/// <summary>
		///   Activates buffering on this XML-based profile object, if not already active. </summary>
		/// <param name="lockFile">
		///   If true, the file is locked when the buffer is activated so that no other processes can write to it.  
		///   If false, other processes can continue writing to it and the actual contents of the file can get 
		///   out of synch with the contents of the buffer. </param>
		/// <returns>
		///   The return value is an <see cref="XmlBuffer" /> object that may be used to control the buffer used
		///   to read/write values from this XmlBased profile.  </returns>
		/// <exception cref="InvalidOperationException">
		///	  Attempting to lock the file  and the name is null or empty. </exception>
		/// <exception cref="SecurityException">
		///	  Attempting to lock the file without the required permission. </exception>
		/// <exception cref="UnauthorizedAccessException">
		///	  Attempting to lock the file and ReadWrite access is not permitted by the operating system. </exception>
		/// <remarks>
		///   <i>Buffering</i> is the caching of an <see cref="XmlDocument" /> object so that subsequent reads or writes
		///   are all done through it.  This dramatically increases the performance of those operations, but it requires
		///   that the buffer is flushed (or closed) to commit any changes done to the underlying file.
		///   <para>
		///   The XmlBuffer object is created and attached to this XmlBased profile object, if not already present.
		///   If it is already attached, the same object is returned in subsequent calls, until the object is closed. </para>
		///   <para>
		///   Since the XmlBuffer class implements <see cref="IDisposable" />, the <c>using</c> keyword in C# can be 
		///   used to conveniently create the buffer, write to it, and then automatically flush it (when it's disposed).  
		///   Here's an example:
		///   <code> 
		///   using (profile.Buffer(true))
		///   {
		///      profile.SetValue("A Section", "An Entry", "A Value");
		///      profile.SetValue("A Section", "Another Entry", "Another Value");
		///      ...
		///   }
		///   </code></para></remarks>
		/// <seealso cref="XmlBuffer" />
		/// <seealso cref="Buffering" />
		public XmlBuffer Buffer(bool lockFile)
		{
			if (m_buffer == null)
				m_buffer = new XmlBuffer(this, lockFile);
			return m_buffer; 
		}

		/// <summary>
		///   Activates <i>locked</i> buffering on this XML-based profile object, if not already active. </summary>
		/// <returns>
		///   The return value is an <see cref="XmlBuffer" /> object that may be used to control the buffer used
		///   to read/write values from this XmlBased profile.  </returns>
		/// <exception cref="InvalidOperationException">
		///	  Attempting to lock the file  and the name is null or empty. </exception>
		/// <exception cref="SecurityException">
		///	  Attempting to lock the file without the required permission. </exception>
		/// <exception cref="UnauthorizedAccessException">
		///	  Attempting to lock the file and ReadWrite access is not permitted by the operating system. </exception>
		/// <remarks>
		///   <i>Buffering</i> refers to the caching of an <see cref="XmlDocument" /> object so that subsequent reads or writes
		///   are all done through it.  This dramatically increases the performance of those operations, but it requires
		///   that the buffer is flushed (or closed) to commit any changes done to the underlying file.
		///   <para>
		///   The XmlBuffer object is created and attached to this XmlBased profile object, if not already present.
		///   If it is already attached, the same object is returned in subsequent calls, until the object is closed. </para>
		///   <para>
		///   If the buffer is created, the underlying file (if any) is locked so that no other processes 
		///   can write to it. This is equivalent to calling Buffer(true). </para>
		///   <para>
		///   Since the XmlBuffer class implements <see cref="IDisposable" />, the <c>using</c> keyword in C# can be 
		///   used to conveniently create the buffer, write to it, and then automatically flush it (when it's disposed).  
		///   Here's an example:
		///   <code> 
		///   using (profile.Buffer())
		///   {
		///      profile.SetValue("A Section", "An Entry", "A Value");
		///      profile.SetValue("A Section", "Another Entry", "Another Value");
		///      ...
		///   }
		///   </code></para></remarks>
		/// <seealso cref="XmlBuffer" />
		/// <seealso cref="Buffering" />
		public XmlBuffer Buffer()
		{
			return Buffer(true);
		}

		/// <summary>
		///   Gets whether buffering is active or not. </summary>
		/// <remarks>
		///   <i>Buffering</i> is the caching of an <see cref="XmlDocument" /> object so that subsequent reads or writes
		///   are all done through it.  This dramatically increases the performance of those operations, but it requires
		///   that the buffer is flushed (or closed) to commit any changes done to the underlying file.
		///   <para>
		///   This property may be used to determine if the buffer is active without actually activating it.  
		///   The <see cref="Buffer" /> method activates the buffer, which then needs to be flushed (or closed) to update the file. </para></remarks>
		/// <seealso cref="Buffer" />
		/// <seealso cref="XmlBuffer" />
		public bool Buffering
		{
			get 
			{
				return m_buffer != null;
			}
		}

		/// <summary>
		///   Gets or sets the encoding, to be used if the file is created. </summary>
		/// <exception cref="InvalidOperationException">
		///   Setting this property if <see cref="Profile.ReadOnly" /> is true. </exception>
		/// <remarks>
		///   By default this property is set to <see cref="System.Text.Encoding.UTF8">Encoding.UTF8</see>, but it is only 
		///   used when the file is not found and needs to be created to write the value. 
		///   If the file exists, the existing encoding is used and this value is ignored. 
		///   The <see cref="Profile.Changing" /> event is raised before changing this property.  
		///   If its <see cref="ProfileChangingArgs.Cancel" /> property is set to true, this method 
		///   returns immediately without changing this property.  After the property has been changed, 
		///   the <see cref="Profile.Changed" /> event is raised. </remarks>
		public Encoding Encoding
		{
			get 
			{ 
				return m_encoding; 
			}
			set 
			{ 
				VerifyNotReadOnly();
				if (m_encoding == value)
					return;
						
				if (!RaiseChangeEvent(true, ProfileChangeType.Other, null, "Encoding", value))
					return;

				m_encoding = value; 				
				RaiseChangeEvent(false, ProfileChangeType.Other, null, "Encoding", value);				
			}
		}
	}

	/// <summary>
	///   Buffer class for all <see cref="XmlBased" /> Profile classes. </summary>
	/// <remarks>
	///   This class provides buffering functionality for the <see cref="XmlBased" /> classes.
	///   <i>Buffering</i> refers to the caching of an <see cref="XmlDocument" /> object so that subsequent reads or writes
	///   are all done through it.  This dramatically increases the performance of those operations, but it requires
	///   that the buffer is flushed (or closed) to commit any changes done to the underlying file. 
	///   <para>
	///   Since an XmlBased object can only have one buffer attached to it at a time, this class may not
	///   be instanciated directly.  Instead, use the <see cref="XmlBased.Buffer" /> method of the profile object. </para></remarks>
	/// <seealso cref="XmlBased.Buffer" />
	public class XmlBuffer : IDisposable
	{
		private XmlBased m_profile;
		private XmlDocument m_doc;
		private FileStream m_file;
		internal bool m_needsFlushing;

		/// <summary>
		///   Initializes a new instance of the XmlBuffer class and optionally locks the file. </summary>
		/// <param name="profile">
		///   The XmlBased object to associate with the buffer and to assign this object to. </param>
		/// <param name="lockFile">
		///   If true and the file exists, the file is locked to prevent other processes from writing to it
		///   until the buffer is closed. </param>
		/// <exception cref="InvalidOperationException">
		///	  Attempting to lock the file  and the name is null or empty. </exception>
		/// <exception cref="SecurityException">
		///	  Attempting to lock the file without the required permission. </exception>
		/// <exception cref="UnauthorizedAccessException">
		///	  Attempting to lock the file and ReadWrite access is not permitted by the operating system. </exception>
		internal XmlBuffer(XmlBased profile, bool lockFile)
		{
			m_profile = profile;

			if (lockFile)
			{
				m_profile.VerifyName();
				if (File.Exists(m_profile.Name))
					m_file = new FileStream(m_profile.Name, FileMode.Open, m_profile.ReadOnly ? FileAccess.Read : FileAccess.ReadWrite, FileShare.Read);
			}
		}

		/// <summary>
		///   Loads the XmlDocument object with the contents of an XmlTextWriter object. </summary>
		/// <param name="writer">
		///   The XmlTextWriter object to load the XmlDocument with. </param>
		/// <remarks>
		///   This method is used to load the buffer with new data. </remarks>
		internal void Load(XmlTextWriter writer)
		{
			writer.Flush();
			writer.BaseStream.Position = 0;
			m_doc.Load(writer.BaseStream);

			m_needsFlushing = true;
		}

		/// <summary>
		///   Gets the XmlDocument object associated with this buffer, based on the profile's Name. </summary>
		/// <exception cref="InvalidOperationException">
		///	  <see cref="Profile.Name" /> is null or empty. </exception>
		/// <exception cref="XmlException">
		///	  Parse error in the XML being loaded from the file. </exception>
		internal XmlDocument XmlDocument
		{
			get
			{
				if (m_doc == null)
				{
					m_doc = new XmlDocument();

					if (m_file != null)
					{
						m_file.Position = 0;
						m_doc.Load(m_file);
					}
					else
					{
						m_profile.VerifyName();
						if (File.Exists(m_profile.Name))
							m_doc.Load(m_profile.Name);
					}
				}
				return m_doc;
			}
		}

		/// <summary>
		///   Gets whether the buffer's XmlDocument object is empty. </summary>
		internal bool IsEmpty
		{
			get
			{
				return XmlDocument.InnerXml == String.Empty;
			}
		}

		/// <summary>
		///   Gets whether changes have been made to the XmlDocument object that require
		///   the buffer to be flushed so that the file gets updated. </summary>
		/// <remarks>
		///   This property returns true when the XmlDocument object has been changed and the 
		///   <see cref="Flush" /> (or <see cref="Close" />) method needs to be called to 
		///   update the file. </remarks>
		/// <seealso cref="Flush" />
		/// <seealso cref="Close" />
		public bool NeedsFlushing
		{
			get
			{
				return m_needsFlushing;
			}
		}

		/// <summary>
		///   Gets whether the file associated with the buffer's profile is locked. </summary>
		/// <remarks>
		///   This property returns true when this object has been created with the <i>lockFile</i> parameter set to true,
		///   provided the file exists.  When locked, other processes will not be allowed to write to the profile's
		///   file until the buffer is closed. </remarks>
		/// <seealso cref="Close" />
		public bool Locked
		{
			get
			{
				return m_file != null;
			}
		}

		/// <summary>
		///   Writes the contents of the XmlDocument object to the file associated with this buffer's profile. </summary>
		/// <remarks>
		///   This method may be used to explictly commit any changes made to the <see cref="XmlBased" /> profile from the time 
		///   the buffer was last flushed or created.  It writes the contents of the XmlDocument object to the profile's file.
		///   When the buffer is being closed (with <see cref="Close" /> or <see cref="Dispose" />) this method is 
		///   called if <see cref="NeedsFlushing" /> is true. After the buffer is closed, this method may not be called. </remarks>
		/// <exception cref="InvalidOperationException">
		///   This object is closed. </exception>
		/// <seealso cref="Close" />
		/// <seealso cref="Reset" />
		public void Flush()
		{
			if (m_profile == null)
				throw new InvalidOperationException("Cannot flush an XmlBuffer object that has been closed.");

			if (m_doc == null)
				return;

			if (m_file == null)
				m_doc.Save(m_profile.Name);
			else
			{
				m_file.SetLength(0);
				m_doc.Save(m_file);
			}

			m_needsFlushing = false;
		}

		/// <summary>
		///   Resets the buffer by discarding its XmlDocument object. </summary>
		/// <remarks>
		///   This method may be used to rollback any changes made to the <see cref="XmlBased" /> profile from the time 
		///   the buffer was last flushed or created. After the buffer is closed, this method may not be called. </remarks>
		/// <exception cref="InvalidOperationException">
		///   This object is closed. </exception>
		/// <seealso cref="Flush" />
		/// <seealso cref="Close" />
		public void Reset()
		{
			if (m_profile == null)
				throw new InvalidOperationException("Cannot reset an XmlBuffer object that has been closed.");

			m_doc = null;
			m_needsFlushing = false;
		}

		/// <summary>
		///   Closes the buffer by flushing the contents of its XmlDocument object (if necessary) and dettaching itself 
		///   from its <see cref="XmlBased" /> profile. </summary>
		/// <remarks>
		///   This method may be used to explictly deactivate the <see cref="XmlBased" /> profile buffer. 
		///   This means that the buffer is flushed (if <see cref="NeedsFlushing" /> is true) and it gets 
		///   dettached from the profile. The <see cref="Dispose" /> method automatically calls this method. </remarks>
		/// <seealso cref="Flush" />
		/// <seealso cref="Dispose" />
		public void Close()
		{
			if (m_profile == null)
				return;
				
			if (m_needsFlushing)
				Flush();

			m_doc = null;
		
			if (m_file != null)
			{
				m_file.Close();
				m_file = null;
			}

			if (m_profile != null)
				m_profile.m_buffer = null;
			m_profile = null;
		}

		/// <summary>
		///   Disposes of this object's resources by closing the buffer. </summary>
		/// <remarks>
		///   This method calls <see cref="Close" />, which flushes the buffer and dettaches it from the profile. </remarks>
		/// <seealso cref="Close" />
		/// <seealso cref="Flush" />
		public void Dispose()
		{
			Close();
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

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


Written By
Web Developer
United States United States
I've done extensive work with C++, MFC, COM, and ATL on the Windows side. On the Web side, I've worked with VB, ASP, JavaScript, and COM+. I've also been involved with server-side Java, which includes JSP, Servlets, and EJB, and more recently with ASP.NET/C#.

Comments and Discussions