KnowDotNet

Multiple Duplicate Keys in App.Config Files

Easier Way Than IConfiguratSectionHandler

by Les Smith

In Windows applications written before the advent of .NET, developers used various methods for providing configuration information to applications, such as .INI files, System Registry, etc. All .NET applications are designed to read configuration from either the app.config (Winform or console apps) or web.config (web apps) file. This normally means the developer adds configuration data to the appSettings section of the app.config file, as shown below.

<appSettings>
    <add key="myKey" value="myValue" />
</appsettings>


The developer can access this information in the application by the use of the following code:

   Dim value As String = ConfigurationSettings.AppSettings("myKey")

This works fine for most applications, but as application complexity increases, a need arises to have duplicate keys in the appSettings section. There is a problem in the implementation of AppSettings Method.  When the application loads, the app.config file is read by the application (behind the scenes) and the values from the App.Config file are placed in a NamedValueCollection.  The problem is that if you have multiple duplicate keys in a section, only the last value in the section will be placed into the NamedValueCollection.  The reason for this is that the collection works like a  VB Collection, where you cannont have duplicate keys.  If you examine the code through Reflector, you will see that the code is implemented in the following way:

    collection(i).value = key ' if the value already exists

instead of

    collection(i).Add(key)

In order to get around this shortcoming, you can implement a sosphicated solution by using IConfigurationSectionHandler.  However, any chance I have to keep something simple, I always do it.  That's a rule of life with me as a programmer.  To me, there is enough complication in programming Windows and Web applications, that cannot be escaped, to unnecessarily complicate something, just to use something that I have never used in the .NET Framework.  Some will not agree, but as you know, if you have read many of my articles, the KISS (keep it simple stupid) principle is always in my mind.

My simple solution allows multiple "logical" duplicate keys without actually having real duplicates in the appSettings section.  
After all, the whole purpose is to get the data to the application without having to recompile the application every time you want to add the ability to handle new customers, etc., in the application.  In this example, I have the requirement to be able to add a new customer name along with a userid and password to the app.config file.  The application will use the customer names in a combo box, from which the user will select the desired customer.  The application will then pick up the userid and password associated with the customer and use it to call a Web Service.

The code shown below, in the FillCustomerCollection method, will be called by the Form_Load event.  This method will build a collection of CustAcctPW objects.  On return from this method, a method to load the combo box will be called.  Finally, when the user selects a user from the combo box, the application will go through the collection to find the selected customer, and then retrieve the associated userid and password.  I will not show all of that ancillary code, but I will quickly explain the logic behind this simple method.

Instead of having multiple, duplicate keys in appSettings, which will not work, I will place a "NumberCustomers" key and value in the appSettings section of the app.config file.  Next, I will add two "Customer" keys and associated values.  Instead of duplicating the key, I simply give them keys of "Customer001", "Customer002", etc.  The appSetting section that contains my customers is shown below.

<configuration>
   <appSettings>

      
<!-- Add Customer keys with Name|Account|PW -->
     <add key = "NUMBERCUSTOMERS" value = "2" />
     <add key = "CUSTOMER001" value="Jones Co|JONESUser|!23*(Abc1" />
     <add key = "CUSTOMER002" value= "NYTA|NYTAUserid|y*hg01K" />
   </
appSettings>
</configuration>

The NumberCustomers value will determine the number of times to loop to retrieve the customers and associated data values.  Obviously, you want to make sure that the value of NumberCustomers changes if you add or remove a customer, but then any other invalid value in your app.config file will probably cause something ugly in your application.  I use the NumberCustomers as an index which will be converted to string and appended to "Customer" to access the appSetting.  I concatenate multiple values of Customer, Userid (Account), and Password in the value field delimited by "|".  The FillCustomerCollection method is shown below, along with the CustAcctPW class and collection.


   ''' <summary>
   ''' The App Config file will have multiple CUSTNBRnnn keys in appsettings.
   ''' It will also have a NumberCustomers key with value = nbr of CustNbr occurrences.
   ''' Retrieve the number and use it to compute the value of each CustNbrnnn key value.
   ''' This must be done because the ConfigurationSettings.AppSettings method only picks up
   ''' the last value of a duplicate key name (bummer).  This is because the code that builds
   ''' the NamedValueCollection stores into the collection, if a value exists, instead of
   ''' adding to the collection.  It's built like an old VB Collection.  I.,e., you can't have
   ''' but one occurrence of the same key name.  But we wlll not be daunted...
   ''' </summary>
   ''' <remarks></remarks>
   Private Sub FillCustomerCollection()
      
Dim nbrCusts As Integer = _
        
CType(ConfigurationSettings.AppSettings("NUMBERCUSTOMERS"), Integer)
      colCustomers =
New Collection

      
For i As Integer = 1 To nbrCusts
        
Dim custKey As String = "CUSTOMER" & Format(i, "000")
        
Dim custValue As String = ConfigurationSettings.AppSettings(custKey)
        
Dim cust As New CustAcctPW
        
Dim m As Match = Regex.Match(custValue, _
            
"(?<cust>\w+)\|(?<acct>\w+)\|(?<pw>\w+)")
        
If m.Success Then
            cust.Customer = m.Groups("cust").Value
            cust.Account = m.Groups(
"acct").Value
            cust.PW = m.Groups(
"pw").Value
            colCustomers.Add(cust)
        
End If
      Next
   End Sub

   Private colCustomers As Collection
   Public Class CustAcctPW
      
Public Customer As String
      Public Account As String
      Public PW As String
   End
Class

The following method fills the combo box from the collection of customer data created by the FillCustomerCollection method shown above.

   ' Fill the customer combo box with customer numbers from the
   ' colCustomers collection.
   Private Sub FillCustomerCombo(ByVal colCustomers As Collection)
      
Me.cboSelectCustomer.Items.Clear()
      
For i As Integer = 1 To colCustomers.Count
        
Dim cust As CustAcctPW = CType(colCustomers(i), CustAcctPW)
        
Me.cboSelectCustomer.Items.Add(cust.Customer)
      
Next
   End Sub

Finally, the following combo box event handler fills the userid and password text boxes when the user selects a customer in the combo box.

   Private Sub cboSelectCustomer_SelectedIndexChanged(ByVal sender As System.Object, _
      
ByVal e As System.EventArgs) Handles cboSelectCustomer.SelectedIndexChanged
      
If formLoading Then Exit Sub
      For i As Integer = 1 To colCustomers.Count
        
Dim cust As CustAcctPW = CType(colCustomers(i), CustAcctPW)
        
If cust.Customer = cboSelectCustomer.Text Then
            Me.txtAccount.Text = cust.Account
            
Me.txtPassword.Text = cust.PW
        
End If
      Next
   End Sub