Go visit the Visual Basic Product Team at TechEd!

It was this time last year at TechEd that Cory Smith introduced me to the Visual Basic product team.  He first introduced me to Ed Hickey, who is the VB MVP Lead at Microsoft.  The funny story about Ed is, less than a week after Ed awarded me the MVP status, he transfers out.  I knew I was hard to work with, but didn't realize I was .... that .... difficult.

Also last year at TechEd 2007, Cory let me hang out with him at Universal Studios where I rubbed elbows with the Visual Basic product team!  He's really been a great mentor to me, and I'm very grateful.

All this is to say, go visit the Visual Basic Product Team website and meet some of the product team.  They are all very accessible and have always been very friendly.  If you're at TechEd 2008 this year, stop by the Visual Basic booth.  Amanda Silver is one of the most visible team members (and very easy on the eyes), so go strike up a conversation.

MVP Summit, Being Busy, plus Presenting @ the Little Rock .Net User Group

I suppose I was naive in thinking that once I received my Microsoft MVP award, I could breathe a bit.  But we've had sooo much Developer Community activity, I haven't had a chance to breathe, much less blog.  I still haven't had an opportunity to blog about the MVP Summit!  Needless to say, the MVP Summit was a blast (other than having a digestive track illness, where I missed the last day's keynotes).  The best thing about the Summit was the ability to meet and talk directly with the product teams.

You really get the sense of the ability to impact .Net by being an MVP!

On June 12, 2008 I'll be at the Little Rock .Net User Group, and I hope you can join me.  I'll be speaking on SQL Server Reporting Services.  Unfortunately, due to the late notice, I'll be speaking on the same subject that I presented at the Tech Expo (had hoped to prep some other topics by now).  But, I'll have more time to answer questions and hopefully get a bit more detailed on the subject.

Congrats to the new officers there at Little Rock.

Here's a quick synopsis of the content:

One of the hardest parts of learning a technology that is new to you, is getting started.  Join us as you learn the tips, tricks, and fundamentals of Microsoft's SQL Server Reporting Services.  Avoid those common mistakes and get up and running with SSRS in this presentation from Randy Walker, a 12 year veteran of report design.

Writing to .Net Config Files

I've been working with config files for quite some time.  I was recently reminded that I needed to finish my original article and share my final findings on my personal best practices for working with config files.

One of the coolest and most useful features in config files is the file attribute as displayed below (see my other article for a more detailed explaination, preferred-method-for-read-only-config-files).

<configuration>
  <configSections>
    <section name="MyCustomSection" type="System.Configuration.NameValueFileSectionHandler, System, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
  </configSections>
  <MyCustomSection file="MyCustomConfigFile.config"></MyCustomSection>
</configuration>

If you take a look at the other article, it's a terrific way to setup a read only config file, but what about writing back to it at runtime?  Well, the best way is to use the ConfigurationManager object and then open the raw xml into an XmlDocument object.  (yes yes, I might be a Visual Basic MVP, but I can write the occasional C#)

Configuration config = ConfigurationManager.OpenExeConfiguration(FileName);
ConfigurationSection section = config.GetSection(SectionName);

XmlDocument xml = new XmlDocument();
xml.LoadXml(section.SectionInformation.GetRawXml());

But the problem is, if you have used the file attribute (file="MyCustomConfigFile.config"), it's not smart enough to grab the contents of the file (I'm hoping to contact the product team soon to see if they can address the issue).  Fortunately, there is a way to get the filename from the file attribute.  And by adding in a little recursion, we're able to create an easy method to write back to the proper config file, without having to know the actual filename at runtime.

private const string SETTING_KEY_NAME = "key";
private const string SETTING_VALUE_NAME = "value";

public static void SetConfigValue(string SectionName, string KeyName, string Value)
        {
            SetConfigValue(System.Reflection.Assembly.GetEntryAssembly().Location , SectionName, KeyName, Value);
        }

        public static void SetConfigValue(string FileName, string SectionName, string KeyName, string Value)
        {
            Configuration config = ConfigurationManager.OpenExeConfiguration(FileName);
            ConfigurationSection section = config.GetSection(SectionName);

            XmlDocument xml = new XmlDocument();
            xml.LoadXml(section.SectionInformation.GetRawXml());

            if (xml.DocumentElement.Attributes["file"] == null)
                {
                    foreach (XmlElement element in xml.ChildNodes[0])
                    {
                        if (element.Attributes[SETTING_KEY_NAME].Value == KeyName) element.Attributes[SETTING_VALUE_NAME].Value = Value;
                    };

                    section.SectionInformation.SetRawXml(xml.OuterXml);
                    config.Save();
                }
            else
                {
                    SetConfigValue(System.Reflection.Assembly.GetEntryAssembly().Location.Remove
                        (
                        System.Reflection.Assembly.GetEntryAssembly().Location.LastIndexOf("\\")) + "\\" + xml.DocumentElement.Attributes["file"].Value.Remove
                            (
                            xml.DocumentElement.Attributes["file"].Value.LastIndexOf(".config")
                            ),
                        SectionName,
                        KeyName,
                        Value);
                }
        }

