Still the Last Configuration Handler you'll ever need

I was responding to a newsgroup question about how to use an XmlTextReader to open and read configuration settings. My answer was, don't. Craig Andera's "Last Configuration Section Handler" post from way back in 2003 is still the best way to read config settings in .NET. I went looking for the code, but couldn't find it. Looks like the link in Craig's post is stale. I let him know, and he sent me the updated pointer to the code.

Enjoy.

Update, 2005 April 21. included code below.

Using Craig's config section handler in an ASP.NET app, I found some challenges that required changes. One thing was: the handler presumed that the type that you want to de-serialized into is specified in a separate assembly, with a known name. But if you are doing quick-n-dirty ASP.NET programming, sometimes you define the type in the ASP.NET code-behind, which gets dynamically compiled, and you don't know the name of the assembly. So, the handler needed to be changed to allow for that scenario. Anyway, here's my modified copy of the handler, for posterity. You'll have to take out the trace calls or replace them with whatever you use.

// ConfigSectionHandler.cs

//

// ripped and extended from

// https://staff.develop.com/candera/weblog/stories/2003/02/20/theLastConfigurationSectionHandlerIllEverNeed.html

// https://www.pluralsight.com/craig/articleview.aspx/CLR%20Workings/The%20Last%20Configuration%20Section%20Handler%20I.xml

//

// There are a couple of problems with the implementation above:

// - there is no error handling. If the config data cannot be de-serialized, there

// is little to no warning or explanation given.

// - It is rather rigid, requiring the type of the config data section to be

// specified in a standalone assembly with a known DLL. This is not the case when used within

// ASP.NET, and the type is defined in the "code behind" file, for example.

//

// This implementation attempts to generalize the ConfigSectionHandler .

//

// For examples, see the doc at the end of this file.

//

// (c) Ionic Shade

// Wed, 20 Aug 2003, Wed, 03 Nov 2004

//

using System;

using System.Reflection;

namespace DevelopMentor.Candera.Utilities

{

public class MyConfigSectionHandler : System.Configuration.IConfigurationSectionHandler {

public object Create (object parent, object configContext, System.Xml.XmlNode section) {

System.Xml.XPath.XPathNavigator nav = section.CreateNavigator ();

string typename = ( string ) nav.Evaluate ("string(@type)");

if ((typename== null)||(typename=="")) {

Ionic.WebUtil.TraceMe("No type information found in the app config file. Please specify a type in the config section element.");

Ionic.WebUtil.TraceMe(String.Format("Example:\n <{0} type=\"namespace.Type[,Assembly]\">", section.Name));

Ionic.WebUtil.TraceMe(String.Format(" {0}",section.InnerXml));

Ionic.WebUtil.TraceMe(String.Format(" </{0}>\n", section.Name));

return null;

}

else

Ionic.WebUtil.TraceMe(String.Format(" got typename= <{0}>\n", typename));

// try1: just load the type, using the fully-qualified typename, or from the currently executing assembly

Type t= Type.GetType ( typename , false ); // true== throwOnError

// try2: load from the calling assembly (probably wrong)

if (t==null) {

Ionic.WebUtil.TraceMe(String.Format(" GetCallingAssembly(): ({0})", Assembly.GetCallingAssembly().FullName));

t= Type.GetType ( typename + "," +Assembly.GetCallingAssembly().FullName , false ); // true== throwOnError

}

// try 3: if a web page, then load from the referenced assemblies in the web page

if (t==null) {

// this will work if it is ASP.NET

// TODO: also support webservice usage (the handler will not be System.Web.UI.Page)

if (System.Web.HttpContext.Current.Handler is System.Web.UI.Page) {

System.Web.UI.Page p= (System.Web.UI.Page) System.Web.HttpContext.Current.Handler;

Assembly a= p.GetType().Assembly;

Ionic.WebUtil.TraceMe(String.Format(" Page Assembly:...({0})",

(a!=null) ? a.FullName : "??"));

t= a.GetType ( typename , false ); // true== throwOnError

// try 4: scan all referenced assemblies of the Page assembly

if (t==null) {

Ionic.WebUtil.TraceMe(String.Format(" Trying referenced Assemblies:"));

try {

AssemblyName[] names= a.GetReferencedAssemblies();

if (names!=null) {

foreach (AssemblyName n in names) {

if (t==null) {

Ionic.WebUtil.TraceMe(String.Format(" trying Ref'd Assembly:...({0})", n));

t= Type.GetType ( typename + "," + n.FullName , false ); // true== throwOnError

if (t!=null)

Ionic.WebUtil.TraceMe(String.Format(" Loaded type {0}", t.FullName));

}

}

}

}

catch (System.Exception ex1) {

Ionic.WebUtil.TraceMe(String.Format(" Cannot GetReferencedAssemblies(): {0}", ex1));

}

}

}

}

if (t!= null) {

object o= null;

try {

System.Xml.Serialization.XmlSerializer s = new System.Xml.Serialization.XmlSerializer (t);

o= s.Deserialize ( new System.Xml.XmlNodeReader (section));

}

catch (System.Exception ex2) {

Ionic.WebUtil.TraceMe(String.Format(" Cannot De-serialize(): {0}", ex2));

}

return o;

}

else {

Ionic.WebUtil.TraceMe(String.Format("Could not load type '{0}', and consequently failed to read config information. Check your config file.",typename));

return null;

}

}

}

}

