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.
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.
<?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.
<?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.
<?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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
<?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.
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.
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.
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
In this article we have worked with XML data in C# using XmlDocument.
Author
List all C# tutorials.