This creates a new problem though.  You can no longer use the same format of the underlying file, specified in the file attribute.

Old format of the MyCustomConfigFile.config specified in my other article preferred-method-for-read-only-config-files:

<?xml version="1.0" encoding="utf-8" ?>
<SectionName>
     <add key="myKey" value ="" />
</SectionName>

New required format for writing to config files with the file attribute:

<?xml version="1.0" encoding="utf-8" ?>
<!-- You must have an empty file named "MyCustomConfigFile" with no extention in the same directory as this file -->
<configuration>
  <MyCustomSection>
    <setting key="MySetting1" value="True" />
    <setting key="MySetting2" value="File;Email;EventLog"/>
    <setting key="MySetting3" value="File"/>
  </MyCustomSection>
</configuration>

You'll notice that I had a comment in the file specified in the file attritube. (<!-- You must have an empty file named "MyCustomConfigFile" with no extention in the same directory as this file -->)  I honestly can't recall why this is required (I'm posting this article months after I did the work), but it has to do with reading the config file, and the filenames it is looking for.

This new referred config file format creates a new problem.  You can no longer use the reading method I used in the other article.  I've fixed it in the code below as well as provided the full class.

 

Below is the full class that I wrote.  All I ask is that you add a comment telling me how you used it.

using System.Configuration;
using System.Xml;

namespace HarvestIT.Common
{
    /// <summary>
    /// Application settings manager.
    /// </summary>
    public class ConfigManager
    {
        // Configuration file node names.
        private const string SETTING_KEY_NAME = "key";
        private const string SETTING_VALUE_NAME = "value";

        public static string GetConfigValue(string SectionName, string KeyName)
        {
            return GetConfigValue(System.Reflection.Assembly.GetEntryAssembly().Location, SectionName, KeyName);
        }

        public static string GetConfigValue(string FileName, string SectionName, string KeyName)
        {
            Configuration config = ConfigurationManager.OpenExeConfiguration(FileName);
            ConfigurationSection section = config.GetSection(SectionName);

            XmlDocument xml = new XmlDocument();
            xml.LoadXml(section.SectionInformation.GetRawXml());

            if (xml.DocumentElement.Attributes["file"] == null)
            {
                foreach (XmlElement element in xml.ChildNodes[0])
                {
                    if (element.Attributes[SETTING_KEY_NAME].Value == KeyName) return element.Attributes[SETTING_VALUE_NAME].Value;
                };
            }
            else
            {
                return GetConfigValue(System.Reflection.Assembly.GetEntryAssembly().Location.Remove
                    (
                    System.Reflection.Assembly.GetEntryAssembly().Location.LastIndexOf("\\")) + "\\" + xml.DocumentElement.Attributes["file"].Value.Remove
                        (
                        xml.DocumentElement.Attributes["file"].Value.LastIndexOf(".config")
                        ),
                    SectionName,
                    KeyName);
            }
            return null;
        }

        public static void SetConfigValue(string SectionName, string KeyName, string Value)
        {
            SetConfigValue(System.Reflection.Assembly.GetEntryAssembly().Location , SectionName, KeyName, Value);
        }

        public static void SetConfigValue(string FileName, string SectionName, string KeyName, string Value)
        {
            Configuration config = ConfigurationManager.OpenExeConfiguration(FileName);
            ConfigurationSection section = config.GetSection(SectionName);

            XmlDocument xml = new XmlDocument();
            xml.LoadXml(section.SectionInformation.GetRawXml());

            if (xml.DocumentElement.Attributes["file"] == null)
                {
                    foreach (XmlElement element in xml.ChildNodes[0])
                    {
                        if (element.Attributes[SETTING_KEY_NAME].Value == KeyName) element.Attributes[SETTING_VALUE_NAME].Value = Value;
                    };

                    section.SectionInformation.SetRawXml(xml.OuterXml);
                    config.Save();
                }
            else
                {
                    SetConfigValue(System.Reflection.Assembly.GetEntryAssembly().Location.Remove
                        (
                        System.Reflection.Assembly.GetEntryAssembly().Location.LastIndexOf("\\")) + "\\" + xml.DocumentElement.Attributes["file"].Value.Remove
                            (
                            xml.DocumentElement.Attributes["file"].Value.LastIndexOf(".config")
                            ),
                        SectionName,
                        KeyName,
                        Value);
                }
        }
    }
}

Twitter