title: Reading an XML Configuration Data File from C#
body: <p>I have a standalone XML file named config.xml that goes with my application that contains basically two sections:</p>
  
  <p>1) Global settings
  2) Server List, including settings</p>
  
  <p>Basically, the global settings would contain a Database Username and Database Password that my program would use for each server listing.</p>
  
  <p>The Server listing entries contain a list of servers, along with some filenames, and Databse Username and Database Password.  The only thing significant here is that if I specify a Username/password in the server list, then it will use this rather than the global database username and password.   Or, said differently, if the database username and password is not defined within the servewr list entry, it will use the global database username pasword.</p>
  
  <p>My program basically loops through and xml configuration file and executes some database queries against each DB2 server and processes the information and creates a report.   It works today, but I do have a few issues...</p>
  
  <p>1) Every time I add a new element to my XML configuration file, I have to add it for each node that I have created, otherwise I get XML parse errors.</p>
  
  <p>2) I'd like to categeory my configuration XML file rather than lump everything in one the same node and include empty elements.</p>
  
  <p>Sample XML is below:</p>
  
  <pre><code>&lt;?xml version="1.0" encoding="utf-8" ?&gt;
  &lt;Config&gt;
      &lt;Global&gt;
      &lt;OutputFolder&gt;C:\DATA\Configs\DB2\&lt;/OutputFolder&gt;
      &lt;DBUser&gt;DB2ADMIN&lt;/DBUser&gt;
      &lt;DBPassword&gt;%SecretPassword%&lt;/DBPassword&gt;
      &lt;FTPFiles&gt;false&lt;/FTPFiles&gt;
      &lt;FTPTcpIp&gt;127.0.0.1&lt;/FTPTcpIp&gt;
      &lt;FTPUser&gt;FTPLogin1&lt;/FTPUser&gt;
      &lt;FTPPassword&gt;P@ssw0rd&lt;/FTPPassword&gt;
      &lt;FTPRemoteFolder&gt;/configs&lt;/FTPRemoteFolder&gt;
      &lt;/Global&gt;
      &lt;Servers&gt;
          &lt;Server enabled="true"&gt;
          &lt;Category&gt;Report1&lt;/Category&gt;
          &lt;TcpIp&gt;192.168.26.107&lt;/TcpIp&gt;
          &lt;Database&gt;SampleData&lt;/Database&gt;
          &lt;User&gt;&lt;/User&gt;
          &lt;Password&gt;&lt;/Password&gt;
          &lt;Report1FileNameList&gt;List1.txt&lt;/Report1FileNameList&gt;
          &lt;Report1FileNameRoutes&gt;Routes1.txt&lt;/Report1FileNameRoutes&gt;
          &lt;Report1FileNameRouteTimeouts&gt;Timeouts1.txt&lt;/Report1FileNameRouteTimeouts&gt;
          &lt;Report1FileNameEndpoints&gt;&lt;/Report1FileNameEndpoints&gt;
          &lt;Report2FilenameServers&gt;&lt;/Report2FilenameServers&gt;
          &lt;Report2FilenameRoutingGroup&gt;&lt;/Report2FilenameRoutingGroup&gt;
       &lt;/Server&gt;
       &lt;Server enabled="true"&gt;
           &lt;Category&gt;Report1&lt;/Category&gt;
           &lt;TcpIp&gt;192.168.26.107&lt;/TcpIp&gt;
           &lt;Database&gt;SampleDataB&lt;/Database&gt;
           &lt;User&gt;&lt;/User&gt;
           &lt;Password&gt;&lt;/Password&gt;
           &lt;Report1FileNameList&gt;List1.txt&lt;/Report1FileNameList&gt;
           &lt;Report1FileNameRoutes&gt;Routes1.txt&lt;/Report1FileNameRoutes&gt;
           &lt;Report1FileNameRouteTimeouts&gt;Timeouts1.txt&lt;/Report1FileNameRouteTimeouts&gt;
           &lt;Report1FileNameEndpoints&gt;&lt;/Report1FileNameEndpoints&gt;
           &lt;Report2FilenameServers&gt;&lt;/Report2FilenameServers&gt;
           &lt;Report2FilenameRoutingGroup&gt;&lt;/Report2FilenameRoutingGroup&gt;
        &lt;/Server&gt;
        &lt;Server enabled="true"&gt;
            &lt;Category&gt;Report2&lt;/Category&gt;
            &lt;TcpIp&gt;192.168.26.107&lt;/TcpIp&gt;
            &lt;Database&gt;SampleDataE&lt;/Database&gt;
            &lt;User&gt;&lt;/User&gt;
            &lt;Password&gt;&lt;/Password&gt;
            &lt;Report1FileNameList&gt;&lt;/Report1FileNameList&gt;
            &lt;Report1FileNameRoutes&gt;&lt;/Report1FileNameRoutes&gt;
            &lt;Report1FileNameRouteTimeouts&gt;&lt;/Report1FileNameRouteTimeouts&gt;
            &lt;Report1FileNameEndpoints&gt;Endpoints2.txt&lt;/Report1FileNameEndpoints&gt;
            &lt;Report2FilenameServers&gt;Servers2.txt&lt;/Report2FilenameServers&gt;
            &lt;Report2FilenameRoutingGroup&gt;Groups2.txt&lt;/Report2FilenameRoutingGroup&gt;
        &lt;/Server&gt;
        &lt;Server enabled="true"&gt;
            &lt;Category&gt;Report2&lt;/Category&gt;
            &lt;TcpIp&gt;192.168.26.108&lt;/TcpIp&gt;
            &lt;Database&gt;SampleDatabase1_D&lt;/Database&gt;
            &lt;User&gt;&lt;/User&gt;
            &lt;Password&gt;&lt;/Password&gt;
            &lt;Report1FileNameList&gt;&lt;/Report1FileNameList&gt;
            &lt;Report1FileNameRoutes&gt;&lt;/Report1FileNameRoutes&gt;
            &lt;Report1FileNameRouteTimeouts&gt;&lt;/Report1FileNameRouteTimeouts&gt;
            &lt;Report1FileNameEndpoints&gt;Endpoints2.txt&lt;/Report1FileNameEndpoints&gt;
            &lt;Report2FilenameServers&gt;Servers2.txt&lt;/Report2FilenameServers&gt;
            &lt;Report2FilenameRoutingGroup&gt;Groups1.txt&lt;/Report2FilenameRoutingGroup&gt;
         &lt;/Server&gt;
      &lt;/Servers&gt;
  </code></pre>
  
  <p>Sample code is below:</p>
  
  <pre><code>        // load XML file
          try
          {
              // Config/Global
              System.Xml.XPath.XPathDocument doc = new System.Xml.XPath.XPathDocument(@"config.xml");
  
              foreach (System.Xml.XPath.XPathNavigator child in doc.CreateNavigator().Select("Config/Global"))
              {
                  xml_global_outputFolder = child.SelectSingleNode("OutputFolder").Value;
                  xml_global_DBuser = child.SelectSingleNode("DBUser").Value;
                  xml_global_DBpassword = child.SelectSingleNode("DBPassword").Value;
                  xml_global_FTPFiles = bool.Parse(child.SelectSingleNode("FTPFiles").Value);
                  xml_global_FTPTcpIp = child.SelectSingleNode("FTPTcpIp").Value;
                  xml_global_FTPUser = child.SelectSingleNode("FTPUser").Value;
                  xml_global_FTPPassword = child.SelectSingleNode("FTPPassword").Value;
                  xml_global_FTPRemoteFolder = child.SelectSingleNode("FTPRemoteFolder").Value;
              }
  
              // Config/Servers
              //System.Xml.XPath.XPathDocument doc = new System.Xml.XPath.XPathDocument(@"config.xml");
              foreach (System.Xml.XPath.XPathNavigator child in doc.CreateNavigator().Select("Config/Servers/*"))
              {
  
                  //string xml_enabled = child.GetAttribute("Enabled", "");
                  string xml_category = child.SelectSingleNode("Category").Value;
                  string xml_tcpip = child.SelectSingleNode("TcpIp").Value;
                  string xml_database = child.SelectSingleNode("Database").Value;
                  string xml_user = child.SelectSingleNode("User").Value;
                  string xml_password = child.SelectSingleNode("Password").Value;
  
                  Console.WriteLine("Connecting to {0} using database {1} for {2} information...", xml_tcpip, xml_database, xml_category);
  
                  // if node user value is empty, use global
                  if (xml_user == string.Empty)
                  {
                      DB2_user = xml_global_DBuser;
                  }
                  else
                  {
                      DB2_user = xml_user;
                  }
  
                  // if node password value is empty, use global
                  if (xml_password == string.Empty)
                  {
                      DB2_password = xml_global_DBpassword;
                  }
                  else
                  {
                      DB2_password = xml_password;
                  }
  
                  string txtFilename = string.Empty;
                  string csvFilename = string.Empty;
  
                  switch (xml_category.ToUpper())
                  {
                      case "SAMPLE":
                          txtFilename = Path.Combine(xml_global_outputFolder, @"EMPLOYEE.csv");
                          csvFilename = Path.Combine(xml_global_outputFolder, Path.GetFileNameWithoutExtension(@"EMPLOYEE.csv"));
                          ExecuteQuery(xml_category, "SAMPLE", xml_tcpip, DB2_user, DB2_password, xml_database, csvFilename, txtFilename, option_debug);
                          break;
                  }
                  Console.WriteLine("");
  
              }
          }
          catch (Exception e)
          {
              Console.WriteLine("Exception: {0}", e.Message);
              Environment.Exit(1);
          }
  
          Environment.Exit(0);
      }
  </code></pre>
  
  <p>I guess ideally, I'd like to create node-specific configurations and if the element is blank, the code should be able to handle either a missing element or an empty element.  Maybe using an attribute for the category, instead?  Something like:</p>
  
  <pre><code>   &lt;Config&gt;
    &lt;Global&gt;
       &lt;OutputFolder&gt;&lt;/OutputFolder&gt;
       &lt;DBUser&gt;&lt;/DBUser&gt;
       &lt;DBPassword&gt;&lt;DBPassword&gt;
    &lt;/Global&gt;
    &lt;Servers category="Report1"&gt;
       &lt;Server&gt;
          &lt;TcpIP&gt;whatever&lt;/TcpIP&gt;
          &lt;User&gt;whatever&gt;&lt;/User&gt;
          &lt;Password&gt;whatever&gt;&lt;/Password&gt;
       &lt;/Server&gt;
       &lt;Server&gt;
          &lt;TcpIP&gt;whatever&lt;/TcpIP&gt;
          &lt;User&gt;whatever&gt;&lt;/User&gt;
          &lt;Password&gt;whatever&gt;&lt;/Password&gt;
       &lt;/Server&gt;
       &lt;Server&gt;
          &lt;TcpIP&gt;whatever&lt;/TcpIP&gt;
          &lt;User&gt;whatever&gt;&lt;/User&gt;
          &lt;Password&gt;whatever&gt;&lt;/Password&gt;
       &lt;/Server&gt;
   &lt;/Server&gt;
    &lt;Servers category="Report2"&gt;
       &lt;Server&gt;
          &lt;TcpIP&gt;whatever&lt;/TcpIP&gt;
          &lt;User&gt;whatever&gt;&lt;/User&gt;
          &lt;Password&gt;whatever&gt;&lt;/Password&gt;
       &lt;/Server&gt;
       &lt;Server&gt;
          &lt;TcpIP&gt;whatever&lt;/TcpIP&gt;
          &lt;User&gt;whatever&gt;&lt;/User&gt;
          &lt;Password&gt;whatever&gt;&lt;/Password&gt;
       &lt;/Server&gt;
       &lt;Server&gt;
          &lt;TcpIP&gt;whatever&lt;/TcpIP&gt;
          &lt;User&gt;whatever&gt;&lt;/User&gt;
          &lt;Password&gt;whatever&gt;&lt;/Password&gt;
       &lt;/Server&gt;
   &lt;/Server&gt;
    &lt;Servers category="AccessList"&gt;
       &lt;Server&gt;
          &lt;TcpIP&gt;whatever&lt;/TcpIP&gt;
          &lt;Database&gt;whatever&gt;&lt;/Database&gt;
          &lt;Active&gt;whatever&lt;/Active&gt;
       &lt;/Server&gt;
       &lt;Server&gt;
          &lt;TcpIP&gt;whatever&lt;/TcpIP&gt;
          &lt;Database&gt;whatever&gt;&lt;/Database&gt;
          &lt;Active&gt;whatever&lt;/Active&gt;
       &lt;/Server&gt;
       &lt;Server&gt;
          &lt;TcpIP&gt;whatever&lt;/TcpIP&gt;
          &lt;Database&gt;whatever&gt;&lt;/Database&gt;
          &lt;Active&gt;whatever&lt;/Active&gt;
       &lt;/Server&gt;
   &lt;/Server&gt;
  &lt;/Config&gt;
  </code></pre>