posts - 5,  comments - 29,  trackbacks - 4

One of the selling points of the .Net Framework is that applications developed on the platform should be easy to deploy. However, I've found that moving a .Net application from development, to QA, and on to production can be somewhat of a pain if settings in my web.config are specific to the environment where the deployed app lives. For instance, each environment may have a different connection string, email server setting, or web service URL that it needs to interact with.

The approach that I used to take, which served me fine for a while, was to create a folder hierarchy in my file system and source control tree along with the rest of the code base. I'd have a folder named after the source environment and destination environment (i.e. QA and PROD), and I would maintain separate web.config files.

This was a good enough solution, but I soon tired of having to verify each setting in each of the three files that all shared the same name. What I wanted was a single web.config file that I could use to store the settings for each environment. I thought it would be nice if it would allow me to store a default setting that I could override in one or more environments (for instance - if I wanted to use a test web service in both dev and QA, but a different one in production).

To start, I created a web environment helper class. This one is specific to my particular environment, and lets me determine at run time where my application is executing:

public enum InsarioWebEnvironment {Dev,QA,Prod}
// Provides acces to information about the environment where the web application is currently executing.
public class WebEnvironment {    
    static InsarioWebEnvironment currentEnvironment = GetCurrentEnvironment();
    public static InsarioWebEnvironment Current {        
        get { return currentEnvironment; }    
    }
    static InsarioWebEnvironment GetCurrentEnvironment() {        
        // In case we are executing unit tests within NUnit        
        if (HttpContext.Current==null) return InsarioWebEnvironment.Dev;        
        string aServer = HttpContext.Current.Request.Url.Host.ToLower();        
        if ((aServer=="localhost") || (aServer=="127.0.0.1")) return InsarioWebEnvironment.Dev;        
        if (aServer.EndsWith("local") || (aServer.IndexOf("dev.insario.com")>0)) return InsarioWebEnvironment.QA;        
        return currentEnvironment = InsarioWebEnvironment.Prod;    
    }
}

Now whenever I want to figure out where my code is executing, I can ask for WebEnvironment.Current, and get back an enum of type InsarioWebEnvironment. This is useful for logic flows within my code - for instance, I could prefill a web form with test user data when in InsarioWebEnvironment.Dev, so that I don't have to fill in a web form over and over again. But the usability is extended even further with the problem domain of this article. To allow me to grab a custom setting, I can .ToString() the InsarioWebEnvironment to get back a token for use within my IConfigurationSectionHandler..

To illustrate my original requirement for how I want to be able to declare and override web.config settings, I threw together this sample XML section that might be found in my web.config:

<MySettings>    
    <SMTPNotification>
        <MailServerName>internalMailServer</MailServerName>
        <MailServerNamePROD>mail.prodmailserver.net</MailServerName>
     </SMTPNotification>
</MySettings>

As you can see, I want to use a different server to route mail messages depending on which environment I'm in. When I'm operating in a Dev or QA environment, I'll use our internal mail server and while operating in production, I'll use a different one. The next step is to build a class that implements the IConfigurationSectionHandler interface. This is a very basic example of how I could go about doing this: The method below is a utility method I use to read specific settings from within a custom app setting in my IConfigurationHandler.

string ReadNode(string nodeNameToRead, XmlElement configNode, bool isRequired)  {    
    string currentEnvironmentToken = WebEnvironment.Current.ToString().ToUpper();    
    //Read environment-specific value first. This will look for "MailServerNameENV", where ENV is the current environment (DEV, QA, or PROD)    
    XmlElement elementNode = (XmlElement)configNode.SelectSingleNode(nodeNameToRead + currentEnvironmentToken);    
    
    // if not found, use the default value for "MailServerName"    
    if (elementNode==null) 
        elementNode=(XmlElement)configNode.SelectSingleNode(nodeNameToRead);    
    if (elementNode==null && isRequired==false) 
        return string.Empty;    
    if (elementNode.HasChildNodes && elementNode.FirstChild.Value!=null)        
        return elementNode.FirstChild.Value;    
    return string.Empty;
}

Here is how I might use it:

try { this.ApplicationName = ReadNode("MailServerName", configNode, true); }
catch(Exception anException){    
    throw new ConfigurationException("Web.config is missing . Please refer to the developer's guide.", anException);
}

That's it! Now, when my app runs in any environment other than prod, my configuration handler will return internalMailServer for the mail server name. When I run in prod, it'll return mail.prodmailserver.net.

posted on Saturday, April 23, 2005 8:56 PM
Post a new comment about this topic
Title  
Name  
Url

Comments   
Protected by Clearscreen.SharpHIPEnter the code you see: