ZetCode

C# XmlDocument

last modified October 18, 2026

In this article we show how to work with XML in C# with XmlDocument.

Extensible Markup Language (XML) is a markup language that defines a set of rules for encoding documents in a format that is both human-readable and machine-readable. XML is often used for application configuration, data storage, and data exchange.

XML is similar to HTML, but does not have predefined tags; we can design our own tags.

XmlDocument

The XmlDocument class represents an XML document. It can be used to load, modify, validate, and navigate XML documents.

It is an in-memory representation of an XML document that implements the W3C XML Document Object Model (DOM). The whole document is read into memory, which makes it easy to navigate and edit, but costly for very large files. For forward-only reading of big documents, see XmlReader; for writing, XmlWriter.

The Document Object Model (DOM) is a language-independent programming interface for HTML and XML documents. Through the DOM interface, programs can change the document structure, style, and content. The DOM represents the document as a tree of nodes and objects. The XmlElement is the most common node type in the XmlDocument; other types include XmlAttribute, XmlText, XmlComment, and XmlCDataSection.

XPath (XML Path Language) is a query language for selecting nodes from an XML document. It can also be used to compute values from the content of an XML document.

Note: for new code, XDocument from LINQ to XML (System.Xml.Linq) is usually easier to use than XmlDocument, because it integrates with LINQ queries and has a more compact API. We use XmlDocument when we need the DOM API, XPath with namespaces managers, or when we work with existing code.

In the examples, we work with the following files. They are in the current working directory of the programs.

words.xml
<?xml version="1.0" encoding="UTF-8"?>
<words>
    <word>falcon</word>
    <word>sky</word>
    <word>bottom</word>
    <word>cup</word>
    <word>book</word>
    <word>rock</word>
    <word>sand</word>
    <word>river</word>
</words>

This is the words.xml file.

users.xml
<?xml version="1.0" encoding="UTF-8"?>
<users>
  <user id="1">
    <name>John Doe</name>
    <occupation>gardener</occupation>
  </user>
  <user id="2">
    <name>Jane Doe</name>
    <occupation>teacher</occupation>
  </user>
  <user id="3">
    <name>Roger Roe</name>
    <occupation>driver</occupation>
  </user>
  <user id="4">
    <name>Lucia Smith</name>
    <occupation>shopkeeper</occupation>
  </user>
</users>

This is the users.xml file.

continents.xml
<?xml version="1.0" encoding="UTF-8"?>
<continents>
    <europe>
        <slovakia>
            <capital>Bratislava</capital>
            <population>421000</population>
        </slovakia>
        <hungary>
            <capital>Budapest</capital>
            <population>1759000</population>
        </hungary>
        <poland>
            <capital>Warsaw</capital>
            <population>1735000</population>
        </poland>
    </europe>
    <asia>
        <china>
            <capital>Beijing</capital>
            <population>21700000</population>
        </china>
        <vietnam>
            <capital>Hanoi</capital>
            <population>7500000</population>
        </vietnam>
    </asia>
</continents>

This is the continents.xml file.

C# XmlDocument DocumentElement

The DocumentElement property returns the root XmlElement of the document.

Program.cs
using System.Xml;

var xmlData = """
    <?xml version="1.0" encoding="UTF-8"?>
    <words>
        <word>falcon</word>
        <word>sky</word>
        <word>bottom</word>
        <word>cup</word>
    </words>
    """;

var doc = new XmlDocument();
doc.LoadXml(xmlData);

XmlElement? root = doc.DocumentElement;

Console.WriteLine(root?.Name);
Console.WriteLine(root?.FirstChild?.InnerText);
Console.WriteLine(root?.LastChild?.InnerText);
Console.WriteLine(root?.OuterXml);
Console.WriteLine(root?.InnerXml);

We load XML data into the XmlDocument and get the root element of the document. Then we call properties of the root node.

var doc = new XmlDocument();
doc.LoadXml(xmlData);

We create an XmlDocument and parse the XML data with the LoadXml method. To read a file, we use Load with the file name. The XML data is a raw string literal; its indentation is removed.

