Monday, April 17, 2006

I've been doing some experimentation lately regarding application configuration files.  I know that there is a horse out there that is really dead due to repetitive beatings, but I'd like to add my two kicks, er, cents.

In all of your .NET application development, if you've ever done configuration files and deserialized their XML contents to objects you may have encountered the following links (originally created, as I understand it, by Craig Andera):

They're all variations on a theme.  The predominant overriding theme describes how to read in a custom XML chunk from a configuration file and deserialize it into the appropriate object type at runtime.  This effectively renders the XML element and attribute values as properties and collections and makes the configuration file settings readable at runtime.  Rather than having to constantly re-code and re-engineer the details of reading XML elements manually and repetitively, these posts demonstrate a technique that relies on objects found the System.Xml.Serialization namspace to automatically do the work for you.  Very handy indeed.  I've used these exact strategies for a few years in my own work to great success.  All in all, I really feel that the original solution has lots of merit and I really like it.

Essentially, the gist is that you create a ConfigSectionHandler class that implements the IConfigurationSectionHandler interface.  This interface is required by the .NET runtime as the means by which it calls into your library so that you can perform custom work on the XmlNode that it provides in the .Create() method.  The strategy here is to create a generic handler that can resolve the appropriate Type to create for deserialization so that all of the work is done for you by a single handler.  This is accomplished by including a 'type' attribute on the XML element to be deserialized.

A simple example might resemble the following:

app.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
   <configSections>
      <sectionGroup name="devstone">
         <section name="settings" type="TestApplication.Configuration.ConfigHandler, TestApplication" />
      </sectionGroup>
   </configSections>
   <devstone>
      <settings type="TestApplication.Configuration.Settings, Testapplication">
         <setting1>Test Value 1</setting1>
         <setting2>Test Value 2</setting2>
      </settings>
   </devstone>
</configuration>

ConfigSectionHandler.cs

namespace TestApplication.Configuration {
   public sealed class ConfigSectionHandler : IConfigurationSectionHandler {
      object IConfigurationSectionHandler.Create(object parent, object configContext, XmlNode section) {
         XPathNavigator nav = section.CreateNavigator();
         string typeName = (string)nav.Evaluate("string(@type)");
         Type type = Type.GetType(typeName);
         if ( null == type )
            throw new ArgumentException(string.Format("Invalid type specification in configuration file: '{0}'", typeName));
         XmlSerializer ser = new XmlSerializer(type);
         return ser.Deserialize(new XmlNodeReader(section));
      }
   }
}

Settings.cs

namespace TestApplication.Configuration {
   [XmlRoot("settings")]
   public sealed class Settings {
     private string _setting1, _setting02;
    
     [XmlElement("setting1")]
     public string Setting1 {
        get { return _setting1; }
        set { _setting1 = value; }
     }
    
     [XmlElement("setting2")]
     public string Setting2 {
        get { return _setting2; }
        set { _setting2 = value; }
     }
   }
}

I got to thinking recently as a simple, side, pet project to see if I might improve upon these methodologies.  A few avenues led to dead-ends while others were more fruitful and enjoyable.  I'd like to present these here, leading up to what I think might be my favorite solution, though the jury's still out on it.  I've got to give it a few more spins around the parking lot first.

First of all, I like to treat each 'configuration section object' as a singleton.  That is, that only one instance of the object should ever exist that represents the settings.  There is absolutely no need to be writing code like this everywhere:

Settings settings = (Settings)ConfigurationSettings.GetConfig("devstone/settings");

To accomplish this I convert my settings class into a singleton following the prescribed pattern for lazy initialization:

Settings.cs

namespace TestApplication.Configuration {
   [XmlRoot("settings")]
   public sealed class Settings {
      public static Settings Instance = SettingsProvider.Instance;
      private sealed class SettingsProvider {
         private SettingsProvider() { }
         internal static Settings Instance { get { return ConfigurationSettings.GetConfig("devstone/settings") as Settings; } }
     }
   
     
// same field and property declarations as before...
   }
}

Note, it's not a true singleton...more on that in a bit.  Now, to reference the settings object I can simply use the simpler, more intuitive code and access the single instance globally across my application:

Settings settings = Settings.Instance;

