To read the article online, visit http://www.4GuysFromRolla.com/articles/022004-1.aspx

Using XML to Store States and Provinces

By Rachael Schoenbaum


Introduction


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!

  • By Rachael Schoenbaum


    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.

  • Article Information
    Article Title: ASP.NET.Using XML to Store States and Provinces
    Article Author: Rachael Schoenbaum
    Published Date: February 20, 2004
    Article URL: http://www.4GuysFromRolla.com/articles/022004-1.aspx


    Copyright 2017 QuinStreet Inc. All Rights Reserved.
    Legal Notices, Licensing, Permissions, Privacy Policy.
    Advertise | Newsletters | E-mail Offers