1. Introduction
You already know that .NET can serialize and deserialize objects to XML by default based on public fields and properties. But when you want a pretty XML that's compatible with other programs/standards, or when you don't want to include some internal details in the XML, you need fine-grained control.
In C# there are serialization attributes — special "tags" you put on classes, properties or fields. With these attributes you can control basically everything: which fields end up in the XML, which are ignored, what an element is called, whether it becomes an attribute, whether it has a complex structure, and even the order of elements!
Think of default serialization like auto-filling a form: first-middle-last name in their boxes, everything else tossed in. With attributes you act like a super-clerk: you decide which line goes where, what to skip, what to highlight in red, and what to hide in a secret section.
Mapping objects to XML
| Class/Property | Attribute | Result in XML |
|---|---|---|
|
Class UserList | |
|
|
|
|
|
id="..." (attribute) |
|
|
|
|
|
|
|
[XmlIgnore] (see RegisteredAtString) | - |
|
|
|
|
|
|
2. Overview of main XML serialization attributes
Let's walk through the most popular attributes .NET users have been decorating their classes with for many generations!
Attribute [XmlElement] — Element name
Changes the element name in XML or marks a property/field as an XML element.
public class Person
{
[XmlElement("FullName")]
public string Name { get; set; }
}
XML:
<Person>
<FullName>Vasiliy Petrov</FullName>
</Person>
If you don't specify the attribute — by default the element name will match the property/field name.
Attribute [XmlAttribute] — Serialize as XML attribute
Allows serializing a property/field as an XML attribute instead of an element.
public class Person
{
[XmlAttribute("id")]
public int Id { get; set; }
}
XML:
<Person id="123"></Person>
In real XML APIs attributes are often used for identifiers, dates, flags, etc.
Attribute [XmlIgnore] — Skip field/property
The dream of every paranoid: completely exclude a property from serialization and deserialization! Anything marked with this attribute simply won't get into the final XML.
public class Person
{
[XmlIgnore]
public string InternalNote { get; set; }
}
XML: (the InternalNote property is not present and won't be)
Attributes [XmlArray] and [XmlArrayItem] — controlling collections
Special attributes for arrays and collections. [XmlArray] sets the outer wrapper name, and [XmlArrayItem] — the individual item name.
public class Person
{
[XmlArray("Phones")]
[XmlArrayItem("Phone")]
public string[] PhoneNumbers { get; set; }
}
XML:
<Person>
<Phones>
<Phone>+12951234567</Phone>
<Phone>+12876543210</Phone>
</Phones>
</Person>
Attribute [XmlRoot] — Root element name
Marks the class itself to change the name of the root element in XML.
[XmlRoot("User")]
public class Person
{
public string Name { get; set; }
}
XML:
<User>
<Name>Anna</Name>
</User>
Attribute [XmlText] — Serialize as element text content
Sometimes you want (or a spec requires) a property's value to be just the text inside an element, not a nested child element or attribute.
public class Note
{
[XmlText]
public string Content { get; set; }
}
XML:
<Note>Call grandma!</Note>
Attributes [XmlNamespaceDeclarations], [XmlElement(Type = ...)]
For more advanced scenarios (for example, serialization with namespaces, support for inheritance, etc.) there are more specific attributes. Be sure to check the official documentation on XML serialization for the full list.
3. Practice: improving serialization with a real example
Let's step-by-step upgrade our demo app to "premium XML serialization" so it will please even the strictest XML inspector.
Base class without attributes
Suppose we have this user class:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string[] Emails { get; set; }
public DateTime RegisteredAt { get; set; }
public string Password { get; set; }
}
Default serialization will produce something like this XML (and date will be written in its own style):
<User>
<Id>42</Id>
<Name>Ivan Ivanov</Name>
<Emails>
<string>user@mail.com</string>
<string>admin@site.org</string>
</Emails>
<RegisteredAt>2024-07-01T00:00:00</RegisteredAt>
<Password>qwerty</Password>
</User>
What's wrong:
- Password should probably not be serialized at all.
- We want the date as an attribute and in a human-friendly format.
- We want Emails in a nicer form.
- We want the root element to be <Person>.
Adding settings via attributes
[XmlRoot("Person")]
public class User
{
[XmlAttribute("id")]
public int Id { get; set; }
[XmlElement("FullName")]
public string Name { get; set; }
[XmlArray("EMails")]
[XmlArrayItem("Email")]
public string[] Emails { get; set; }
[XmlAttribute("registered")]
public string RegisteredAtString
{
get => RegisteredAt.ToString("yyyy-MM-dd");
set => RegisteredAt = DateTime.Parse(value);
}
[XmlIgnore]
public string Password { get; set; }
[XmlIgnore]
public DateTime RegisteredAt { get; set; }
}
Explanation of the details:
- We use [XmlAttribute("registered")] to serialize the date as an attribute.
- To serialize not the DateTime itself but its string representation, we add a separate property RegisteredAtString, and hide the real field with [XmlIgnore].
- We hide the password using [XmlIgnore].
- The email array is configured so the outer tag is <EMails> and each email is <Email>.
Result:
<Person id="42" registered="2024-07-01">
<FullName>Ivan Ivanov</FullName>
<EMails>
<Email>user@mail.com</Email>
<Email>admin@site.org</Email>
</EMails>
</Person>
4. Working with nested objects
XML shines when there are nested objects (for example, a user and their addresses).
public class Address
{
[XmlAttribute]
public string City { get; set; }
[XmlText]
public string Details { get; set; }
}
public class User
{
// ...other properties, see above...
public Address[] Addresses { get; set; }
}
And add settings for the addresses array:
[XmlArray("UserAddresses")]
[XmlArrayItem("Address")]
public Address[] Addresses { get; set; }
Then the XML will look like this:
<Person id="42" registered="2024-07-01">
<FullName>Ivan Ivanov</FullName>
<EMails>
<Email>user@mail.com</Email>
</EMails>
<UserAddresses>
<Address City="Neon city">ul. Zamkovaya, d.1</Address>
<Address City="North Cave">ul. Kraynyaya d.12</Address>
</UserAddresses>
</Person>
You can see that for each <Address> the city is an attribute, and the address itself is provided as text inside the element.
5. Common mistakes and nuances
If you serialize a complex object and don't see the XML changes you expected — most likely you forgot to apply the attribute correctly. Sometimes properties/fields remain unmarked. In that case .NET just picks the default behavior.
Another trap is the collection name. If a collection (for example, an array of strings) is not marked with either [XmlArray] or [XmlArrayItem], the elements will be written with the tag string (as seen in the initial example above). To avoid that, always explicitly set tag names via attributes.
Be careful with getter-only properties — such properties are not deserialized by default. Try to provide a public set.
Some types (for example, dictionaries or interfaces) are not serializable by the standard XmlSerializer, or require special handling.
GO TO FULL VERSION