Adding generic intelligence to ASP.NET configurations files

by Aaron D-H 2. February 2006 05:01

.NET configuration files are the perfect place to keep all your implementation specific information, such as server connection strings, banner content, etc.  However, when you are deploying in multiple environments, with say, a development environment, test environment, pre-production environment, and finally a production environment, one usually has to maintain a specific configuration file for each environment. 

Multiple configuration files bring a whole new set of headaches when it comes to testing.  Applications that work fine with one configuration file may fail spectacularly with another.   Changes common to all configuration files have to be synchronized and each configuration file needs to be managed separately under version control.

Wouldn’t it be nice if we had a single configuration file where the application just “knew” which settings to read, depending on the environment the application found itself in?  This brings us to the point of this article; in it I will present a “configuration helper” that will provide generic configuration file intelligence for all your .NET application.

 

To create the maximum flexibility for an application, configuration settings should be functionally isolated from the logic used to select the value, and the actual values themselves.  For example, an application may need to obtain the URL to a banner image located on another machine on the corporate intranet.  In this case the ASP presentation code could look like:

 

<img src=’<%=ExpandText(“(banner-url)”)%> ’ />

 

.In the configuration file we have the key:

 

<appSettings>

      <add key="banner-url" value="http://corp.net/images/common/mycorp.gif" />

</appSettings>

  

Ignoring for a second the code behind, the presentation xml is calling the function ExpandText on the code behind instance which is responsible for looking up the string “(banner-url)”.   The ExpandText looks at the entire text string passed to it and replaces the braces and the brace enclosed text with the value of the configuration key it looks up.  The key being the text enclosed in the brace.  In this case, the key = “banner-url”, so the text string returned by ExpandText would be http://corp.net/images/common/mycorp.gif so far that’s nothing very exciting, but we have isolated the value of the URL from what we actually need to show functionally on the page.

 

Now for some magic, if we make the ExpandText function recursively expand text until no more braces are found, we don’t need to change our presentation xml to have different URLs returned depending on some other setting in the configuration file.

 

Changing the configuration file to contain:

 

<appSettings>

      <add key="switch" value="internal" />

      <add key="banner-url" value="((switch).unique-banner-url)" />

      <add key="internal.unique-banner-url" value="http://corp.net/images/common/mycorPrivate.gif" />

      <add key="external.unique-banner-url" value="http://corp.net/images/common/mycorpPublic.gif" />

</appSettings>

 

Now using the same presentation code, but changing the key “switch” between values “internal” and “external” will give us different image URLs to display.  What happens here is that on the first call ExpandText expands the text string “(banner-url)” to become “((switch).unique-banner-url)”  since ExpandText recursively calls itself until no more braces are found,  the string “((switch).unique-banner-url)”  is then expanded to become “(internal.unique-banner-url)”  and once more expanded to become “http://corp.net/images/common/mycorPrivate.gif”.  Having the text look up be recursive allows us to isolate the lookup logic from the application.  All the application need to know is that it needs a “banner-url”, how it gets there is configurable.

 

Now let’s add a little more power to ExpandText.  If ExpandText can look up key in the configuration file, why not increase it’s functionality to lookup keys in other areas.

 

(key) looks up appSettings key values from the configuration file.

 

{key} looks up values from the page’s Request collection using HttpContext.Request[key].

 

[key] looks up values from the page’s Session collection using HttpContext.Session[key].

 

#key# looks up values from the page’s Application collection using HttpContext.Application[key].

 

%key% looks up values from environment values using Environment.GetEnvironmentVariable(key).

 If the key name begins with an exclamation mark "!", the mark is removed before key lookup and the expansion is returned as "true" if the lookup succeeds or "false" if the lookup fails. For example:  The expansion of string "{!AZD_USER_ID}" will return "true" if "AZD_USER_ID" is in the HttpContext.Request or "false" if it is not.  Also note that key expansion is recursive, meaning that the string resulting from a key lookup is itself expanded before being return. Key are looked up "depth" first.  That is, the inner most key is looked up first. For example if we expand the string "(a.(b.[c_{d}])" the keys will be expanded as follows: 1) The first key looked up will be "d" from the HttpContext.Request.                         2) Then "c_*result 1*" will be looked up from HttpContext.Session (where *result 1* is the result of the above lookup).                        3) Then "b.*result 2*" will be looked up from System.Configuration.ConfigurationSettings.AppSettings (where *result 2* is the result of the above lookup).                         4) Then "a.*result 3*" will be looked up from System.Configuration.ConfigurationSettings.AppSettings (where *result3* is the result of the above lookup).                        The result of the final lookup (result 4) will be return to the caller                        If a key is not found, then the key, including it's delimiters, are return intact.  For example, “xxx-(does-not-exist)”  will return “xxx-(does-not-exist)”. Using the above functionality we could then write a configuration file as follows: 

