RSS Home Newsletter Advertising
Visit Twellow.com

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.

    Digg This! StumbleUpon This!


    News Tags: State, XML
    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.

    Comments

    Post new comment

    The content of this field is kept private and will not be shown publicly.
    CAPTCHA
    This question is for testing whether you are a human visitor and to prevent automated spam submissions.
    4 + 14 =
    Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.
    Subscribe to WebProNews


    Send me relevant info