Console.WriteLine(root?.Name);

The Name property returns the name of the node. The DocumentElement property is null for an empty document, which is why we use the null-conditional operator.

Console.WriteLine(root?.FirstChild?.InnerText);
Console.WriteLine(root?.LastChild?.InnerText);

We get the first and last child of the root node with FirstChild and LastChild. The InnerText property returns the concatenated text values of the node and all its children.

Console.WriteLine(root?.OuterXml);
Console.WriteLine(root?.InnerXml);

The OuterXml property gets the markup containing the node and all its children. The InnerXml property gets or sets the markup representing only the children of the node.

$ dotnet run
words
falcon
cup
<words><word>falcon</word><word>sky</word><word>bottom</word><word>cup</word></words>
<word>falcon</word><word>sky</word><word>bottom</word><word>cup</word>

The white space between the elements in the source is not preserved because the document was loaded with the default PreserveWhitespace = false; that is also why FirstChild is the first word element and not a text node.

C# XmlNode.RemoveChild

The XmlNode.RemoveChild method removes the specified child node.

Program.cs
using System.Xml;

var doc = new XmlDocument();
doc.Load("users.xml");

XmlElement root = doc.DocumentElement!;
XmlNode? userNode = root.LastChild;

if (userNode is not null)
{
    root.RemoveChild(userNode);
}

doc.Save("users2.xml");

Console.WriteLine(File.ReadAllText("users2.xml"));

In the example, we remove the last child in the document. Note that the method does not modify the original file; it modifies the in-memory representation of the document, and we save the modified document into a new file.

$ dotnet run
<?xml version="1.0" encoding="UTF-8"?>
<users>
  <user id="1">
    <name>John Doe</name>
    <occupation>gardener</occupation>
  </user>
  <user id="2">
    <name>Jane Doe</name>
    <occupation>teacher</occupation>
  </user>
  <user id="3">
    <name>Roger Roe</name>
    <occupation>driver</occupation>
  </user>
</users>

C# XmlDocument.CreateElement

The XmlDocument.CreateElement method creates an element with the specified name. The element is not part of the document until we insert it.

Program.cs
using System.Xml;

var doc = new XmlDocument();
doc.Load("words.xml");

XmlElement root = doc.DocumentElement!;

XmlElement e1 = doc.CreateElement("word");
e1.InnerText = "eagle";
root.InsertAfter(e1, root.LastChild);

XmlElement e2 = doc.CreateElement("word");
e2.InnerText = "cheetah";
root.InsertBefore(e2, root.FirstChild);

doc.Save("words2.xml");

Console.WriteLine(File.ReadAllText("words2.xml"));

In the example, we create two new elements and insert them into the document.

XmlElement e1 = doc.CreateElement("word");
e1.InnerText = "eagle";

A new element named word is created with CreateElement. Its text is set with the InnerText property. Setting it also escapes special characters such as < and & correctly.

root.InsertAfter(e1, root.LastChild);

The element is inserted after the last child with InsertAfter. We could also call AppendChild.

root.InsertBefore(e2, root.FirstChild);

The second element is inserted before the first element with InsertBefore.

$ dotnet run
<?xml version="1.0" encoding="UTF-8"?>
<words>
  <word>cheetah</word>
  <word>falcon</word>
  <word>sky</word>
  <word>bottom</word>
  <word>cup</word>
  <word>book</word>
  <word>rock</word>
  <word>sand</word>
  <word>river</word>
  <word>eagle</word>
</words>

C# XmlDocument create new document

In the following example, we create a new XML document from objects.

Program.cs
using System.Xml;

var users = new List<User>
{
    new(1, "John Doe", "gardener"),
    new(2, "Jane Doe", "teacher"),
    new(3, "Roger Roe", "driver"),
    new(4, "Lucia Smith", "shopkeeper")
};

var doc = new XmlDocument();
XmlDeclaration xmlDeclaration = doc.CreateXmlDeclaration("1.0", "UTF-8", null);
doc.AppendChild(xmlDeclaration);

