Using XML to Store States and Provinces

With every single project I’ve ever worked on, I’ve had to deal with displaying U.S. States. Sometimes I needed to include territories like Puerto Rico and the U.S. Virgin Islands; sometimes I needed to include Canadian provinces. It varied across projects so I wanted an easy way to store the information I needed and to be able to easily and quickly add, edit, and delete the information as necessary.

In addition, I wanted a simple way to store the full name and the abbreviation so that it would be easy to switch back and forth within a single application or if the client changed their mind about how they wanted the information to appear. The last thing I wanted to do was to hardcode in the states and provinces into my application.

My solution was to store the state and province information in an XML file, and to create a StateManager module from which I could easily extract the state information. In this article we’ll examine this XML file and class, and see how a DropDownList with the names of states and provinces can be created in just a couple lines of code using the StateManager module.

Storing State Information as XML

I decided to store the state and province information as an XML file instead of using a traditional relational database. There were a couple of reasons for this decision:

1. With the data in XML it’s much faster for me to change the list than if it were in a database.

2. With the data in XML, implementing the code in new applications is a breeze – I just copy the XML file to the new Web application instead of having to create new tables and reinsert the data.

3. It freed me up to do other things – people who aren’t database-savvy, but who want control over the data, can modify the list of states without my help. Had I used a relational database for the state information, anytime a change or addition was needed, I’d be the one who’d have to take the time to make the change.

Below is an example XML document. The structure I chose has <states> as the document root element. Each state or province is indicated with a <state> element, with the full name specified in the name attribute and the abbreviation in the abbreviation attribute. Create a file called stats.config and add the appropriate XML. Below is an excerpt from states.config:

<?xml version="1.0" encoding="utf-8" standalone="yes" ?> 
<states>
	<!--US States and Territories-->
	<state name="Alabama" abbreviation="AL" />
	<state name="Alaska" abbreviation="AK" />
	<state name="Arizona" abbreviation="AZ" />
	<state name="Arkansas" abbreviation="AR" />

	<!--Canadian Provinces-->
	<state name="Alberta" abbreviation="AB" />
	<state name="British Columbia" abbreviation="BC" />
</states>

Programmatically Accessing the State Information

