Improving Using XML to Share Constants Across Projects
By Rachael Schoenbaum
Introduction
In an earlier article of mine, Using XML to Share Constants Across Projects, I
looked at how to share common constants across multiple ASP.NET Web projects using a "constants XML file" and a custom
class (ConstantsManager) to read this file and pick out particular constant values. Since publishing that article, I've improved the
class and file structure to allow for constants specific to a particular development phase or environment. For example,
a constant that you might need to have access to is the mail server to use to send emails. However, the mail server
to use might be different when testing, staging, and when the site goes live.
In this article we'll look at how to extend my earlier article to allow for the constants file to have versioned constants.
The ConstantsManager Class and Multiple Environments
The problem with the ConstantsManager class as it exists in the first
article, is that every time you roll your application out to staging or to production, you have to go in a
modify the config file for values that are different across the different environments (like database connection
strings, paths on the file server, and URLs). It's easy to forget which variables need to be modified and what they
should be modified to. Plus, every time you touch the code, the likelihood for introducing errors grows.
An additional consideration is that not all constants are affected by different staging environments. For example,
things like the number of search results that display per page or length of the columns in the database will remain
the same across environments. My challenge, then, was to come up with a way to modify the
ConstantsManager class to allow for an optional tag that indicated which environment (if any) the
constant applied to without requiring modifications to the projects. The rest of the article describes in
detail I accomplished this.
Modifying the Config File
To prepare the config file for this new concept, I first decided that I needed some mechanism to specify what
version should be used. I decided to add a single new tag to the config file - THIS_VERSION - that
would indicate what version of the constants to use. That way, when moving from one environment to another, all I would
have to do was modify the value of this tag.
<THIS_VERSION>1</THIS_VERSION>
Next, for those constants that need to have different values for different versions,
I added a version attribute. Imagine we had a constant named APPLICATION_URL, that had
three different versions for, say, testing, staging, and production servers. The constant file, then, would contain
three APPLICATION_URL elements, each with a differing version attribute like so:
Of course not all constants require different values for different versions. For those constants, you can simply
use just one tag and omit the version attribute altogether:
<RESULTS_PER_PAGE>10</ RESULTS_PER_PAGE>
To handle the versioned constants, the ConstantsManager code needs to be changed slightly. The following
shows the complete code for this class, with the changes from my first article
shown in red.
'***************************************************************
' ConstantsManager
' Description: Manages the constants that live in the
' applicationConstants.config file. These get stored
' in memory and when the file changes, the file is
' removed from memory and re-read back into memory
'
' NOTE: Constants file must live in the webroot
' on the web server for the web application to run.
'
' Author: Rachael Schoenbaum
' Create date: 12/4/2002
'***************************************************************
Imports System, System.Web.Caching, System.Xml, Microsoft.VisualBasic
Public Class ConstantsManager
' Cache object that will be used to store and retrieve items from
' the cache and constants used within this object
Protected Friend Shared myCache as System.Web.Caching.Cache = _
System.Web.HttpRuntime.Cache()
Private Shared applicationConstantsFile As String = "ApplicationConstantsFile"
Public Shared applicationConstantsFileName As String = _
String.Replace(System.AppDomain.CurrentDomain.BaseDirectory, "/", "\") & _
"applicationConstants.config"
Private Shared xmlFile As New XmlDocument()
Private Shared constantIdentifier As String = "constant"
Private shared constantKey As String = "cacheDependencyKey"
Private Shared thisVersion As Integer = getConstant("THIS_VERSION")
Public Shared Function getConstant(ByRef key As String) As Object
Dim tmpObj As Object
If Not (myCache(constantIdentifier & key) Is Nothing) Then
tmpObj = CType(myCache(constantIdentifier & key), Object)
Else
tmpObj = pullConstantFromFile(key)
'Create the cache dependencies and insert the object into the cache
If Not IsNothing(tmpObj) Then
If myCache(constantKey) Is Nothing Then
myCache.Insert(constantKey, now)
End If
myCache.Insert(constantIdentifier & key, tmpObj, _
New CacheDependency(applicationConstantsFileName))
End If
End If
Return tmpObj
End Function
Private Shared Function pullConstantFromFile(ByRef key As String) As Object
Dim obj As Object = 0
If myCache(applicationConstantsFile) Is Nothing Then
PopulateCache()
End If
'Attempt to find the element given the "key" for that tag
Dim elementList As XmlNodeList = xmlFile.GetElementsByTagName(key)
'If the key is found, the element list will have a count greater than
'zero and we retrieve the value of the tag...
If elementList.Count > 0 Then
Dim node As XmlNode
'If there is only 1 element in the list, then the element doesn't
'have a version number and can be returned as is.
If elementList.Count = 1 Then
node = elementList.Item(0)
'If there is more than 1 element in the list, find the element for
'the version of the application
Else
node = xmlFile.SelectSingleNode("descendant::" & key & _
"[@version=" & thisVersion & "]")
End If
'Retrieve the value behind the node that matched the key/version
If Not (node Is Nothing) Then
obj = node.InnerText()
End If
'If the value is a numeric, convert it to a number; otherwise
'convert it to a string (we don't store values other than strings
'and numbers).
If IsNumeric(obj) Then
obj = CType(obj,Integer)
Else
obj = CType(obj, String)
End If
End If
Return obj
End Function
Private Shared Sub PopulateCache()
'With a try around the entire event, the object attempts to load the XML
'file and store it in the cache with a dependency on the XML file itself.
'This means that any time the XML file changes, it is removed from the
'cache. When the "getConstant" method is called again, the XML file won't
'exist in memory and the PopulateCache will be re-called.
Try
xmlFile.Load(applicationConstantsFileName)
myCache.Insert(applicationConstantsFile, xmlFile, _
New CacheDependency(applicationConstantsFileName))
Catch e As Exception
System.Diagnostics.Debug.WriteLine("Error: " & e.Message)
End Try
End Sub
End Class
Using the New ConstantsManager Class
Using the new ConstantsManager class is absolutely no different than before. It's no different because
the current version information is stored in the <THIS_VERSION> element in the constants file itself.
An example of using the ConstantsManager class in an ASP.NET Web page is shown below:
If userType = ConstantsManager.getConstant("MEMBER") Then
'Do something...
End If
The manager pulls the value for the tag indicated. If the tag has a version, it only uses the version that matches
what's in the <THIS_VERSION> element.
The best part of this enhancement is that if you're already using the ConstantsManager class, upgrading
to this improved implementation requires no changes to your existing code base. The benefit to this methodology
are that all of your constants for all different environments live in one file and you only have to change one value
in the constants file to update your config file for your new environment. This makes roll outs easier and less
error-prone.
About the Author
Rachael Schoenbaum is a developer specializing in ASP and VB.NET,
ASP/Visual Basic, SQL Server, XML, and related technologies. She consults
for Lucidea and has been programming
since 1999.