XmlElement usersNode = doc.CreateElement("users");
doc.AppendChild(usersNode);

foreach (var user in users)
{
    XmlElement userEl = doc.CreateElement("user");
    userEl.SetAttribute("id", user.Id.ToString());
    usersNode.AppendChild(userEl);

    XmlElement nameEl = doc.CreateElement("name");
    nameEl.InnerText = user.Name;
    userEl.AppendChild(nameEl);

    XmlElement occupationEl = doc.CreateElement("occupation");
    occupationEl.InnerText = user.Occupation;
    userEl.AppendChild(occupationEl);
}

doc.Save(Console.Out);

record User(long Id, string Name, string Occupation);

A new XML document is created from a list of user records.

XmlDeclaration xmlDeclaration = doc.CreateXmlDeclaration("1.0", "UTF-8", null);
doc.AppendChild(xmlDeclaration);

An XmlDeclaration is created and appended to the document. The declaration is the first tag in the document.

XmlElement usersNode = doc.CreateElement("users");
doc.AppendChild(usersNode);

We create the root element of the document. A document can have only one root element.

XmlElement userEl = doc.CreateElement("user");
userEl.SetAttribute("id", user.Id.ToString());

For each user, we create an element and set its attribute. The SetAttribute method is shorter than creating an XmlAttribute object and appending it.

doc.Save(Console.Out);

This time we write the XML data to the console. The Save method also accepts a file name, a stream, a TextWriter, or an XmlWriter.

$ dotnet run
<?xml version="1.0" encoding="utf-8"?>
<users>
  <user id="1">
    <name>John Doe</name>
    <occupation>gardener</occupation>
  </user>
  <user id="2">
    <name>Jane Doe</name>
    <occupation>teacher</occupation>
  </user>
  <user id="3">
    <name>Roger Roe</name>
    <occupation>driver</occupation>
  </user>
  <user id="4">
    <name>Lucia Smith</name>
    <occupation>shopkeeper</occupation>
  </user>
</users>

C# XmlNode.ChildNodes and XmlNodeList.Count

The XmlNode.ChildNodes property returns all the children of the given node as an XmlNodeList, an ordered collection of nodes. Its Count property gives the number of nodes.

Program.cs
using System.Xml;

var doc = new XmlDocument();
doc.Load("words.xml");

XmlNodeList? childNodes = doc.DocumentElement?.ChildNodes;

if (childNodes is null)
{
    Console.WriteLine("no nodes found");
    return;
}

Console.WriteLine($"There are {childNodes.Count} elements");

foreach (XmlNode node in childNodes)
{
    Console.WriteLine(node.InnerText);
}

In the example, we get all the children of the root node, print their number, and print their inner text.

$ dotnet run
There are 8 elements
falcon
sky
bottom
cup
book
rock
sand
river

C# XmlNode.SelectSingleNode

The XmlNode.SelectSingleNode method selects the first XmlNode that matches the XPath expression, or returns null.

Program.cs
using System.Xml;

var doc = new XmlDocument();
doc.Load("users.xml");

string input = "2";

// never put untrusted text into an XPath expression: accept only an integer
if (!int.TryParse(input, out int id))
{
    Console.WriteLine("invalid id");
    return;
}

XmlNode? node = doc.SelectSingleNode($"/users/user[@id='{id}']");

if (node is null)
{
    Console.WriteLine("node not found");
    return;
}

var name = node.SelectSingleNode("name")?.InnerText;
var occupation = node.SelectSingleNode("occupation")?.InnerText;
var uid = node.Attributes?["id"]?.Value;

Console.WriteLine($"Id: {uid}");
Console.WriteLine($"Name: {name}");
Console.WriteLine($"Occupation: {occupation}");

From the users.xml file, we select the user with the given id.

if (!int.TryParse(input, out int id))

Putting text into an XPath expression is like putting text into an SQL string: a value such as ' or '1'='1 would change the meaning of the query (XPath injection). Here we parse the id as an integer first, so only digits can get into the expression. For arbitrary strings, use an XPathNavigator with a compiled expression and a custom context that supplies variables.