<appSettings>

      <add key="proxy-indicator" value="{!PROXY_USER_ID}" />

      <add key="proxy.true" value="external" />

      <add key="proxy.false" value="internal" />

      <add key="switch" value="proxy.(proxy-indicator)" />

      <add key="banner-url" value="((switch).unique-banner-url)" />

      <add key="internal.unique-banner-url" value="http://corp.net/images/common/mycorPrivate.gif" />

      <add key="external.unique-banner-url" value="http://corp.net/images/common/mycorpPublic.gif" />

</appSettings>

 

Using the above configuration file,  if the HTTP header “PROXY_USER_ID” is found in the http request header,  the user would see the mycorpPublic.gif,  if not found, the user would see mycorpPrivate.gif.  Assuming that a reverse proxy on the corporate intranet was configured to identify external users by including the HTTP header “PROXY_USER_ID” in all requests passed through it to internal ASP applications.  We now have an environmentally aware configuration file.  This concept could be expanded to change values based on any environmental change. 

Another common indicator might be to use the host name to indicate a change of environment. 

 

For example:

 

<appSettings>

      <add key="host-indicator" value="{SERVER_NAME}" />

      <add key="host.localhost" value="development" />

      <add key="host.test.mycorp.net" value="test" />

      <add key="host.production.mycorp.net" value="production" />

      <add key="environment" value="(host.(host-indicator))" />

      <add key="sql.development" value="Initial Catalog=MyDatabase;Data Source=localhost;Persist Security Info=False;Integrated Security=yes" />

      <add key="sql.test" value=" Initial Catalog=MyDatabase;Data Source=test.sqlserver3.corp.net;Persist Security Info=False;Integrated Security=yes " />

      <add key="sql.production" value="Initial Catalog=MyDatabase;Data Source= test.sqlserver8.corp.net;Persist Security Info=False;Integrated Security=yes" />

      <add key="sql-connection-string" value="(sql.(environment))" />

</appSettings>

 

In this example the application code to connection to SQL might be:

 SqlConnection myconnection = new SqlConnection(ExpandText(Context,"(sql-connection-string)"));myconnection.Open(); The ExpandText method will lookup and expands “(sql-connection-string)” as follows: “(sql-connection-string)” is expanded to “(sql.(environment))” “sql.(environment)” is expanded to “(sql.(host.(host-indicator)))” “(sql.(host-indicator))” is expanded to “(sql.(host.{SERVER_NAME})” “(sql.(host.{SERVER_NAME})” is expanded to “(sql.(host.localhost)” “(sql.(host.localhost)” is expanded to “(sql.development)” “(sql.development)” is expanded to “Initial Catalog=MyDatabase;Data Source=localhost;Persist Security Info=False;Integrated Security=yes” As you can see this simple configuration helper method added to a .NET application provides a great deal of functionality to application configuration files. For the source file please see

 

ConfigurationHelper.zip (2.57 kb)

Tags:

Software Engineering | .NET | C#

Comments


June 25. 2009 10:27
Custom banner
    I think the codes that in this blog site I got the information that I really need.,


October 12. 2009 19:46
aarondh
It took awhile but I managed to locate the code for this article in one of my archives.

Comments are closed

About the Author

I'd like to introduce myself to you... My name is Aaron Daisley-Harrison.  I have worked in the software engineering field for a number of years, and as an  Application Architect have created solutions for many industry verticals; worked with both Sun Microsystem and Microsoft technologies; Developed SQL database engines as well as full text search systems; and Knowledge management systems.   I am currently doing contract work out of the Pacific North West and have lately been focusing on Microsoft technologies like SharePoint 2007/2010, WCF, Identity Framework, JQuery, Xml and Silverlight.
[more]


 Digg!

 

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2009 Aaron G. Daisley-Harrison - All Rights Reserved.