// Configuration Guide:

// ----------------------

// set up the app config file like this:

//

//

// <configuration>

//

// <!-- this section tells .NET where to find the type and assembly that handles later sections in this config file -->

// <configSections>

// <section name ="MyAppConfig" type="DevelopMentor.Candera.Utilities.MyConfigSectionHandler,ConfigSectionHandler"/>

// </configSections>

//

//

// <!-- This is the custom app config section, handled by the above specified type. -->

// <MyAppConfig type="utils.MyAppConfig,Assembly" >

// <ConnString>server=dinoch-8;Integrated Security=SSPI;database=northwind</ConnString>

// </MyAppConfig>

//

// </configuration>

//

//

//

// In the top section, the type="xxxx" specifies the

// typename,assembly. The above example assumes this type is named

// MyConfigSectionHandler and has been

// compiled into a DLL of name ConfigSectionHandler.dll, which is in

// the local dir or on the load path (Eg, bin). You could also GAC it.

//

// In the 2nd section, it assumes that the type to be de-serialized is

// called utils.MyAppConfig and lives in an assembly named Assembly.{exe,dll}.

// Change this to reflect the DLL or EXE you have compiled the MyAppConfig type into.

//

// On assemblies:

// You cannot leave out the assembly names, if the

// ConfigSectionHandler is being loaded from a separate standalone

// DLL. If you collapse all of the ConfigSectionHandler, MyAppConfig,

// and app code into a single assembly you can leave out the names and

// just use shorthand typenames. This should work in ASP.NET as well.

//

// .NET will then map any elements in the config file called

// "MyAppConfig" to the named ConfigSectionHandler class. .NET

// instantiates ConfigSectionHandler, calls Create() on that

// instance, passing it (via the section parameter) a reference

// to the relevant portion of the config file. (the 2nd section above).

//

//

// Programming Guide:

// -----------------

// To use this, define a type to receive the config info like this:

//

// namespace utils {

// public class MyAppConfig {

// public string ConnString;

// }

// }

//

// then, in app code, do something like this:

// utils.MyAppConfig cfg= (utils.MyAppConfig) System.Configuration.ConfigurationSettings.GetConfig ("MyAppConfig");

//

//

// Compiling:

// ------------

// Don't need to reference the ConfigSectionHandler.dll in the compile

// line, since it is dynamically loaded by .NET at runtime.

// But that DLL must be in the loadpath (local dir or bin subdir) .

//

// Groups:

// ------------

// It is also possible to specify groups of config sections. like so:

//

//

// <configuration>

// <configSections>

// <sectionGroup name="MyCustomConfig">

// <section name="Xml2PdfConfig"

// type="DevelopMentor.Candera.Utilities.MyConfigSectionHandler,ConfigSectionHandler, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"/>

//

// </sectionGroup>

// </configSections>

//

// ...

// <!-- custom config data sections : -->

//

// <MyCustomConfig>

//

// <Xml2PdfConfig type="Ionic.IText.Xml2PdfConfig">

// <XmlUrl>https://dinoch.dyndns.org:8080/winisp/faq/faq.aspx?f=xml</XmlUrl>

// <MapFile>xml2pdf-itext-tagmap.xml</MapFile>

// </Xml2PdfConfig>

//

// </MyCustomConfig>

//

// </configuration>

//

// In this case the app code looks like this:

//

// Xml2PdfConfig o1 = (Xml2PdfConfig) System.Configuration.ConfigurationSettings.GetConfig("MyCustomConfig/Xml2PdfConfig");

//

// =======================================================