XmlNode? node = doc.SelectSingleNode($"/users/user[@id='{id}']");

The expression /users/user[@id='2'] selects the user element under users whose id attribute is 2. The part in square brackets is a predicate.

var name = node.SelectSingleNode("name")?.InnerText;
var uid = node.Attributes?["id"]?.Value;

We read the text of the child elements by name rather than by position, which keeps working when the order of the elements changes. Attributes are available through the Attributes collection.

$ dotnet run
Id: 2
Name: Jane Doe
Occupation: teacher

C# XmlNode.SelectNodes

The XmlNode.SelectNodes method selects a list of nodes matching the XPath expression.

Program.cs
using System.Xml;

var doc = new XmlDocument();
doc.Load("users.xml");

XmlNodeList? nodes = doc.SelectNodes("/users/user");

if (nodes is null)
{
    Console.WriteLine("No users found");
    return;
}

var users = new List<User>();

foreach (XmlNode node in nodes)
{
    long id = long.Parse(node.Attributes!["id"]!.Value);
    string name = node["name"]!.InnerText;
    string occupation = node["occupation"]!.InnerText;

    users.Add(new User(id, name, occupation));
}

users.ForEach(Console.WriteLine);

// XPath predicates and functions
Console.WriteLine("--");

foreach (XmlNode n in doc.SelectNodes("/users/user[occupation='teacher' or @id > 3]/name")!)
{
    Console.WriteLine(n.InnerText);
}

Console.WriteLine(doc.SelectNodes("//user")!.Count);

record User(long Id, string Name, string Occupation);

In the example, we select all users from the users.xml file, and then try some more expressions.

XmlNodeList? nodes = doc.SelectNodes("/users/user");

The /users/user expression selects all user elements. The SelectNodes method returns an XmlNodeList.

long id = long.Parse(node.Attributes!["id"]!.Value);
string name = node["name"]!.InnerText;

We go through the list and create a User record from each node. The indexer node["name"] returns the first child element with that name.

doc.SelectNodes("/users/user[occupation='teacher' or @id > 3]/name")

XPath predicates can combine conditions. This one selects the names of users who are teachers or have an id greater than 3. The expression //user selects all user elements anywhere in the document.

$ dotnet run
User { Id = 1, Name = John Doe, Occupation = gardener }
User { Id = 2, Name = Jane Doe, Occupation = teacher }
User { Id = 3, Name = Roger Roe, Occupation = driver }
User { Id = 4, Name = Lucia Smith, Occupation = shopkeeper }
--
Jane Doe
Lucia Smith
4

C# XmlDocument.GetElementsByTagName

The GetElementsByTagName method returns an XmlNodeList containing all descendant elements that match the specified tag name. It is available on the document and on XmlElement.

Program.cs
using System.Xml;

var doc = new XmlDocument();
doc.Load("continents.xml");

XmlNodeList nodes = doc.GetElementsByTagName("capital");
Console.WriteLine("All capitals:");

foreach (XmlNode node in nodes)
{
    Console.WriteLine(node.InnerText);
}

Console.WriteLine("Only European:");

foreach (XmlNode node in doc.SelectNodes("/continents/europe//capital")!)
{
    Console.WriteLine(node.InnerText);
}

We go over the capital tags in the continents.xml file, and then use an XPath expression to restrict the search to Europe.

$ dotnet run
All capitals:
Bratislava
Budapest
Warsaw
Beijing
Hanoi
Only European:
Bratislava
Budapest
Warsaw

C# XmlDocument.CreateNavigator

The XmlDocument.CreateNavigator method creates an XPathNavigator for navigating the document. An XPathNodeIterator provides an iterator over a selected set of nodes. The navigator also evaluates XPath expressions that compute values.

Program.cs
using System.Xml;
using System.Xml.XPath;

var doc = new XmlDocument();
doc.Load("users.xml");

XPathNavigator nav = doc.CreateNavigator()!;
XPathNodeIterator it = nav.Select("/users/user");

