asp tutorials, asp.net tutorials, sample code, and Microsoft news from 15Seconds
Data Access  |   Troubleshooting  |   Security  |   Performance  |   ADSI  |   Upload  |   Email  |   Control Building  |   Component Building  |   Forms  |   XML  |   Web Services  |   ASP.NET  |   .NET Features  |   .NET 2.0  |   App Development  |   App Architecture  |   IIS  |   Wireless
 
Pioneering Active Server
 Power Search





Active News
15 Seconds Weekly Newsletter
• Complete Coverage
• Site Updates
• Upcoming Features

More Free Newsletters
Reference
News
Articles
Archive
Writers
Code Samples
Components
Tools
FAQ
Feedback
Books
Links
DL Archives
Community
Messageboard
List Servers
Mailing List
WebHosts
Consultants
Tech Jobs
15 Seconds
Home
Site Map
Press
Legal
Privacy Policy
internet.commerce














internet.com
IT
Developer
Internet News
Small Business
Personal Technology
International

Search internet.com
Advertise
Corporate Info
Newsletters
Tech Jobs
E-mail Offers

HardwareCentral
Compare products, prices, and stores at Hardware Central!

Creating a Flexible Configuration Library
By Robert Chartier
Rating: 3.6 out of 5
Rate this article


  • email this article to a colleague
  • suggest an article

    Introduction

    A recent project required a way for my .NET assemblies to read and write to disk. This project entailed using automated agents on a distributed network that were being spread out over various Internet Service Providers (ISPs). These agents needed to save and load a file from the disk. The agents' function was to periodically contact the server, retrieve its current settings, save them to disk, and use these settings until instructed to change. These changes happened on the fly and were performed by the systems administrators.

  • download source code

    The ISPs all had different requirements for how I could store this configuration data locally on their machines. Some required the following:

    Faced with all of these options I needed a way in which to have the agent itself transparently use these data-storage devices. I wanted to be able to construct a library for which I could easily change the storage device at design time so that these agents would be reading and writing to the correct location. I also wanted the library flexible enough so that if we needed to add a new storage device it could be accomplished easily. That is the main purpose of the library we will create.

    Lastly, note that 15 Seconds author Jeff Gonzalez initially came up with the idea of this entire library, including putting some of the foundation work in place.

    Later, this article takes advantage of serialization. One of the requirements for this library is to be able to serialize the configuration settings in either binary or Extensible Markup Language (XML) (or SOAP) format. If you are not familiar with serialization, or need a quick refresher, see my article "Serialization in the .NET Framework" (http://www.15seconds.com/issue/020903.htm). This article will use a system that includes the library I created in my previous article, with a few minor upgrades to the code.

    Since we will be storing our configuration data on remote machines that we have no control over, there's a potential security risk involved. Thus we want to be able to encrypt this configuration information before saving it to the storage device. I have taken the liberty to borrow some code from an MSDN "How To" article named "How To: Create an Encryption Library" (see http://msdn.microsoft.com/library/en-us/dnnetsec/html/SecNetHT10.asp). I have tried to take most of that code line by line and apply it to our solution here, but you will see some obvious differences in how we use the library.

    With all of our requirements listed, let's consider the library and its objects that we will need to create. Table 1.0 below outlines each of the requirements above, with a description of what is needed.

    Table 1.0 Requirements and Descriptions of the Library

    Requirement

    Description

     

     

    Data Collection Object

    A generic NameValueObject Collection

     

     

    Storage Devices Library

    An abstract class to force the contract of all storage devices

    --File Storage

    Derivative to read/write to files only

    --Isolated Storage

    Derivative to read/write to isolated storage, with added functionality for all of IS options

     

     

    Serialization Library

    A class to handle serialization of objects in binary or xml format

     

     

    Encryption Library

    A library to handle encryption of the data before saving to disk

     

     

    Configuration Settings Object

    An object which binds together all other objects/libraries above

    With it mapped out, let's throw it all together, visually, for an understanding of how these various objects and libraries will interact with each other. Figure 1.0 below demonstrates this.


    Figure 1.0 Data Flow Diagram for the Library

    Notice how our Configuration Settings Object sits at the top of all other objects and libraries. It binds all other items together and orchestrates the flow of data to the necessary objects.

    The following sections will delve into most of the objects closely to examine their purpose and the relevant portions of the source. The encryption library and serialization library will not be covered in detail because they have already been addressed in the links provided above.

    Notice I have many projects in a single solution, and many times each of these projects will only have a single file in each. The reason for this is I like to reuse each of these projects in portions of my other applications. Since VS .NET will create a single assembly for each project, this allows me to have an assembly created for each item I want to reuse. I only need to create project references in this single solution, or normal file references to the assemblies, for other solutions that need to use them. This creates a very reusable infrastructure for your applications.

    Lastly, one quick point to notice is that I will only have one class per file, and one file per class, and the name of that file is the name of the class. This allows for an extremely easy-to-use and navigate solution and project structure.

    Data Collection Object

    The data collection object is a good place to start because it's probably the simplest of all within our library. Since we will only need to store configuration data, it all boils down to name/value pairs. We will have only a name and a value for each item that we will store. Since this will remain a constant, we can use a fairly generic collection to hold this information.

    Within the .NET Framework we have an existing base class that we can take advantage of -- System.Collections.Specialized.NameObjectCollectionBase. According to the documentation, this class:

    Provides the abstract (MustInherit in Visual Basic) base class for a sorted collection of associated String keys and Object values that can be accessed either with the key or with the index. (See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemcollectionsspecializednameobjectcollectionbaseclasstopic.asp)

    This is exactly what we need to do for our library, so let's take time to create a class derived from this base class. Before we dive into the code, there is one last thing that we need to cover. Since this class is going to be the core location of where/how the data is stored, we need the ability to serialize this class. As some of us know, there are many collections within the framework that cannot be serialized, so we must ensure that we create an object that is serializable. In the documentation you will notice that the class is marked as [Serializable], thus it will suit our needs perfectly. All we will need to do is implement the System.Runtime.Serialization.ISerializable interface for our derived class. The following is the code listing for our generic, serializable, NameObjectCollection.

    
    using System;
    
    namespace CustomCollections {
    	[Serializable]
    	public class NameObjectCollection : System.Collections.Specialized.NameObjectCollectionBase, 
    System.Runtime.Serialization.ISerializable {
    
    		public NameObjectCollection() {}
    
    		public void Add(string name, object value) { base.BaseAdd(name, 
    value); }
    		public void Remove(string name){base.BaseRemove(name);}
    		
    public object this[string name] {
    			get {
    				return base.BaseGet(name);
    			}
    		}
    		public object this[int i] {
    			get {
    				return base.BaseGet(i);
    			}
    		}
    		public NameObjectCollection (System.Runtime.Serialization.SerializationInfo info, 
    System.Runtime.Serialization.StreamingContext context) {
    			System.Runtime.Serialization.SerializationInfoEnumerator 
    infoItems = info.GetEnumerator();
    			while(infoItems.MoveNext()) {
    				base.BaseAdd(infoItems.Name, infoItems.Value);
    			}
    
    		}
    
    		public override void GetObjectData(System.Runtime.Serialization.SerializationInfo 
    info,System.Runtime.Serialization.StreamingContext context) {
    			foreach(string name in base.BaseGetAllKeys()) {
    				try {
    					info.AddValue(name, base.BaseGet(name));
    				} catch(Exception){}
    			}
    		}
    
    	}
    }
    
    
    We needed to add the "Add", "Remove", and two "this" property accessors (int and string names) members to the class in order to satisfy the needs of the collection. This gives us the rudimentary ability to add, remove, and retrieve items based on their index or name.

    The last two methods are needed to satisfy the serialization requirements of the object -- the constructor, which is used to create the object from serialized data, and the GetObjectData(), which is called during the serialization process to gather the internal pieces of data you want to add to the serialized data.

    Storage Library

    Our storage library is used to create classes that allow us to read and write using various storage devices. These devices could consist of files, isolated storage, SQL Server, or any other sort of storage. This article covers file and isolated storage and ignores SQL Server storage. Consult the source in order to see its implementation.

    In order for our storage library to be able to have many alternative devices to read and write to, we need a way to represent each of these devices in the same manner. The easiest way to do this is allow for each of them to derive from some common base class and force the required contract from that base class. We need to define a base class with generic read/write abilities and have our derived classes implement each member on its own.

    If you recall, a few sections back, I explained to you that I intentionally created may projects in this single solution because I wanted to be able to reuse as much of this library as possible. This class could easily be reused, as can most of the storage classes below.

    The Storage Base Class

    This class is a very simple, small class. It is marked as abstract, with only two abstract methods and the default constructor.

    
    using System;
    
    namespace Storage {
    	/// <summary>
    	/// The base class for all of our storage devices
    	/// </summary>
    	public abstract class BaseStorage {
    		public BaseStorage() {}
    		public abstract void Save(string file, System.IO.MemoryStream 
    contents);
    		public abstract System.IO.MemoryStream Load(string file);		
    
    	}
    }
    
    
    The two methods, Save() and Load(), will be implemented in our derived classes to save and load the System.IO.MemoryStream's from their specific devices.

    The FileStorage Derived Class

    This class, which is derived from the BaseStorage class, is the simplest implementation of a storage device. It merely implements the Save() and Load() methods with the code necessary to read and write the System.IO.MemoryStream to the provided location on disk.

    
    using System;
    
    namespace Storage {
    	/// <summary>
    	/// A FileStorage Device.
    	/// </summary>
    	public class FileStorage : BaseStorage {
    		public FileStorage() {}
    		public override void Save(string file, System.IO.MemoryStream 
    contents){
    			System.IO.FileStream writer = System.IO.File.OpenWrite(file);
    			byte[] data = contents.ToArray();
    			writer.Write(data, 0, data.Length);
    			writer.Flush();
    			writer.Close();
    		}
    		public override System.IO.MemoryStream Load(string file) {
    			if(System.IO.File.Exists(file)) {
    				System.IO.FileStream reader = 
    System.IO.File.OpenRead(file);
    				byte[] contents = new byte[reader.Length];
    				reader.Read(contents, 0, (int)reader.Length);
    				reader.Close();
    				return new System.IO.MemoryStream(contents);
    			} else {
    				return null;
    			}
    		}
    
    	}
    }
    
    

    The Isolated Storage Class

    This class is a bit more complex because of the nature and flexibility of isolated storage itself. It obviously needs to satisfy the basic requirements of the base class, with its Save() and Load() members.

    
    using System;
    
    namespace Storage {
    	/// <summary>
    	/// IsolatedStorage device derived class.
    	/// </summary>
    	public class IsolatedStorage : BaseStorage {
    		protected System.IO.IsolatedStorage.IsolatedStorageScope scope;
    		/// <summary>
    		/// Anything other than Domain or Assembly you must also provide 
    both DomainIdentity and AssemblyIdentity
    		/// </summary>
    		public System.IO.IsolatedStorage.IsolatedStorageScope 
    Scope{get{return scope;}set{scope=value;}}
    		protected object domainIdentity;
    		protected object assemblyIdentity;
    		public object DomainIdentity{get{return 
    domainIdentity;}set{domainIdentity=value;}}
    		public object AssemblyIdentity{get{return 
    assemblyIdentity;}set{assemblyIdentity=value;}}
    
    		public IsolatedStorage() {}
    
    		public override void Save(string file, System.IO.MemoryStream 
    contents){
    			System.IO.IsolatedStorage.IsolatedStorageFile isoStorage = 
    GetISOStorage();
    
    			System.IO.IsolatedStorage.IsolatedStorageFileStream writer = 
    				new 
    System.IO.IsolatedStorage.IsolatedStorageFileStream(file, 
    System.IO.FileMode.Create, isoStorage);
    			
    			byte[] data = contents.ToArray();
    			writer.Write(data, 0, data.Length);
    			writer.Close();
    			isoStorage.Close();
    		}
    
    		public override System.IO.MemoryStream Load(string file){
    			System.IO.IsolatedStorage.IsolatedStorageFile isoStorage = 
    GetISOStorage();			
    			string[] storeNames = isoStorage.GetFileNames(file);
    			if(storeNames==null || storeNames.Length==0) 
    				return null;
    			else {
    			
    				System.IO.IsolatedStorage.IsolatedStorageFileStream 
    reader = 
    					new 
    System.IO.IsolatedStorage.IsolatedStorageFileStream(file, 
    System.IO.FileMode.Open, isoStorage);				
    				
    				byte[] contents = new byte[reader.Length];
    				reader.Read(contents, 0, (int)reader.Length);
    				reader.Close();
    				isoStorage.Close();
    				return new System.IO.MemoryStream(contents);
    
    			}
    		}
    
    
    		protected System.IO.IsolatedStorage.IsolatedStorageFile 
    GetISOStorage() {
    			System.IO.IsolatedStorage.IsolatedStorageFile isoStorage;
    			switch(scope) {
    				case 
    System.IO.IsolatedStorage.IsolatedStorageScope.Domain:
    					isoStorage = 
    System.IO.IsolatedStorage.IsolatedStorageFile.GetUserStoreForDomain();
    					break;
    				case 
    System.IO.IsolatedStorage.IsolatedStorageScope.Assembly:
    					isoStorage = 
    System.IO.IsolatedStorage.IsolatedStorageFile.GetUserStoreForAssembly();
    					break;
    				default:
    					isoStorage = 
    System.IO.IsolatedStorage.IsolatedStorageFile.GetStore(scope,domainIdentity, 
    assemblyIdentity);
    					break;
    			}
    			return isoStorage;
    		}
    
    
    	}
    }
    
    
    This is basically the same as normal File IO, except when using isolated storage you first need to get the store from a number of predefined scopes, and then you can use that isolated storage file to create a IsolatedStorageFileStream to either read or write.

    With two storage devices -- File and IsolatedStorage -- both derived from the same base class, we have the ability to save and load from either device easily.

    The Configuration Settings Object

    The Configuration Settings Object is the hub of this library. It is where most of the coordination of the work takes place. This object brings together the functionality of all the rest of the portions of the library and allows us to interact with it easily.

    Within it, we have an instance of the NameObjectCollection described above, which we use to add and remove items from our custom collection. It also gives you the ability to choose your storage device, with options for XML and binary serialization, encryption, etc. Lastly, it is responsible for using all of the specified options when loading and saving the data. These two methods are what we will take a closer look at now.

    Saving
    First, we need to be able to save our NameObjectCollection. This is a fairly straightforward process involving three simple steps:

    1. Serializing the data
    2. Encrypting the data, if need be, and
    3. Asking the storage device to save the data
    Serialization of the data is a fairly simple matter in our case, mostly because I have provided a class library with all the members necessary to handle SOAP, binary, or XML serialization for us. Look at the source solution for this article and notice the CustomSerialization project, and within that, the CustomSerialization class. It is merely a collection of static methods that you can send your objects to and have them (de)serialized and returned for you, automatically. Let's take a look at how we will implement this in our Configuration Settings Object:
    
    System.IO.MemoryStream data;
    //deserialize based on storage format
    switch(Format) {
    	case StorageFormats.Xml:
    		data = CustomSerialization.Serialize.SerializeSOAP(config);
    		break;
    	default:
    		//binary format
    		data = CustomSerialization.Serialize.SerializeBinary(config);
    		break;
    }
    
    
    First, we will need a memory stream to hold the content after we perform serialization. This is the variable data in the block of code above. Next, since we have the option of either going XML or binary for the actual format of the serialization process, we need to switch off on the chosen format and call the appropriate static method in our CustomSerialization project to get the result. Once this is done, we can treat our serialized content like any other memory stream.

    With the serialization completed, we can optionally choose to encrypt this data before sending it to our storage device. If there is a provided password, then we will encrypt the data. Here is the relevant block of code:

    
    if(password!=null && password!="") { 
    	byte[] IV = Encoding.ASCII.GetBytes(iv);
    	byte[] cipherText = null;
    	byte[] key = Encoding.ASCII.GetBytes(password);
    	
    
    	//encrypt data				
    	Encryption.Encryptor enc = new Encryption.Encryptor(algorithm);
    
    	enc.IV = IV;
    	cipherText = enc.Encrypt(data.ToArray(), key);
    	key = enc.Key;
    	data = new System.IO.MemoryStream(cipherText);
    
    }
    
    
    Notice that we are calling the "Encrypt" method from the encryption library and passing to it a byte[] of our serialized memory stream. This is a requirement of the encryption library.

    This memory stream is now ready to be sent to our storage device and allow it to handle saving it.

    Loading
    The last concept we need to cover is how to reload the settings from our storage device. We will obviously need to create the Configuration Settings Object with the same options as we did when saving it:

    • choosing the right serialization format,
    • encryption key,
    • and location.

    Given the flexibility of all these options, you may want to create a class within your solution that will initialize the Configuration Settings Object properly, according to your needs, each time.

    For example, we are creating a photo album, and we need to save the meta-data information regarding each photo (description, title, etc..) using our library. I would create an instance Configuration Settings Object within the photo album source that set up the Configuration Settings properly, and according to our needs. This would be a design-time issue and remain constant until the programmer decided it was necessary to change something. Another option would be to use app.config to specify these details and pull the settings out of there.

    Regardless of how you do it, in the end we have a Configuration Settings Object properly loaded with the correct settings that match those that we used when saving. The load process is essentially the reverse of what we did when we saved:

    1. Load the memory stream from the storage device.
    2. If needed, decrypt the memory stream.
    3. Choose the right deserialization method, and deserialize.
    Let's take a close look at each of these steps.

    The first was to load the data from the storage device:

    
    config = new CustomCollections.NameObjectCollection();
    if(storage==null) storage = new Storage.FileStorage();
    System.IO.MemoryStream data = storage.Load(filePath);
    
    
    First, we re-create our config object, which is simply a NameObjectCollection. We then need to create an instance of our storage device, if it has not already been created during the construction of the class or by the calling application. And finally we call the .Load() method to retrieve the data from the device.

    Now let's attempt to decrypt the memory stream, if it's been encrypted.:

    
    if(password!=null && password!="") { 
    	Encryption.Decryptor dec = new Encryption.Decryptor(algorithm);
    
    	byte[] key = Encoding.ASCII.GetBytes(password);
    	byte[] IV = Encoding.ASCII.GetBytes(iv);
    	dec.IV=IV;
    	data = new System.IO.MemoryStream(dec.Decrypt(data.ToArray(), key));
    
    }
    
    
    So in this case, as previously, we only choose to decrypt when the caller has provided us with a password. Next let's take our decrypted stream and deserialize it.
    
    //deserialize based on storage format
    switch(Format) {
    	case StorageFormats.Xml:
    		config = (CustomCollections.NameObjectCollection) 
    CustomSerialization.Serialize.DeSerializeSOAP(data);
    		break;
    	default:
    		//binary format
    		config = (CustomCollections.NameObjectCollection) 
    CustomSerialization.Serialize.DeSerializeBinary(data);
    		break;
    }
    data.Close();
    
    
    Notice that after deserialization, we are casting it back into our NameObjectCollection, thus completing the process of loading the data.

    Conclusion

    In this article we address the need for a flexible configuration library within our .NET applications. We have seen how easy it is to add features, such as serialization, encryption, and any number of storage devices for any sort of storage medium that we have.

    In the final stages of creating this library I decided to create a new storage device and publish it along with this article. It uses SQL Server to hold the data. I have provided the SQL in order to create the database, table, and stored procedures that accompany the many examples provided in the source code. In those examples, you will see how to use the library to its greatest potential.

    If you have any questions or comments, please feel free to contact me.

    Sources

    Jeff Gonzalez has been working in the IT industry for the last six years. He started his IT career as an NT4 administrator and network engineer. While working for a hosting company, he recognized the power of Windows DNA and sought out to learn everything he could about it. Since his foray into the Internet development world, he has worked on several e-commerce, e-business, and intranet applications. He can be reached at rig444@hotmail.com.

    About the Author

    Robert Chartier has developed IT solutions for more than nine years with a diverse background in both software and hardware development. He is internationally recognized as an innovative thinker and leading IT architect with frequent speaking engagements showcasing his expertise. He's been an integral part of many open forums on cutting-edge technology, including the .NET Framework and Web Services. His current position as vice president of technology for Santra Technology (http://www.santra.com) has allowed him to focus on innovation within the Web Services market space.

    He uses expertise with many Microsoft technologies, including .NET, and a strong background in Oracle, BEA Systems, Inc.'s BEA WebLogic, IBM, Java 2 Platform Enterprise Edition (J2EE), and similar technologies to support his award-winning writing. He frequently publishes to many of the leading developer and industry support Web sites and publications. He has a bachelor's degree in Computer Information Systems.

    Robert Chartier can be reached at rob@santra.com.

  • Rate This Article
    Not HelpfulMost Helpful
    1 2 3 4 5
    Other Articles
    Jul 28, 2005 - N-Tier Web Applications using ASP.NET 2.0 and SQL Server 2005 - Part 2
    In the second part of his series on building N-tier web applications using ASP.NET 2.0 and SQL Server 2005, Thiru Thangarathinam covers the business logic and user interface layers. In the process, he also examines some new features in ASP.NET 2.0 that greatly simplify the development process.
    [Read This Article]  [Top]
    Jul 14, 2005 - An Innovative Technique for Creating Reusuable Page Templates in ASP.NET 1.x
    Code reusuability is one of the major goals of any good object-oriented programmer. While the ASP.NET framework has made code reusuability easier and more elegant than it was in classic ASP, one area where reusuability could be improved is at the UI level. This article outlines a technique that you can use in ASP.NET 1.x that allows every page in your web application to inherit not only the functionality of a base page, but its UI as well.
    [Read This Article]  [Top]
    Jun 23, 2005 - Monitoring SharePoint Usage through an ASP.NET Web Application
    In this article, Gayan Peiris looks at creating an ASP.NET web application that will display the usage details of a selected SharePoint site. Building such an application enables SharePoint administrators to gather all SharePoint usage data from a central location.
    [Read This Article]  [Top]
    May 12, 2005 - Retrieving SharePoint Site Information in an ASP.NET Web Application
    In this article, Gayan Peiris examines using the SharePoint Object Model to access SharePoint site information from an ASP.NET web application. It should be of particular interest to SharePoint administrators who can use the included code as a starting point for development of their own web-based SharePoint administration application.
    [Read This Article]  [Top]
    Dec 23, 2004 - Automated Deployment for Side By Side .NET Web Apps for Visual Studio .NET 2003
    In this article, David Every outlines a step-by-step account of how he solved the problems he encountered while implementing an auto-deployment process. He also describes how to create a stable process for automated remote .NET deployment featuring "side-by-side" capability.
    [Read This Article]  [Top]
    Sep 29, 2004 - Developing Web Parts with ICellConsumer Interface
    Most default SharePoint Server Web Parts can be connected across organizations. The third article in this series shows how to develop connectable Web Parts that consume information provided by other Web Parts.
    [Read This Article]  [Top]
    Aug 10, 2004 - Implementing and Promoting Daily Builds
    Automatic daily builds is a well known software engineering best practice. This article introduces a strategy for implementing and promoting daily builds and offers tips and tricks for preventing and fixing breaks.
    [Read This Article]  [Top]
    Jun 21, 2004 - Using Open Source .NET Tools for Sophisticated Builds
    Building an application can be more than pressing F5. With an increasing number of quality packages being released, developers for the .NET platform now have options to create a very sophisticated build process. Aaron Junod describes a sample build environment and shows how a number of tools can work together to make reliable, predictable, and value-added builds.
    [Read This Article]  [Top]
    Jun 24, 2003 - Programming for the Palm Part 1 - Creating the Palm Application
    The first part of this three part series walks through the process of creating a mobile blog application using a BASIC development environment for Palm OS devices called NS Basic. Subsequent articles will focus on synchronizing the data to the desktop using C# and creating an installer.
    [Read This Article]  [Top]
    Jun 18, 2003 - Online Database Functions Testing Tool
    This short article provides source code for a classic ASP online database functions testing application and shows how to configure and use the tool for either SQL Server or Oracle.
    [Read This Article]  [Top]
    Mailing List
    Want to receive email when the next article is published? Just Click Here to sign up.

    Support the Active Server Industry



    JupiterOnlineMedia

    internet.comearthweb.comDevx.commediabistro.comGraphics.com

    Search:

    Jupitermedia Corporation has two divisions: Jupiterimages and JupiterOnlineMedia

    Jupitermedia Corporate Info


    Legal Notices, Licensing, Reprints, & Permissions, Privacy Policy.

    Advertise | Newsletters | Tech Jobs | Shopping | E-mail Offers