C# XML Configuration File Read and Write Operations
XML files serve as excellent configuration sources due to their human-readable structure. Unlike INI files, XML supports complex nested configurations including lists and hierarchical data structures, providing greater flexibility for application settings.
Working with XML in C#
The System.Xml namespace providesXmlDocument for loading and manipulating XML files. The process involves loading the XML document, navigating to required nodes via XPath, extract or modify values, then save changes back to disk.
This guide presents a reusable XML utility class and configuration manager implementation.
Sample XML Structure
<?xml version="1.0" encoding="utf-8" ?>
<Configuration>
<Logging Mode="file" />
<Server Host="localhost" Port="8080" />
<Users>
<User Id="101" Name="Alice" Role="admin" />
<User Id="102" Name="Bob" Role="developer" />
<User Id="103" Name="Charlie" Role="viewer" />
</Users>
</Configuration>
XmlUtility.cs
public static class XmlUtility
{
public static bool TryGetElementValueAsInt(XmlNode parentNode, string xpath, out int result)
{
var text = parentNode?.SelectSingleNode(xpath)?.InnerText.Trim();
return int.TryParse(text, out result);
}
public static bool TryGetElementValueAsString(XmlNode parentNode, string xpath, out string result)
{
result = parentNode?.SelectSingleNode(xpath)?.InnerText.Trim();
return !string.IsNullOrEmpty(result);
}
public static void UpdateElementText(XmlNode parentNode, string xpath, string newText)
{
var targetElement = parentNode?.SelectSingleNode(xpath);
if (targetElement != null)
{
targetElement.InnerText = newText;
}
}
public static bool TryGetAttributeAsInt(XmlNode parentNode, string xpath, out int result)
{
var text = parentNode?.Attributes?.GetNamedItem(xpath)?.Value.Trim();
return int.TryParse(text, out result);
}
public static bool TryGetAttributeAsString(XmlNode parentNode, string xpath, out string result)
{
result = parentNode?.Attributes?.GetNamedItem(xpath)?.Value.Trim();
return !string.IsNullOrEmpty(result);
}
public static void UpdateAttributeValue(XmlNode parentNode, string xpath, string newValue)
{
var targetAttr = parentNode?.Attributes?.GetNamedItem(xpath);
if (targetAttr != null)
{
targetAttr.Value = newValue;
}
}
}
ConfigurationManager.cs
internal class UserEntry
{
public int Id { get; set; }
public string Name { get; set; }
public string Role { get; set; }
public UserEntry(int id, string name, string role)
{
Id = id;
Name = name;
Role = role;
}
}
internal class ServerSettings
{
public string Host { get; set; }
public int Port { get; set; }
public ServerSettings()
{
Port = 80;
}
}
internal sealed class ConfigurationManager
{
public string LogMode { get; private set; }
public ServerSettings ServerConfig { get; }
#region Connection Parameters
public string DatabaseHost { get; private set; }
public int DatabasePort { get; private set; } = 1433;
public string DatabaseName { get; private set; }
public string DatabaseUser { get; private set; }
public string DatabasePassword { get; private set; }
public string ConnectionString =>
$"Data Source={DatabaseHost},{DatabasePort};Initial Catalog={DatabaseName};Integrated Security=False;User ID={DatabaseUser};Password={DatabasePassword}";
#endregion
private string _fileName;
private static readonly ILogger Logger = LogManager.GetLogger(nameof(ConfigurationManager));
public void Initialize(string configFileName)
{
_fileName = configFileName;
var assemblyLocation = new FileInfo(Assembly.GetExecutingAssembly().Location);
var xmlPath = Path.Combine(assemblyLocation.DirectoryName ?? string.Empty, _fileName);
var xmlDoc = new XmlDocument();
try
{
xmlDoc.Load(xmlPath);
}
catch (Exception ex)
{
Logger.Error($"Failed to load configuration file [{configFileName}]: {ex.Message}");
return;
}
var root = xmlDoc.SelectSingleNode("/Configuration");
LoadLoggingSettings(root);
LoadServerSettings(root);
LoadDatabaseSettings(root);
LoadUserList(root);
}
#region Loading Methods
private void LoadLoggingSettings(XmlNode root)
{
try
{
var loggingNode = root?.SelectSingleNode("Logging");
if (XmlUtility.TryGetAttributeAsString(loggingNode, "Mode", out var str))
{
LogMode = str;
}
}
catch (Exception ex)
{
Logger.Error($"Error loading logging settings: {ex.Message}");
}
}
private void LoadServerSettings(XmlNode root)
{
try
{
var serverNode = root?.SelectSingleNode("Server");
if (XmlUtility.TryGetAttributeAsString(serverNode, "Host", out var host))
{
ServerConfig.Host = host;
}
if (XmlUtility.TryGetAttributeAsInt(serverNode, "Port", out var port))
{
ServerConfig.Port = port;
}
}
catch (Exception ex)
{
Logger.Error($"Error loading server settings: {ex.Message}");
}
}
private void LoadDatabaseSettings(XmlNode root)
{
try
{
var dbNode = root?.SelectSingleNode("Database");
if (XmlUtility.TryGetAttributeAsString(dbNode, "Host", out var host))
{
DatabaseHost = host;
}
if (XmlUtility.TryGetAttributeAsInt(dbNode, "Port", out var port))
{
DatabasePort = port;
}
if (XmlUtility.TryGetAttributeAsString(dbNode, "Name", out var name))
{
DatabaseName = name;
}
if (XmlUtility.TryGetAttributeAsString(dbNode, "User", out var user))
{
DatabaseUser = user;
}
if (XmlUtility.TryGetAttributeAsString(dbNode, "Password", out var password))
{
DatabasePassword = password;
}
}
catch (Exception ex)
{
Logger.Error($"Error loading database settings: {ex.Message}");
}
}
private void LoadUserList(XmlNode root)
{
try
{
var usersNode = root?.SelectSingleNode("Users");
var userNodes = usersNode?.SelectNodes("User");
if (userNodes == null || userNodes.Count == 0)
{
return;
}
foreach (XmlNode node in userNodes)
{
{
int id;
string name, role;
if (XmlUtility.TryGetAttributeAsInt(node, "Id", out id) &&
XmlUtility.TryGetAttributeAsString(node, "Name", out name) &&
XmlUtility.TryGetAttributeAsString(node, "Role", out role))
{
UserCollection.Add(new UserEntry(id, name, role));
}
}
}
catch (Exception ex)
{
Logger.Error($"Error loading user list: {ex.Message}");
}
}
#endregion
#region Update Methods
public void UpdateDatabaseSettings(string host, string name, string user, string password)
{
DatabaseHost = host;
DatabaseName = name;
DatabaseUser = user;
DatabasePassword = password;
var assemblyLocation = new FileInfo(Assembly.GetExecutingAssembly().Location);
var xmlPath = Path.Combine(assemblyLocation.DirectoryName ?? string.Empty, _fileName);
var xmlDoc = new XmlDocument();
try
{
xmlDoc.Load(xmlPath);
var dbNode = xmlDoc.SelectSingleNode("/Configuration/Database");
XmlUtility.UpdateAttributeValue(dbNode, "Host", host);
XmlUtility.UpdateAttributeValue(dbNode, "Name", name);
XmlUtility.UpdateAttributeValue(dbNode, "User", user);
XmlUtility.UpdateAttributeValue(dbNode, "Password", password);
xmlDoc.Save(xmlPath);
}
catch (Exception ex)
{
Logger.Error($"Failed to update database settings: {ex.Message}");
throw;
}
}
#endregion
#region Singleton Pattern
private static ConfigurationManager _instance;
private static readonly object SyncObject = new object();
private ConfigurationManager()
{
ServerConfig = new ServerSettings();
}
public static ConfigurationManager Instance
{
get
{
if (_instance != null)
{
return _instance;
}
lock (SyncObject)
{
_instance ??= new ConfigurationManager();
}
return _instance;
}
}
#endregion
}
Usage Example
ConfigurationManager.Instance.Initialize("config.xml");
var serverHost = ConfigurationManager.Instance.ServerConfig.Host;
var connectionString = ConfigurationManager.Instance.ConnectionString;
Key Implementation Details
The XmlUtility class provides generic methods for retrieving and updating both element values and attributes. The TryParse pattern ensures safe type conversions without exceptions. The ConfigurationManager implements singleton patern for centralized access across the application.
File paths are resolved relative to the executing assembly location using DirectoryInfo and Path.Combine, ensuring consistent behavior regardless of deployment directory.
Error handling wraps file operations in try-catch blocks, logging failures while allowing the application to continue with default values or handle errors appropriately.