while (it.MoveNext())
{
    XPathNavigator user = it.Current!;

    Console.WriteLine(user.GetAttribute("id", string.Empty));

    XPathNodeIterator children = user.SelectChildren(XPathNodeType.Element);

    while (children.MoveNext())
    {
        Console.WriteLine($"  {children.Current!.Name}: {children.Current.Value}");
    }
}

// XPath can also compute values
Console.WriteLine(nav.Evaluate("count(/users/user)"));
Console.WriteLine(nav.Evaluate("sum(/users/user/@id)"));

We use the XPathNavigator and the XPathNodeIterator to go over the users in the users.xml file.

XPathNodeIterator it = nav.Select("/users/user");

while (it.MoveNext())
{

First, we iterate over the user elements. The Current property is a navigator positioned on the current node.

XPathNodeIterator children = user.SelectChildren(XPathNodeType.Element);

Then we go over the child elements of each user.

Console.WriteLine(nav.Evaluate("count(/users/user)"));
Console.WriteLine(nav.Evaluate("sum(/users/user/@id)"));

Evaluate can run XPath functions such as count and sum; the result is a double.

$ dotnet run
1
  name: John Doe
  occupation: gardener
2
  name: Jane Doe
  occupation: teacher
3
  name: Roger Roe
  occupation: driver
4
  name: Lucia Smith
  occupation: shopkeeper
4
10

C# XmlDocument recursive loop

In the following example, we recursively walk over the nodes of the users.xml file.

Program.cs
using System.Xml;

var doc = new XmlDocument();
doc.Load("users.xml");

Traverse(doc.DocumentElement, 0);

static void Traverse(XmlNode? node, int depth)
{
    if (node is null)
    {
        return;
    }

    string indent = new(' ', depth * 2);

    switch (node)
    {
        case XmlElement element:
            string attrs = string.Concat(element.Attributes.Cast<XmlAttribute>()
                .Select(a => $" {a.Name}={a.Value}"));
            Console.WriteLine($"{indent}{element.Name}{attrs}");
            break;
        case XmlText text:
            Console.WriteLine($"{indent}{text.Value}");
            break;
    }

    foreach (XmlNode child in node.ChildNodes)
    {
        Traverse(child, depth + 1);
    }
}

We traverse the document tree depth first and print the elements, their attributes, and the text, indented by their depth.

Traverse(doc.DocumentElement, 0);

We start by passing the root element to the Traverse method.

switch (node)
{
    case XmlElement element:
        ...
    case XmlText text:
        ...
}

A type pattern tells the kinds of nodes apart. We print an element's name and its attributes, and the value of a text node.

foreach (XmlNode child in node.ChildNodes)
{
    Traverse(child, depth + 1);
}

We recurse into every child with a deeper indentation. Looping over ChildNodes is simpler than following FirstChild and NextSibling manually.

$ dotnet run
users
  user id=1
    name
      John Doe
    occupation
      gardener
  user id=2
    name
      Jane Doe
    occupation
      teacher
  user id=3
    name
      Roger Roe
    occupation
      driver
  user id=4
    name
      Lucia Smith
    occupation
      shopkeeper

C# XmlDocument attributes, comments and CDATA

Elements have methods for reading and changing attributes. The document can also create comments and CDATA sections, which hold text that is not escaped.

Program.cs
using System.Xml;

var doc = new XmlDocument();
doc.Load("users.xml");

// attributes
XmlElement user = (XmlElement)doc.SelectSingleNode("/users/user[@id='1']")!;
Console.WriteLine(user.GetAttribute("id"));
user.SetAttribute("active", "true");
Console.WriteLine(user.HasAttribute("active"));
user.RemoveAttribute("id");

// comments and CDATA
user.AppendChild(doc.CreateComment("added by the program"));
XmlElement note = doc.CreateElement("note");
note.AppendChild(doc.CreateCDataSection("a < b && c > d"));
user.AppendChild(note);

// settings for readable output
var ws = new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true };
using var sw = new StringWriter();
using (var xw = XmlWriter.Create(sw, ws))
{
    user.WriteTo(xw);
}

