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:
And now, some of the cons:
Array.Sort() be sure to read Sorting an Array Using Array.Sort().) 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.