With the information in the XML file, the next step was to be able to programmatically access it. I decided to “mimic” the data in the XML file and create a class to store the structure of a state. This class, State, contains three properties:

  • Name – returns the full name of the state.
  • Abbreviation – returns the state’s abbreviation.
  • FullAndAbbrev – returns the full name and abbreviation, like: “Full Name (Abbreviation)”. (The reason I chose to add this property was so that when binding the state information to a DropDownList the DropDownList’s DataTextField could be set to this property, thereby displaying both the full name and abbreviation of the state in the DropDownList’s text.)
  • Public Class State
        Private _name As String
        Private _abbreviation As String
    
        Public Sub New(ByRef nameArg As String, _
                           ByRef abbreviationArg As String)
            _name = nameArg
            _abbreviation = abbreviationArg
        End Sub
    
        Public ReadOnly Property Name() As String
            Get
                Return _name
            End Get
        End Property
    
        Public ReadOnly Property Abbreviation() As String
            Get
                Return _abbreviation
            End Get
        End Property
    
        Public ReadOnly Property FullAndAbbrev() As String
            Get
                Return _name & " (" & _abbreviation & ")"
            End Get
        End Property
    End Class

    The task that I was now faced with was getting the data out of the XML file and back in terms of a State class instance. To accomplish this I created a StateManager module with public methods getStates, getStateByName, getStateByAbbreviation, hasErrors, and getErrors. The first method returns an array of State objects. The second two methods I used so that I could store values in one format in a database and be able to switch to the other format in my application, if necessary. That is, I might have saved a state by its abbreviation in a database and I want to get its full name. I would call getStateByAbbreviation, passing in the stored abbreviation, and would get back a State object that I could then work with. The last two methods are used to determine if and what errors have occurred. Note that getErrors returns an ArrayList of Exception objects.

    Because file I/O is expensive in terms of system resources, I decided that once an object was requested I would store it in the Cache. When inserting an item into the Cache object you can make it dependent on an external file, meaning that the item is automatically evicted from the Cache when the dependent file is changed. This is ideal for my application, since I can cache the XML data until the underlying XML file changes. (For more information on caching options in ASP.NET be sure to read: Caching with ASP.NET.)

    To create this StateManager module create a file in your Project called StateManager.vb and add the following code:

    Imports System, System.Web.Caching, _
                    System.Xml, Microsoft.VisualBasic
    
    ''' <summary>
    ''' Provides the functionality related to retrieving the list 
    ''' of states for a system; this is meant for US states, 
    ''' territories, and Canadian provinces.  It can also be used 
    ''' for other countries that have states or analogous areas.  It 
    ''' uses the States.config file as its data source.
    ''' </summary>
    Public Module StateManager
       ' Cache object that will be used to store and retrieve items from
       ' the cache and constants used within this object
       Private myCache As Cache = System.Web.HttpRuntime.Cache() 
       Private stateKey As String = "StateKey"
       Public applicationConstantsFileName As String = _
          Replace(System.AppDomain.CurrentDomain.BaseDirectory & _
                       "States.config", "/", "")
       Private stateArray As State()
       Private errorList As ArrayList
    
       
       ' Tells you whether or not any errors have occurred w/in the module
       Public ReadOnly Property hasErrors() As Boolean
          Get
             If errorList Is Nothing OrElse errorList.Count = 0 Then
                Return False
             Else
                 Return True
             End If
          End Get
       End Property
    
    
       ' Retrieves an array list of Exception objects
       Public ReadOnly Property getErrors() As ArrayList
          Get
             Return errorList
          End Get
       End Property
    
           
       ' Private method used to add errors to the errorList
       Private Sub addError(ByRef e As Exception)
          If errorList Is Nothing Then errorList = New ArrayList
          errorList.Add(e)
       End Sub
           
       ''' <summary>
       ''' Gets all the states
       ''' </summary>
       ''' <returns>An array of State objects</returns>
       Public Function getStates() As State()
          If myCache(stateKey) Is Nothing Then
             PopulateCache()
          End If
          Return stateArray
       End Function
    
           
       ''' <summary>
       ''' Takes the abbreviation given and returns the full name
       ''' </summary>
       ''' <returns>The full name for the abbreviation in 
       ''' question</returns>
       Private Function convertAbbreviationToName(ByRef abbreviation _
                                                     As String) As String
          Dim xmlFile As New XmlDocument()
    
          Try
             xmlFile.Load(applicationConstantsFileName)
             Dim theNode As XmlNode = _
                xmlFile.SelectSingleNode("descendant::state[@abbreviation='" & _
                                          abbreviation & "']")
    
             If Not theNode Is Nothing Then _
                Return theNode.Attributes.GetNamedItem("name").Value
    
          Catch e As Exception
             addError(e)
    
          End Try
    
          Return vbNullString
       End Function
    
           
       ''' <summary>
       ''' Gets the state object based on the full name
       ''' </summary>
       ''' <param name="name">The full name of the state to 
       ''' retrieve</param>
       ''' <returns>A State object for the name given</returns>
       Public Function getStateByName(ByRef name As String) As State
          If myCache(stateKey & name) Is Nothing Then PopulateCache()
          Return myCache(stateKey & name)
       End Function
    
           
       ''' <summary>
       ''' Gets the state object based on the abbreviation
       ''' </summary>
       ''' <param name="abbreviation">The abbreviation of the state 
       ''' to retrieve</param>
       ''' <returns>A State object for the abbreviation 
       ''' given</returns>
       Public Function getStateByAbbreviation(ByRef abbreviation _
                                                        As String) As State
          Dim name As String = convertAbbreviationToName(abbreviation)
          If name <> vbNullString Then
             Return getStateByName(name)
          Else
             Return Nothing
          End If
       End Function
    
           
       '''<summary>The manager 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 methods that return State 
       ''' objects are called again, the XML file won't exist in memory 
       ''' and the PopulateCache will be re-called.
       ''' </summary>
       Private Sub PopulateCache()
          Dim xmlFile As New XmlDocument()
          Dim theState As State
          Dim theNode As XmlNode
          Dim theName, theAbbreviation As String
          Dim i As Integer = 0
    
          Try
             xmlFile.Load(applicationConstantsFileName)
    
             'Attempt to find the element given the "key" for that tag
             Dim elementList As XmlNodeList = _
                        xmlFile.GetElementsByTagName("state")
    
             If Not elementList Is Nothing Then
                stateArray = Array.CreateInstance(GetType(State), _
                                                           elementList.Count)
                    
                'Loop through each element that has the name we're looking for
                For i=0 To elementList.Count-1
                   theNode = elementList(i)
                    
                   'Get the name for that tag
                   If Not theNode.Attributes.GetNamedItem("name") Is Nothing Then
                      theName = theNode.Attributes.GetNamedItem("name").Value
                   Else
                      theName = vbNullString
                   End If
    
                   'Get the abbreviation for that tag
                   If Not theNode.Attributes.GetNamedItem("abbreviation") _
                                                                 Is Nothing Then
                      theAbbreviation = _
                            theNode.Attributes.GetNamedItem("abbreviation").Value
                   Else
                      theAbbreviation = vbNullString
                   End If
    
                   'Populate that location in the array with the
                   ' values for the tag
                   stateArray(i) = New State(theName, theAbbreviation)
    
                   'Insert the state into cache
                   myCache.Insert(stateKey & theName, stateArray(i), _
                           New CacheDependency(applicationConstantsFileName))
    
                Next
    
                'Insert the state array into cache
                myCache.Insert(stateKey, stateArray, _
                           New CacheDependency(applicationConstantsFileName))
    
             End If 
    
          Catch e As Exception
             addError(e)
    
          End Try
       End Sub      
    End Module

    Using the StateManager Module in an Application

    To use the StateManager module in a .NET application start by creating a StateManager.vb file in your Visual Studio .NET Project and add the code above, as well as the code for the State class in a State.vb file. Next, add the states.config file. If you are wanting to use this in a WinForms application, place the <states.config> file in the application’s bin directory; if you’re using an ASP.NET Web application, place the <states.config> file in the Web application’s root directory.

    Since the <StateManager> is a module, you can use its methods without creating a class instance. This first code snippet shows how to get back the abbreviation from a state knowing that the full state name is “Virginia”:

    Dim theState As State = StateManager.getStateByName("Virginia")  
    If Not theState Is Nothing Then 
       Response.WriteLine("Abbr: " & theState.Abbreviation)
    End If

    This next example shows how to get all of the states, and enumerate through them in a For loop:

    Dim i As Integer = 0
    Dim theStates() As State = StateManager.getStates()
    If Not theStates Is Nothing Then 
       For i=0 To theStates.Length-1
          Response.WriteLine(theState.FullAndAbbrev & "<br>")
       Next
    End If

    Finally, here’s an example of using a databound DropDownList to display the states. In the Web page’s HTML portion add a DropDownList like so:

    <asp:DropDownList id=”stateList” runat=”server” />

    Then, in the code-behind class’s Page_Load event handler you can bind the state data to the DropDownList:

    Private Sub Page_Load(ByVal sender As System.Object, _
                     ByVal e As System.EventArgs) Handles MyBase.Load
        'Put user code to initialize the page here
        stateList.DataSource = StateManager.getStates()
        stateList.DataTextField = "FullAndAbbrev"
        stateList.DataValueField = "Name"
        stateList.DataBind()
    End Sub

    Conclusion

    In this article we examined a technique for storing state and province data in an XML file. We saw how to extract the XML data using a StateManager module. Before wrapping things up, let’s take a look at some of the pros and cons of this approach. First, the pros:

  • Changes to the underlying states/province data are automatic and don’t require a recompile – Deployments to multiple environments are easier, as is maintaining the code.
  • Switching back and forth between using names and abbreviations is easy
  • And now, some of the cons:

  • Data Not Sorted – The getStates method retrieves State objects in the order they appear in the XML file. (If you need to sort the results retrieved, you can use the Array.Sort() method. For more information on Array.Sort() be sure to read Sorting an Array Using Array.Sort().)
  • Case Sensitivity – The getStateByName and getStateByAbbreviation methods are case sensitive; therefore, if the parameter doesn’t match the case of the attribute in the XML, the application won’t retrieve anything.
  • An improvement on this code would be to add country information to the XML document, so that a page developer could retrieve the states just from a particular country (as opposed to all of the states/provinces in the XML file). Feel free to use the code presented in this article however you see fit. If you make any improvements on the code, or have further ideas for enhancements, please don’t hesitate to contact me!
  • Happy Programming!

    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.

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    Back To Top