Console.WriteLine(sw);

We modify the first user and print the resulting markup.

user.SetAttribute("active", "true");
user.RemoveAttribute("id");

GetAttribute, SetAttribute, HasAttribute, and RemoveAttribute work with attributes by name.

var ws = new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true };
using var xw = XmlWriter.Create(sw, ws);
user.WriteTo(xw);

The WriteTo method writes any node to an XmlWriter. With XmlWriterSettings, we control indentation and the declaration.

$ dotnet run
1
True
<user active="true">
  <name>John Doe</name>
  <occupation>gardener</occupation>
  <!--added by the program-->
  <note><![CDATA[a < b && c > d]]></note>
</user>

C# XmlDocument read currency rates

In the following example, we read the daily reference rates published by the European Central Bank. The document uses XML namespaces, so the XPath expressions need a namespace manager.

Program.cs
using System.Xml;

var doc = new XmlDocument();

using (var reader = XmlReader.Create("https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml"))
{
    doc.Load(reader);
}

var nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("gesmes", "http://www.gesmes.org/xml/2002-08-01");
nsmgr.AddNamespace("e", "http://www.ecb.int/vocabulary/2002-08-01/eurofxref");

XmlNodeList cubes = doc.SelectNodes("//e:Cube[@currency]", nsmgr)!;

Console.WriteLine($"{cubes.Count} currencies");

foreach (XmlNode node in cubes.Cast<XmlNode>().Take(5))
{
    Console.WriteLine($"{node.Attributes!["currency"]!.Value}: {node.Attributes["rate"]!.Value}");
}

We get the currency rates against the Euro.

var nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("e", "http://www.ecb.int/vocabulary/2002-08-01/eurofxref");

In XPath 1.0, an element in a default namespace cannot be selected without a prefix. We register our own prefix e for the namespace URI; the prefix does not have to match the one in the document.

XmlNodeList cubes = doc.SelectNodes("//e:Cube[@currency]", nsmgr)!;

The expression selects all Cube elements that have a currency attribute. This is more robust than navigating through ChildNodes[2].ChildNodes[0], which breaks as soon as the layout changes.

$ dotnet run
29 currencies
USD: 1.1253
JPY: 177.82
CZK: 24.323
DKK: 7.4748
GBP: 0.84385

The rates change every working day.

C# XmlDocument validate

An XML document with correct syntax is said to be well formed. Validation is the process of checking that a document is both well formed and valid. A valid document adheres to the rules dictated by a particular DTD or XML schema.

XML Schema, or XML Schema Definition (XSD), is a formal description of the structure and the content of an XML document. It is used to validate XML documents.

users.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="users" type="usersType"/>

    <xs:complexType name="userType">
        <xs:sequence>
            <xs:element name="name" type="xs:string"/>
            <xs:element name="occupation" type="xs:string"/>
        </xs:sequence>
        <xs:attribute type="xs:string" name="id"/>
    </xs:complexType>

    <xs:complexType name="usersType">
        <xs:sequence>
            <xs:element name="user" type="userType" maxOccurs="unbounded" minOccurs="0"/>
        </xs:sequence>
    </xs:complexType>
</xs:schema>

We have the schema definition for the users.xml file. Each user must have a name and an occupation, in this order.

Program.cs
using System.Xml;
using System.Xml.Schema;

Validate("users.xml");

File.WriteAllText("bad.xml", """
    <users>
      <user id="1"><name>John</name></user>
      <person/>
    </users>
    """);

Validate("bad.xml");

static void Validate(string file)
{
    var settings = new XmlReaderSettings { ValidationType = ValidationType.Schema };
    settings.Schemas.Add(null, "users.xsd");

    int errors = 0;
    settings.ValidationEventHandler += (sender, e) =>
    {
        errors++;
        Console.WriteLine($"  {e.Severity}: {e.Message}");
    };

    using var reader = XmlReader.Create(file, settings);
    var doc = new XmlDocument();
    doc.Load(reader);

    Console.WriteLine($"{file}: {(errors == 0 ? "valid" : $"{errors} error(s)")}");
}

