Monday, April 16, 2007

.NET Class Library Get Configuration File

A class library is used by different kind of applications, like a test console, a WinForm or Web applications. And the class library needs to read a configuration file at run time. How to do that? The common way is copy the configuration file to the folder where the class library assembly is sitting. Then the configuration file can be located by reflection at run time. But this doesn't work in ASP.NET web site environment because of the .NET dynamic compilation process. One resolution is to handle the path differently in web environment. Let's say if We have a convention of copying the configuration file to the root folder of web site, then the code to retrieve the full path of configuration file is like:
    public static string GetConfigurationFile(string fileName)
{
string asmLocation = Assembly.GetExecutingAssembly().Location;
string asmFolder = asmLocation.Substring(0, asmLocation.LastIndexOf(@"\"));
string fileFullPath = asmFolder + @"\" + fileName;

// Fix the temporary folder issue when running in web environment
if (fileFullPath.Contains(@"\Microsoft.NET\Framework"))
{
fileFullPath = System.Web.Hosting.HostingEnvironment.MapPath("/") + fileName;
}

return fileFullPath;
}
(Updated 2008/2) Another easy way to get configuration file is using AppDomain.CurrentDomain.SetupInformation.ConfigurationFile property which maps to app.config for Cosole/WinForm applications, and maps to web.config in ASP.NET.

Tuesday, April 03, 2007

StringDictionary, NameValueCollection, Dictionary<TKey, TValue> and KeyedCollection<TKey, TItem>

How to store key/value data in .NET?

DictionaryEntry in .NET 1.0 and generic KeyValuePair<TKey, TValue> in .NET 2.0 both store key/value pair inside a struct (not class). How about many KeyValue pairs? One solution is to put Key/value objects inside a collection like Generic List. But there's a major problem of such use: search by key is expensive and you need to loop through the whole collection. HashTable is fast in search but it's limited to string/object pair, and it's not strongly typed.

Are there any out-of-box strongly-typed collections for key/value objects?

If the key and value both are strings, StringDictionary and NameValueCollection are options. They have been available since .NET 1.0 and both are under System.Collections.Specialized name space. The difference is that NameValueCollection can have duplicate keys (multiple values for the same key are allowed).

In .NET 2.0 we have generic Dictionary<TKey, TValue> that is efficient and easy to use. More importantly the key and value objects can be of any types, and are strongly typed. But one issue of generic dictionary is that it can not be serialized directly. Paul Welter provided a SerializableDictionary solution for the problem but it's just a workaround. The other drawback of generic dictionary is that index accessing is not supported.

Generic KeyedCollection<TKey, TItem> under System.Collections.ObjectModel name space does not have those two issues on generic dictionary. KeyedCollection<TKey, TItem> supports index and key search, just like a hybrid version of generic List and Dictionary. But KeyedCollection<TKey, TItem> is just an abstract class, and you have to inherit from it and implement at least one abstract method to tell what's the key in each object inside the collection:
protected abstract TKey GetKeyForItem(TItem item);
Following is a demo of how to use KeyedCollection, where the EmployeeCollection uses employee number as a key:
using System;
using System.Text;
using System.Collections.ObjectModel;

class Program
{
static void Main()
{
EmployeeCollection ec = new EmployeeCollection();
ec.Add(new Employee("123456", "Rob", 40));
ec.Add(new Employee("234567", "Steve", 45));

bool contained = ec.Contains("123456");
ec["123456"].Age = 44;
ec[1].Age = 45;

Console.Read();
}
public class Employee
{
public Employee(string empNumber, string name, int age)
{
EmployeeNumber = empNumber;
Name = name;
Age = age;
}
public string EmployeeNumber;
public string Name;
public int Age;
// other properties and memebers
}

public class EmployeeCollection : KeyedCollection<string, Employee>
{
protected override string GetKeyForItem(Employee item)
{
return item.EmployeeNumber;
}
}
}