Search code examples
c#.netxmlxmldocumentcircular-reference

Xml circular reference


I am trying to produce the following XML using the class structure below.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<VirtualDesktopManager>
  <Categories>
    <Category Name="Category 1">
      <Desktops>
        <Desktop Name="Desktop 1">
          <Applications Name="Application 1" />
          <Applications Name="Application 2" />
        </Desktop>
      </Desktops>
    </Category>
  </Categories>
</VirtualDesktopManager>

When executing the code below, I get the exception: System.ArgumentException: 'Cannot insert a node or any ancestor of that node as a child of itself.'. The classes themselves do not have any circular references so I must be doing something wrong.

private static void Main ()
{
    var database = new Database();
    var category = new VirtualDesktopCategory();
    var desktop = new VirtualDesktop();
    var application = new VirtualDesktopApplication();

    category = new VirtualDesktopCategory() { Name = "Cat 1", };
    database.Categories.Add(category);
    desktop = new VirtualDesktop() { Name = "Desktop 1", };
    category.Desktops.Add(desktop);
    application = new VirtualDesktopApplication() { Name = "Application 1", };
    desktop.Applications.Add(application);
    application = new VirtualDesktopApplication() { Name = "Application 2", };
    desktop.Applications.Add(application);

    database.ToXmlDocument().InnerText.Dump();
}

public class Database
{
    public string Name { get; set; } = "";
    public List<VirtualDesktopCategory> Categories { get; private set; } = new();

    public XmlDocument ToXmlDocument()
    {
        var document = new XmlDocument();
        var xml = $@"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes"" ?><VirtualDesktopManager></VirtualDesktopManager>";

        document.LoadXml(xml);
        document.DocumentElement?.AppendChild(this.ToXmlElement(document, document.DocumentElement));

        return document;
    }

    public XmlElement ToXmlElement(XmlDocument document, XmlElement elementParent)
    {
        var elementCategories = document.CreateElement("Categories");

        elementParent.AppendChild(elementCategories);

        foreach (var category in this.Categories)
        {
            // System.ArgumentException: Cannot insert a node or any ancestor of that node as a child of itself.
            elementCategories.AppendChild(category.ToXmlElement(document, elementCategories));
        }

        return elementCategories;
    }
}

public class VirtualDesktopCategory
{
    public string Name { get; set; } = "";
    public List<VirtualDesktop> Desktops { get; private set; } = new();

    public XmlElement ToXmlElement(XmlDocument document, XmlElement elementParent)
    {
        var elementCategory = document.CreateElement("Category");
        var elementDesktops = document.CreateElement("Desktops");

        elementCategory.AppendAttribute(document, nameof(this.Name), this.Name);

        elementParent.AppendChild(elementCategory);
        elementCategory.AppendChild(elementDesktops);

        foreach (var desktop in this.Desktops)
        {
            elementDesktops.AppendChild(desktop.ToXmlElement(document, elementDesktops));
        }

        return elementCategory;
    }
}

public class VirtualDesktop
{
    public string Name { get; set; } = "";
    public List<VirtualDesktopApplication> Applications { get; private set; } = new();

    public XmlElement ToXmlElement(XmlDocument document, XmlElement elementParent)
    {
        var elementDesktop = document.CreateElement("Desktop");
        var elementApplications = document.CreateElement("Applications");

        elementDesktop.AppendAttribute(document, nameof(this.Name), this.Name);

        elementParent.AppendChild(elementDesktop);
        elementDesktop.AppendChild(elementApplications);

        foreach (var application in this.Applications)
        {
            elementApplications.AppendChild(application.ToXmlElement(document, elementApplications));
        }

        return elementParent;
    }
}

public class VirtualDesktopApplication
{
    public string Name { get; set; } = "";

    public XmlElement ToXmlElement(XmlDocument document, XmlElement elementParent)
    {
        var elementApplication = document.CreateElement("Application");

        elementApplication.AppendAttribute(document, nameof(this.Name), this.Name);

        elementParent.AppendChild(elementApplication);

        return elementApplication;
    }
}

public static class Extensions
{
    public static XmlAttribute AppendAttribute(this XmlElement element, XmlDocument document, string name, string value)
    {
        var attribute = document.CreateAttribute(name);

        attribute.Value = value;
        element.Attributes.Append(attribute);

        return attribute;
    }
}

Any pointers would be appreciated.


Solution

  • public class VirtualDesktop
    {
        public string Name { get; set; } = "";
        public List<VirtualDesktopApplication> Applications { get; private set; } = new();
    
        public XmlElement ToXmlElement(XmlDocument document, XmlElement elementParent)
        {
            var elementDesktop = document.CreateElement("Desktop");
            var elementApplications = document.CreateElement("Applications");
    
            elementDesktop.AppendAttribute(document, nameof(this.Name), this.Name);
    
            elementParent.AppendChild(elementDesktop);
            elementDesktop.AppendChild(elementApplications);
    
            foreach (var application in this.Applications)
            {
                elementApplications.AppendChild(application.ToXmlElement(document, elementApplications));
            }
    
            return elementParent;
        }
    }
    

    You're returning elementParent, rather than elementDesktop.

    You had mis-identified the line throwing the exception. It was actually:

    elementDesktops.AppendChild(desktop.ToXmlElement(document, elementDesktops));
    

    So, something in desktop.ToXmlElement was the cause. A quick bit of trial-and-error shows that commenting out everything in VirtualDesktop.ToXmlElement doesn't fix it, so it's somewhere between the var elementDesktop = document.CreateElement("Desktop") and return elementParent;... Wait...


    You're also serializing the XmlDocument incorrectly. You need to do something like:

    using var sr = new StringWriter();
    using (var writer = XmlWriter.Create(sr))
    {
        database.ToXmlDocument().Save(writer);
    }
    Console.WriteLine(sr.ToString());
    

    However, this will give you:

    <?xml version="1.0" encoding="utf-16" standalone="yes"?>
    

    To fix the encoding, you'll need to do something like this: https://stackoverflow.com/a/1564727/1086121


    Working example, with the duplicate lines identified by @EyesShriveledToRaisins commented out.