使用父对象引用进行XML反序列化

时间:2015-07-15 07:49:37

标签: c# xml

我有一个描述网站的XML文件。它由Site作为根节点组成,可以有Pages,Pages可以有对象,如Button或TextBox和Dialogs。对话框也可以有对象。

在相应的C#类中,所有类都派生自Element。当我反序列化XML时,如何引用构造元素的Parent?

我被告知复杂类型不能以这种方式反序列化,但如果我在XML中为每个元素添加一个ID字段,那么可以使用它来引用父类,因此可以正确地反序列化。我该如何实现?我不想手动将ID字段添加到XML文件中的每个元素......

我的元素类:

public class Element 
{
    public string Name { get; set; }
    public string TagName { get; set; }
    public string XPath { get; set; }

    [XmlElement(ElementName = "Site", Type = typeof(Site))]
    [XmlElement(ElementName = "Page", Type = typeof(Page))]
    [XmlElement(ElementName = "Dialog", Type = typeof(Dialog))]
    public Element Parent { get; set; }

    [XmlArray("Children", IsNullable = false)]
    [XmlArrayItem(Type = typeof(TextBox))]
    [XmlArrayItem(Type = typeof(Page))]
    [XmlArrayItem(Type = typeof(Button))]
    [XmlArrayItem(Type = typeof(Dialog))]
    public Collection<Element> Children { get; set; }

}

我的反序列化:

public Site GetSiteFromXml(string filePath, string fileName)
{
    XmlSerializer serializer = new XmlSerializer(typeof(Site));
    return serializer.Deserialize(new XmlTextReader(Path.Combine(filePath, fileName))) as Site;
}

我的XML文件:

<Site>
  <Name>WebSiteName</Name>
  <Url>https://site.url</Url>
  <Children>
    <Page>
      <Name>HomePage</Name>
      <Address>#page=1</Address>
      <Children>
        <Button>
          <Name>LoginDialogButton</Name>
          <Id>LoginIcon</Id>
          <XPath>//*[@id="LoginIcon"]</XPath>
          <Enabled>true</Enabled>
          <Action>OpenLoginDialog</Action>
        </Button>
        <Dialog>
          <Name>LoginPopUpDialog</Name>
          <Id>loginModal</Id>
          <Children>
            <TextBox>
              <Name>UserNameInput</Name>
            </TextBox>
            <TextBox>
              <Name>PasswordInput</Name>
            </TextBox>
            <Button>
              <Name>LoginButton</Name>
              <Action>DialogDismiss</Action>
            </Button>
          </Children>
        </Dialog>
      </Children>
    </Page>
  </Children>
</Site>

1 个答案:

答案 0 :(得分:0)

由于您没有任何机制来保证ParentChildren属性保持同步,因此最简单的方法是告诉XmlSerializer忽略这两种属性属性,并添加一个代理属性,该属性将子列表作为数组(不是集合)返回。在代理属性的 setter 中,填充Children集合并设置每个子项的Parent属性:

public class Element
{
    public Element() { Children = new Collection<Element>(); }

    public string Name { get; set; }
    public string TagName { get; set; }
    public string XPath { get; set; }

    [XmlIgnore]
    public Element Parent { get; set; }

    [XmlIgnore]
    public Collection<Element> Children { get; set; }

    [XmlArray("Children", IsNullable = false)]
    [XmlArrayItem(Type = typeof(TextBox))]
    [XmlArrayItem(Type = typeof(Page))]
    [XmlArrayItem(Type = typeof(Button))]
    [XmlArrayItem(Type = typeof(Dialog))]
    [XmlArrayItem(Type = typeof(Site))]
    public Element[] ChildArrayCopy
    {
        get
        {
            return Children.ToArray();
        }
        set
        {
            Children.Clear();
            if (value != null)
                foreach (var child in value)
                    child.SetParent(this);
        }
    }
}

public static class ElementExtensions
{
    public static void SetParent(this Element child, Element parent)
    {
        if (child == null)
            throw new ArgumentNullException();
        if (child.Parent == parent)
            return; // Nothing to do.
        if (child.Parent != null)
            child.Parent.Children.Remove(child);
        child.Parent = parent;
        if (parent != null)
            parent.Children.Add(child);
    }
}

您的XML现在可以成功反序列化和序列化。

顺便提一下,我会考虑将public Collection<Element> Children { get; set; }替换为public ReadOnlyCollection<Element> Children { get; }。然后将孩子保留在私人列表中,并返回list.AsReadOnly()。完成后,您现在可以保证子列表在Parent setter中保持最新状态:

public class Element
{

    public Element() { }

    public string Name { get; set; }
    public string TagName { get; set; }
    public string XPath { get; set; }

    private readonly List<Element> children = new List<Element>();
    private Element parent = null;

    [XmlIgnore]
    public Element Parent
    {
        get
        {
            return parent;
        }
        set
        {
            if (parent == value)
                return;
            if (parent != null)
                parent.children.Remove(this);
            parent = value;
            if (parent != null)
                parent.children.Add(this);
        }
    }

    [XmlIgnore]
    public ReadOnlyCollection<Element> Children
    {
        get
        {
            return children.AsReadOnly();
        }
    }

    [XmlArray("Children", IsNullable = false)]
    [XmlArrayItem(Type = typeof(TextBox))]
    [XmlArrayItem(Type = typeof(Page))]
    [XmlArrayItem(Type = typeof(Button))]
    [XmlArrayItem(Type = typeof(Dialog))]
    [XmlArrayItem(Type = typeof(Site))]
    public Element[] ChildArrayCopy
    {
        get
        {
            return Children.ToArray();
        }
        set
        {
            if (value != null)
                foreach (var child in value)
                    child.Parent = this;
        }
    }
}