Wednesday, December 19, 2007
.NET Custom Configuration
This week I had to create a relatively simple Windows console program that would be scheduled nightly to email a report to a client. Simple enough, right?
Well, I decided I to make the program a little more flexible so that I could easily add more reports later if needed and specify through command-line arguments which report would be run.
So, I decided I would try and make a custom configuration section in the app.config file that would look like this...
<Reports>
<add name="DailyProductionRpt" emailTo="customer@some.com" lastRun="12/15/2007"/>
<add name="NavyMonthlyProduction" emailTo="customer@some.com" lastRun="11/30/2007"/>
</Reports>
Sounds simple enough, huh? This looks just like the <connectionStrings> section so there should be a million examples on the web on how to do it, right? So, I started Googling for examples on how to implement a custom section handler and found a lot of great examples on how to create a custom section if you want to put the collection element inside another element such as like this...
<MyCustomSection>
<Reports>
<add .../>
<add .../>
</Reports>
</MyCustomSection>
But, I didn't want that extra element and didn't see any need to include it. So, after spending several days working on this between other tasks I finally got it working. I'm posting my solution here since there doesn't appear to be an example like this anywhere else on the web.
First you have to add this code to the <configSections> section of your app.config file...
<configSections>
<section name="Reports" type="MyCompany.DataDelivery.ReportsConfigSection, DataDelivery"/>
</configSections>
Where "ReportsConfigSection" is the name of the section handler class and "DataDelivery" is the name of the assembly where it exists.
Here's all the custom section handler code that had to be implemented...
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Text;
namespace MyCompany.DataDelivery
{
/// <summary>
/// The Handler for the custom "Reports" configuration section.
/// </summary>
public sealed class ReportsConfigSection : ConfigurationSection
{
private static ReportsCollection _reports;
public ReportsConfigSection()
{
_reports = (ReportsCollection)base[""];
}
[ConfigurationProperty("", IsDefaultCollection = true, IsRequired = true)]
public ReportsCollection Reports
{
get
{
_reports = (ReportsCollection)base[""];
return _reports;
}
}
public ReportElement this[int idx]
{
get
{
return Reports[idx];
}
}
public ReportElement this[object idx]
{
get
{
return Reports[idx];
}
}
}
/// <summary>
/// This defines the Reports collection element.
/// </summary>
ConfigurationCollection(typeof(ReportElement))]
public class ReportsCollection : ConfigurationElementCollection
{
protected override ConfigurationElement CreateNewElement()
{
return new ReportElement();
}
protected override object GetElementKey(ConfigurationElement element)
{
return ((ReportElement)element).Name;
}
public ReportElement this[int idx]
{
get
{
return (ReportElement)BaseGet(idx);
}
}
public ReportElement this[object idx]
{
get
{
return (ReportElement)BaseGet(idx);
}
}
}
/// <summary>
/// The class that holds onto each element returned by the configuration manager.
/// </summary>
public class ReportElement : ConfigurationElement
{
[ConfigurationProperty("name", DefaultValue = "", IsKey = true, IsRequired = true)]
public string Name
{
get
{
return ((string)(base["name"]));
}
set
{
base["name"] = value;
}
}
[ConfigurationProperty("emailTo", DefaultValue = "", IsKey = false, IsRequired = false)]
public string EmailTo
{
get
{
return ((string)(base["emailTo"]));
}
set
{
base["emailTo"] = value;
}
}
[ConfigurationProperty("lastRun", DefaultValue = "", IsKey = false, IsRequired = false)]
public DateTime LastRun
{
get
{
return ((DateTime)(base["lastRun"]));
}
set
{
base["lastRun"] = value;
}
}
}
}
I'm still not sure that I understand everything happening here, but it works and you should be able to easily adapt it to your needs if you need a custom top-level collection element in your configuration file.
Microsoft Live Maps
Once again, I am overwhelmed by the advancements made in online mapping software. I was first wowed by Google Maps when several years ago they came out with the interactive Web 2.0 interface and I've been a loyal follower every since. Well, yesterday I tried out the new Microsoft Live Maps and I must say that I have been wowed again.
I live in a very rural area and the big test for the abilities of a mapping program has been to see if it can see my house in as high a resolution as has been available in larger cities for some time now. Currently, this is the best I can get from Google Maps.
Not very impressive. So, I was playing around with Windows Live Writer yesterday and tried out its Insert Map feature which sent me to Live Maps. I entered in my home address and this is what I got!
There are also some other amazing features such as the ability to rotate around my house and see it from all angles. I can't imagine how they get all the imagery to make this possible, but it's very impressive.
There appear to be a lot of other features in Live Maps that I've been wishing for and I'll be spending more time playing with in the near future. But, one thing seems certain...
Adios Google Maps!