Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

C# XML Configuration File Read and Write Operations

Tech May 20 3

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.

Tags: C#xml

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.