We validate the users.xml document and an invalid document against the users.xsd schema.

var settings = new XmlReaderSettings { ValidationType = ValidationType.Schema };
settings.Schemas.Add(null, "users.xsd");

Validation is configured through the settings of the reader that feeds the document. We add the schema, and the validation type tells the reader to check the content as it reads.

settings.ValidationEventHandler += (sender, e) => { ... };

Validation problems are reported to the event handler, one by one, instead of stopping at the first. A handler gives us all the errors in one pass.

$ dotnet run
users.xml: valid
  Error: The element 'user' has incomplete content. List of possible elements expected: 'occupation'.
  Error: The element 'users' has invalid child element 'person'. List of possible elements expected: 'user'.
bad.xml: 2 error(s)

C# XmlDocument event

There are events that fire when nodes are changed, inserted, or removed. Each has a "-ing" counterpart (NodeChanging, and so on) that fires before the change.

Program.cs
using System.Xml;

var doc = new XmlDocument();
doc.Load("words.xml");

XmlElement root = doc.DocumentElement!;

doc.NodeChanged += (s, e) => Console.WriteLine($"changed:  {e.Node?.Name} '{e.NewValue}' (was '{e.OldValue}')");
doc.NodeInserted += (s, e) => Console.WriteLine($"inserted: {e.Node?.Name} '{e.Node?.Value}'");
doc.NodeRemoved += (s, e) => Console.WriteLine($"removed:  {e.Node?.Name}");

root.RemoveChild(root.LastChild!);

XmlElement e1 = doc.CreateElement("word");
e1.InnerText = "eagle";
root.AppendChild(e1);

XmlNode? n1 = doc.SelectSingleNode("//word[text()='bottom']");
if (n1 is not null)
{
    n1.InnerText = "star";
}

In the example, we plug in three event handlers and then modify the document.

doc.NodeChanged += (s, e) => ...
doc.NodeInserted += (s, e) => ...
doc.NodeRemoved += (s, e) => ...

We attach lambda handlers for nodes being changed, inserted, and removed.

root.RemoveChild(root.LastChild!);

We remove the last child. This triggers the NodeRemoved handler.

XmlElement e1 = doc.CreateElement("word");
e1.InnerText = "eagle";
root.AppendChild(e1);

We create a new element. Setting the text inserts a text node into the element, which raises the first insertion event (the element is not yet in the document at that time). Appending the element raises the second one.

n1.InnerText = "star";

We modify the text of a node. This triggers the NodeChanged handler.

$ dotnet run
removed:  word
inserted: #text 'eagle'
inserted: word ''
changed:  #text 'star' (was 'bottom')

C# XmlDocument and security

XML parsers can be abused with specially crafted documents that contain a DTD, for example entity expansion bombs. In current .NET versions, external entities are not resolved by default, but inline DTDs are processed. When we load XML from an untrusted source, we should prohibit DTDs.

Program.cs
using System.Xml;

string xml = """
    <?xml version="1.0"?>
    <!DOCTYPE d [<!ENTITY x "expanded">]>
    <d>&x;</d>
    """;

// LoadXml accepts the inline DTD and expands the entity
var doc = new XmlDocument();
doc.LoadXml(xml);
Console.WriteLine(doc.DocumentElement!.InnerText);

// for untrusted input, load through a reader that prohibits DTDs
var settings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Prohibit };

try
{
    using var reader = XmlReader.Create(new StringReader(xml), settings);
    new XmlDocument().Load(reader);
}
catch (XmlException e)
{
    Console.WriteLine($"rejected: {e.Message}");
}

The first load expands the entity &x; defined in the inline DTD. The second goes through an XmlReader with DtdProcessing.Prohibit and is rejected.

$ dotnet run
expanded
rejected: For security reasons DTD is prohibited in this XML document. To enable DTD processing set the DtdProcessing property on XmlReaderSettings to Parse and pass the settings into XmlReader.Create method.

Source

XmlDocument class

In this article we have worked with XML data in C# using XmlDocument.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all C# tutorials.