In fact, this is more akin to the methodology I've used now for some time, but still some things grate on me.

  1. I have to build my configuration XML files explicitly knowing the types.  One type for the <configSections /> section.  Another type for the attribute on the XML element to be deserialized.  I'd rather only need to remember one type.  Also, the 'type' of the object to be deserialized isn't the business of the configuration file nor the eyes of the person viewing it.  How many times do you want to actually be able 'change' the type via the configuration file?  Nonetheless, I enjoy the clean elegance of the aforementioned solution.
  2. While not explicitly heinous, I don't really like having seemingly unnecessary accessors on my deserialized type except for the sole purpose of supporting deserialization (e.g. public 'set' accessors on properties, having to make the 'Settings' type public with a public parameterless constructor (though implicit it may be in this case)).
  3. If there are other members (e.g. properties that aggregate values) I have to explicitly identify them as [XmlIgnore()] so as to not permit them to (de)serialize.  This is trivial in reality, but thought I'd mention it.

I'd like to address these bit by bit.

First of all, let's address the notion of 'Type'.  As I mentioned, I don't really care to have to remember two types (though Scott Weinstein's blog post does address this issue some by generating the XML necessary for you.  I'd rather not have my XML polluted with unnecessary type information.  Therefore, let's move the generic handler into a base class from which the Settings class can derive, thereby providing it's own type information.  This will also necessitate a change to the configuration file to reflect the new type information and to the handler to be an abstract base class:

app.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
   <configSections>
      <sectionGroup name="devstone">
         <section name="settings" type="TestApplication.Configuration.Settings, TestApplication" />
      </sectionGroup>
   </configSections>
   <devstone>
      <settings>
      ...
      </settings>
   </devstone>
</configuration>

ConfigSectionHandlerBase.cs

namespace TestApplication.Configuration {
   public abstract class ConfigSectionHandlerBase : IConfigurationSectionHandler {
      object IConfigurationSectionHandler.Create(object parent, object configContext, XmlNode section) {
         try {
            XmlSerializer ser = new XmlSerializer(this.GetType());
            using ( StringReader reader = new StringReader(section.OuterXml) )
               return ser.Deserialize(reader);
         }
         catch ( Exception ) {
            // here, just for example return a clean instance if the object failed to deserialize
            return Activator.CreateInstance(this.GetType());
         }
      }
   }
}

Settings.cs

namespace TestApplication.Configuration {
   [XmlRoot("settings")]
   public class Settings : ConfigurationSectionHandlerBase {
      // same singleton, field, and property declarations as before
   }
}

Now the XML is cleaned up a little bit as no further type information needs to be specified.  However, there is an issue with this code.  Granted it's not very serious, but I view it as a shortcoming and not as clean as it might otherwise be.  The error isn't very obvious.

If you add the following code to the Settings class you'll see what I mean:

public Settings() { Console.WriteLine("Instance created"); }

The C# compiler will automatically provide a default, public constructor for a class that does not have an explicit constructor defined.  By adding this it simply makes it explicit and obvious.  If you were to run an application that consumed this Settings class, you'd see the text "Instance created" appear twice on the console window.  This illustrates two points:

  1. Even though we wrote code to make the class a 'singleton' it's not truly a singleton because it has a default public constructor.  This public constructor is required by the serialization engine so we're kinda stuck with it.  (We'll address this shortly).
  2. The .NET runtime is creating an instance of our class to be able to call the Create() method so that we can in turn create another instance (our 'singleton' instance) and return it.  This is pretty ugly.  Besides, the settings object shouldn't really have to know how to deserialize itself - that responsibility rightfully belongs to someone else.

Therefore, let's get to the final proposed solution.  Please bear with me on this as it's all kinda conceptual here - I've got to experiment a bit more to see if I like it, but I think I do.

SOLUTION:

First of all, let's move the implementation of the settings object (the one that gets deserialized) into an inner class.  Note, it still has to be public, with public get/set accessors on serialized methods, with a public constructor, but we can live with that.  I'll name this class Impl for 'Implementation'.  Ultimately, this type is not generally accessed except by the Settings class; unfortunately, there's nothing really stopping anyone from getting to it.  Peeve: I wish deserialized types could be internal or private.

Second, let's keep our base handler type, but delegate control to another inner class called Handler.  By doing this we can extend the base class to provide further metadata about the type to be deserialized along with some other information.  Additionally, it can be a private class - the runtime won't care in this case.

Third, we redo the Settings class such that it's properties are static readonly properties that redirect to the inner Impl 'singleton' class.

Fourth, we update the configuration file to reference the private, inner Handler class.

app.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
   <configSections>
      <sectionGroup name="devstone">
         <section name="settings" type="TestApplication.Configuration.Settings+Handler, TestApplication" />
      </sectionGroup>
   </configSections>
   <devstone>
      <settings>
         <setting1>Test Value 1</setting1>
         <setting2>Test Value 2</setting2>
      </settings>
</configuration>

ConfigSectionHandlerBase.cs

namespace TestApplication.Configuration {
   public abstract class ConfigSectionHandlerBase : IConfigurationSectionHandler {
      protected virtual bool CreateOnError { get { return true; } }
      protected abstract Type DeserializedType { get; }
     
      object IConfigurationSectionHandler.Create(object parent, object configContext, XmlNode section) {
         try {
            XmlSerializer ser = new XmlSerializer(this.DeserializedType);
            using ( StringReader reader = new StringReader(section.OuterXml) )
               return ser.Deserialize(reader);
         }
         catch ( Exception ) {
            // perform logging if you'd like
            if ( this.CreateOnError )
               return Activator.CreateInstance(this.DeserializedType);
            else
               throw;
         }
      }
   }
}

Settings.cs

namespace TestApplication.Configuration {
   public sealed class Settings {
      private Settings() { }
     
      public static string Setting1 { get { return Impl.Instance.Setting1; } }
      public static string Setting2 { get { return Impl.Instance.Setting2; } }
     
      private class Handler : ConfigSectionHandlerBase {
         protected override Type DeserializedType { get { return typeof(Impl) } }
      }
     
      [XmlRoot("settings")]
      public sealed class Impl : ConfigurationSectionHandlerBase {
         public static Impl Instance = ImplProvider.Instance;
         private sealed class ImplProvider {
            private ImplProvider() { }
            internal static Impl Instance { get { return ConfigurationSettings.GetConfig("devstone/settings") as Impl; } }
         }
        
         private string _setting1, _setting02;
        
         [XmlElement("setting1")]
         public string Setting1 {
            get { return _setting1; }
            set { _setting1 = value; }
         }
        
         [XmlElement("setting2")]
         public string Setting2 {
            get { return _setting2; }
            set { _setting2 = value; }
         }
      }
   }
}

Ultimately, I think I like this solution a lot.  It does require a bit more code in the outset, but pays dividends in being much more readable.  Now, all XML serialization responsibilities are delegated directly to the Impl class and any other custom properties and methods can be tied directly to the Settings class as necessary.  Additionally, the implementor doesn't even need to know they're dealing with a single instance, but reference the properties directly:

string s1 = Settings.Setting1;

vs

string s1 = Settings.Instance.Setting1;

I'll have to experiment more with this, but it seems like a pretty cool solution.  What do you think?  I'll be posting a .NET 2.0 version soon, this is all 1.1 - I just wanted to get my thoughts out there.

Monday, April 17, 2006 9:29:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Friday, April 14, 2006

I have the privilege of speaking at the Utah County .NET User Group next Thursday night.  My topic will be 'Threading'.  I've given a similar presentation a few times now, though with each iteration I try to spice it up and make it different and better.  We'll talk about lots of .NET 1.x and the new 2.0 stuff as well.  We'd love to see you there!

Website: http://www.ucnug.org/
Time: 7:00 PM
Date: Thursday, April 20th, 2006
Place: Utah Vally State College (UVSC) room CS512

If you want to go and want to carpool, let me know, I'd be happy to drive.

Friday, April 14, 2006 6:53:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Thursday, April 13, 2006

Tonight was a success at the Utah .NET User Group.  Unfortunately we were not being able to get a reminder out to the group due to the site being down (more accurately the DNS server was down, but same difference in that it was inaccessible).  We had a pretty small turnout and a quiet crowd today, but it was fun nonetheless.

I had the opportunity to present on developing ASP.NET controls.  We focused on how to create UserControls and WebControls.  We talked about properly deriving from WebControl, rendering content and controls, utilizing embedded resources (a new .NET 2.0 feature which is totally awesome - I'll blog more about it in a future post), creating ITemplate-based controls server-side.  I've posted the code online if you'd like to download it here.

I'm glad for the faithful who showed up and had a great time and for TEKSystems for sponsoring this month's meeting.

Thursday, April 13, 2006 3:51:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback

For all those trying to hit my blog and view an individual post my apologies that it wasn't working.  I had no idea.  The main page was up and working, but it appeared that when you would browse to a single post the site would bomb with a Null reference exception.  I never got the error because I rarely visit my posts.  Today, however, I had the need to go back and review something I had written and that's when I got the error.  No wonder I hadn't received any comments or ratings for several days.  I wonder how long it had been down...?

Thanks for your patience.

Thursday, April 13, 2006 4:31:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Wednesday, April 12, 2006

We have the opportunity, tomorrow night, to attend a Utah .NET User Group Meeting (please forgive the site for it's horrendously slow performance or outage - we've almost got the new site up which should be MUCH better in so many ways).

Tomorrow's presentation will be by yours truly as a continuation of the last presentation in a series for the Component Developer.  This one is focused on ASP.NET Controls whereas the previous presentation was targeting Windows Forms.  It should be a lot of fun.

If you have any particular requests and questions, please post them here and I'll see if I can't dedicate some time in my talk to answer those questions - it will give me a little more lead time too :)

Time: 6:00 PM, Suite 300 (3rd floor)
Place: Neumont University, 10701 South River Front Parkway, South Jordan, Utah

Here's to hoping it's a good one!  They usually are.

Wednesday, April 12, 2006 2:51:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback

I had the opportunity today to attend a presentation on WPF (Windows Presentation Foundation) focusing on interop; interop between Windows Forms (WF) and WPF.  It was pretty cool.  Though we didn't really get deep into XAML (we just touched on the surface), we explored the interactivity between the two platforms.

A few interesting aspects that the speaker, Mike Henderlight, touched on were the goals and the non-goals of his team (UI Frameworks Client Team, previously known as Windows Forms Team) with respect to WPF.

Primarily, their goal is to provide both design- and runtime-support for building application that allow WPF and WF to co-exist seamlessly.  That is, you will be able to leverage your existing codebase and investments already made in WF and host WPF components thereon (and vice versa).  In addition, ActiveX components will be supported.

They will not, however, via XAML provide a 'general-purpose mark-up solution' for developing WF applications.  They are not, for instance, trying to achieve a WF markup of any kind.  Additionally, no “code migration wizard” is in the works.  Therefore, when creating a WPF application, the developer should consciously be designing for that platform - there's no one-to-one relationship between controls.

Mike went on to illustrate a few example applications which he developed in both WinForms as well as WinFX to host the other platform's controls, perform databinding, etc.  All in all it was a fun, educational presentation.  Thanks, Mike!

Wednesday, April 12, 2006 2:43:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Sunday, April 09, 2006

Today marks the 2 year mark for my blog, and what a couple of years it has been!  There have been 350 posts since it's inception (this being the 351st post overall).  While not as active in terms of posting as I would like to have been over the past couple of years, I feel that some goodness has come out of having the blog.  Many people have found some assistance through their coding and .NET travails for which I am grateful.  Earlier this year I was awarded Microsoft MVP award, which in an of itself was an exciting bit of news.  I have grown too.  Not in height, mind you (I'm still a stable 6'6”), and girth (well, we'll discuss that sometime else), but in experience and knowledge.

Through imparting tidbits of information I come across or solutions and strategies for problems, I've been able to document my progress and see how I've adapted to various situations.  I hope that some of my experiences have been for a greater good and benefitted others.

I eagerly look forward each day for opportunites to submit content to this meager site.  In fact, there have been innumerable occurrences in which I started down the road of typing up a post and then decided to renege and deleted the post in progress because I ultimately thought that the ideas I was putting down were not going to be beneficial.  Perhaps they were stupid (I've been known to post some pretty dumb things in the past) or perhaps not.  Either way, this blog has been a blast to maintain and I hope to continue to do so moving forward.

Thanks to all my loyal readers for your support.  Is there anything that I can do or content that I can add that would make this site better and more beneficial?  Any issues that I might be able to help address or topics you'd like me to cover?

Sunday, April 09, 2006 5:52:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Saturday, April 08, 2006

The other day I started to write a new utility application which, among its various tasks, launches applications as specified by the user.  Typically, the user may enter or browse to a folder such as "C:\Program Files\Some Application\DummyApp.exe".  Passing this path to the Process class's Start() method will then launch the application as in the following contrived examples (which are conspicuously devoid of any error handling code - don't try this at home):

using System.Diagnostics;

// in some class somewhere...
private void launchSomeApp() {
   string appPath = "\"C:\\Program Files\\Some Application\\DummyApp.exe\"";
   Process.Start(appPath);
}

However, suppose the user providing the application path in the following format:

string appPath = "\"%ProgramFiles%\\Some Application\\DummyApp.exe\"";

That path is perfectly valid and typing it at the command prompt will execute the application properly (provided it actually exists).  However, it appears that using environment variables within your path provided to the Process.Start() method it will fail to execute.  Using either the previously mentioned code or the following will yield a "The system cannot find the file specified." error message.

private void launchSomeApp() {
   string appPath = "\"%ProgramFiles%\\Some Application\\DummyApp.exe\"";
   ProcessStartInfo startInfo = new ProcessStartInfo();
   startInfo.FileName = appPath;
   Process.Start(startInfo);
}

You might be tempted to go down the path, to experiment with the EnvironmentVariables property of ProcessStartInfo along with UseShellExecute and others.  This will all be in vain.  Effectively, the EnvironmentVariables property creates a copy of the executing application's environment variables when first referenced.  This provides a means by which the program can edit, clear, add, or even remove environment variables for the invoked process.  Note that these variables are available TO the process as environment variable, not the process invoking the new process as such.  Therefore, they have no effect on the command string.

Well, fortunately, it's not too difficult to overcome.  Built-in to the Environment class is a method which takes a string and substitutes environment variables for you.  Now users of this utility application can specify paths however they see fit (using environment variables or not), and the application will continue to work as expected.  Simple.  And saves me from having to write my own 'Environment Variable Expander' method.

private void launchSomeApp() {
   string appPath = "\"%ProgramFiles%\\Some Application\\DummyApp.exe\"";
   ProcessStartInfo startInfo = new ProcessStartInfo();
   startInfo.FileName = Environment.ExpandEnvironmentVariables(appPath);
   Process.Start(startInfo);
}

Saturday, April 08, 2006 6:01:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Wednesday, April 05, 2006

Attention ASP.NET 2.0 Developers:  The Release Candidate 1 (RC1) of the VS 2005 Web Application Projects (WAP) has now been released.  If you're like me and prefer the 'old' 1.x style of ASP.NET applications (with namespaces, more intuitive folder hierarchy structures, a \bin folder, and much more), you'll really enjoy this.  WAP brings that style of website development to VS.NET 2005.  I've been using WAP since early Beta 2 and despite a few beta-related issues have really enjoyed it.  I'm looking forward to some of the new enhancements and fixes!

Read more about it on Scott Guthrie's blog.

Wednesday, April 05, 2006 6:20:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback

My faithful one or two readers out there may have noticed that I've not updated this blog much (if only very sporatically) lately.  This was not for lack of desire, but more accurately for lack of time.  Additionally, for the past week I've really been facing a very difficult decision that pretty much consumed all thought processes and I've not had the energy to devote to adding more content.  I will work harder at that.

My turmoil and deep introspection revolved primarily around whether or not I should leave my current employment (I am full-time engaged and committed to Experlogix) or rejoin the ranks at Microsoft.  Those that know me are aware that I used to be employed by Microsoft in the capacity of Consultant - something I deeply loved and thoroughly enjoyed on so many levels.  I was in MCS for a little over two years and had the wonderful opportunity to get to know some really great people, and learn a TON, and be genuinely influential in my client engagements.  What a wonderful few years.  I have since left Microsoft to pursue other endeavors but always had my heart in Microsoft, thinking full well that I would be back there again one day.

That opportunity presented itself a few weeks ago in the form of a formal invitation to come back.  I would, of course, have to be screened and interviewed as all hires do.  I had a pretty intense day of interviews (though not quite as intense as it was on my first go around) in part because I knew the people interviewing me (some better than others) and had worked with some of them on projects.  Well, it turns out that I got an offer...a very good offer as a Senior Consultant.  One that made me really evaluate my position with Experlogix and my future as well as reflect back on Microsoft and the opportunities there.

I spent four days seriously considering all of the options, literally wracking my brain.  I have a great situation with Experlogix and I work with some wonderful people.  Would I be willing to give that up?  I truly felt that I was presented with two great options, neither of which would be a 'wrong' choice necessarily for me personally.  It came down to contemplating all of the pros and cons and seriously weighing all of the possibilities as well as what I want out of life.

I'll not really beat around the bush here, but I decided to stay and continue on with Experlogix for a variety of reasons, not the least of which is that I really believe in our product and I believe in our future.  I didn't want to give that up and deliver a blow to the company by leaving, as I feel I would sorely be missed.  Technically, no one is irreplaceable, but I felt strongly that my participation going forward coupled with my devotion to them in the past and the committments that I had made outweighed anything anyone could offer.

I really wanted to accept he Microsoft offer, but at the end of the day, I felt that staying with Experlogix was the right decision and one that I will be able to live with very happily and with a clean conscience at this stage in my life.

Wednesday, April 05, 2006 5:08:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [3]  |  Trackback
 Tuesday, April 04, 2006

I'm really sad that I missed this conference.  Every once in a while a conference comes around that provides tons of actual and applicable value to the industry that no one should miss.  This particular conference seems to be (pardon the pun) overflowing with rich content and practical information.  I hope there's a Waterfall 2007...I'd be there in a heartbeat.

These seem to be highlights, sessions I would love to attend:

Kent Beck: “wordUnit: A Document Testing Framework“
Jean Tabaka: “Eliminating Collaboration: Get More Done Alone”
Scott Ambler: “The Glacial Methodology Workshop: A Data-Centric Software Development Process”

Tuesday, April 04, 2006 5:02:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback