.NET - 是否可以在同一个对象中同时使用 XmlAnyElementAttribute 和 XmlSerializer.UnknownElement 事件 [英] .NET - Is it possible to use both XmlAnyElementAttribute and XmlSerializer.UnknownElement event within the same object

查看:33
本文介绍了.NET - 是否可以在同一个对象中同时使用 XmlAnyElementAttribute 和 XmlSerializer.UnknownElement 事件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个类,我必须在其中将属性的类型从简单的 List 更改为复杂的 List.

I have a class in which I had to change te type of a property from a simple List<string> to a complex List<CustomObject>.

我的问题是,在一段时间内,我会有人使用新旧版本的软件.到目前为止,当我更改合同时,我只是使用 UnknownElement 事件将旧成员映射到新成员,因为它是 私有 文件并且它非常适合向后兼容性但破坏了旧版本,因为它没有写回旧格式.

My problem is that for some period of time, I will have people using the old and the new version of the software. Up until now, when I had contract changes, I simply used the UnknownElement event to map the old member to the new one since it was private files and it works perfectly for backward compatibility but broke the old version since it didn't write the old format back.

但这一次,它是一个共享文件,这让我意识到我错过了向上兼容性,使用旧版本的人将删除新成员.我阅读了 XmlAnyElementAttribute 以保留未知元素并将它们序列化回文件.这修复了向上兼容性.

But this time, it is a shared file and it made me realise that I missed upward compatibility and that people using the old version will remove the new member. I read about XmlAnyElementAttribute to keep unknown elements and have them serialized back to the file. This fixes upward compatibility.

我现在有了拼图的每一部分,但我找不到如何让它们一起工作,因为添加 XmlAnyElementAttribute 似乎以 UnknownElement 没有被触发结束.

I now have every pieces of the puzzle but I can't find how to have them work together since adding XmlAnyElementAttribute seems to end in UnknownElement not being triggered.

我还想过在反序列化完成后简单地回读 XmlAnyElementAttribute 属性,但这一次,XmlSerializer` 缺少反序列化事件.

I also thought of simply reading back the XmlAnyElementAttributeproperty once the deserialization is done but this time, it is theXmlSerializer` that lacks an event for Deserialized.

以下是两个文件的示例:旧格式:

Here is a sample of both files: Old format:

<OptionsSerializable xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <ListeCategories>
    <string>SX00</string>
    <string>SX01</string>
  </ListeCategories>
</OptionsSerializable>

新格式:

<OptionsSerializable xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <ListeCategoriesExt>
    <CategoryInfo Name="SX00" Type="Principal" Persistence="Global">
      <ToolTip>SX00</ToolTip>
      <SearchTerm>SX00</SearchTerm>
    </CategoryInfo>
    <CategoryInfo Name="SX01" Type="Principal" Persistence="Global">
      <ToolTip>SX01</ToolTip>
      <SearchTerm>SX01</SearchTerm>
    </CategoryInfo>
  </ListeCategoriesExt>
</OptionsSerializable>

需要:

<OptionsSerializable xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <ListeCategories>
    <string>SX00</string>
    <string>SX01</string>
  </ListeCategories>
  <ListeCategoriesExt>
    <CategoryInfo Name="SX00" Type="Principal" Persistence="Global">
      <ToolTip>SX00</ToolTip>
      <SearchTerm>SX00</SearchTerm>
    </CategoryInfo>
    <CategoryInfo Name="SX01" Type="Principal" Persistence="Global">
      <ToolTip>SX01</ToolTip>
      <SearchTerm>SX01</SearchTerm>
    </CategoryInfo>
  </ListeCategoriesExt>
</OptionsSerializable>

推荐答案

根据 文档:

XmlSerializer.UnknownElement ... 当 XmlSerializer 在反序列化过程中遇到未知类型的 XML 元素时发生.

XmlSerializer.UnknownElement ... Occurs when the XmlSerializer encounters an XML element of unknown type during deserialization.

如果您的 元素绑定到 [XmlAnyElement] 属性,则它们不是未知类型,因此不会引发任何事件.

If your <ListeCategories> elements are getting bound to an [XmlAnyElement] property, then they aren't of unknown type, and so no event is raised.

现在,如果除了 (未显示在您的问题中)之外,您还有一些 other 未知元素要使用 UnknownElement 进行后处理,您可以通过限制使用 [XmlAnyElementAttribute(string name)]:

Now, if you have some other unknown elements besides <ListeCategories> (not shown in your question) that you want to post-process using UnknownElement, you can do that by restricting the names of elements bound by using [XmlAnyElementAttribute(string name)]:

初始化 XmlAnyElementAttribute 类的新实例并指定在 XML 文档中生成的 XML 元素名称.

Initializes a new instance of the XmlAnyElementAttribute class and specifies the XML element name generated in the XML document.

即:

public class OptionsSerializable 
{
    [XmlAnyElement("ListeCategories")]
    public XmlElement [] ListeCategories { get; set; }

现在其他未知元素,例如,仍会引发事件.演示小提琴 #1 此处.但是您仍然不会收到 的事件回调.

Now other unknown elements, e.g. <SomeOtherObsoleteNodeToPostprocess />, will still raise the event. Demo fiddle #1 here. But you still won't get an event callback for <ListeCategories>.

那么,你有什么选择?

首先,您可以在 XmlElement [] 数组的 setter 中进行后处理,如所示这个答案更好的 IXmlSerializable 格式?:

Firstly, you could do your postprocessing in the setter for the XmlElement [] array, as shown in this answer to Better IXmlSerializable format?:

[XmlRoot(ElementName="OptionsSerializable")]
public class OptionsSerializable 
{
    [XmlAnyElement("ListeCategories")]
    public XmlElement [] ListeCategories
    {
        get
        {
            // Convert the ListeCategoriesExt items property to an array of XmlElement
        }
        set
        {
            // Convert array of XmlElement back to ListeCategoriesExt items.
        }
    }

原始的 UnknownElement 事件逻辑也可以通过使用这个来部分保留:

The original UnknownElement event logic could also be partly preserved by using this:

XmlElement[] _unsupported;
[XmlAnyElement()]
 public XmlElement[] Unsupported {
     get {
         return _unsupported;
     }
     set {
         _unsupported = value;
         if ((value.Count > 0)) {
             foreach (element in value) {
                 OnUnknownElementFound(this, new XmlElementEventArgs(){Element=element});
             }
         }
     }
 }

但是,如果后处理要由 OptionsSerializable 对象本身完成,则将 ListeCategories 视为 的已弃用的过滤视图更有意义>ListeCategoriesExt 属性.这是我的做法:

However, if the postprocessing is to be done by the OptionsSerializable object itself, it makes more sense to think of ListeCategories as a deprecated, filtered view of the ListeCategoriesExt property. Here's how I would do it:

[XmlRoot(ElementName="OptionsSerializable")]
public class OptionsSerializable 
{
    [XmlArray("ListeCategories"), XmlArrayItem("string")]
    public string [] XmlListeCategories
    {
        //Can't use [Obsolete] because doing so will cause XmlSerializer to not serialize the property, see https://stackoverflow.com/a/331038
        get
        {
            // Since it seems <CategoryInfo Name="VerifierCoherence" Type="Principal" Persistence="Global"> should not be written back,
            // you will need to add a .Where clause excluding those CategoryInfo items you don't want to appear in the old list of strings.
            return ListeCategoriesExt?.Select(c => c.Name)?.ToArray();
        }
        set
        {
            // Merge in the deserialization results.  Note this algorithm assumes that there are no duplicate names.
            // Convert array of XmlElement back to ListeCategoriesExt items.
            foreach (var name in value)
            {
                if (ListeCategoriesExt.FindIndex(c => c.Name == name) < 0)
                {
                    ListeCategoriesExt.Add(new CategoryInfo
                                           {
                                               Name = name, Type = "Principal", Persistence = "Global",
                                               ToolTip = name,
                                               SearchTerm = name,
                                           });
                }
            }
        }
    }

    [XmlArray("ListeCategoriesExt"), XmlArrayItem("CategoryInfo")]
    public CategoryInfo [] XmlListeCategoriesExt
    {
        get
        {
            return ListeCategoriesExt?.ToArray();
        }
        set
        {
            // Merge in the deserialization results.  Note this algorithm assumes that there are no duplicate names.
            foreach (var category in value)
            {
                var index = ListeCategoriesExt.FindIndex(c => c.Name == category.Name);
                if (index < 0)
                {
                    ListeCategoriesExt.Add(category);
                }
                else
                {
                    // Overwrite the item added during XmlListeCategories deserialization.
                    ListeCategoriesExt[index] = category;
                }
            }
        }
    }

    [XmlIgnore]
    public List<CategoryInfo> ListeCategoriesExt { get; set; } = new List<CategoryInfo>();
}

[XmlRoot(ElementName="CategoryInfo")]
public class CategoryInfo 
{
    [XmlElement(ElementName="ToolTip")]
    public string ToolTip { get; set; }
    [XmlElement(ElementName="SearchTerm")]
    public string SearchTerm { get; set; }
    [XmlAttribute(AttributeName="Name")]
    public string Name { get; set; }
    [XmlAttribute(AttributeName="Type")]
    public string Type { get; set; }
    [XmlAttribute(AttributeName="Persistence")]
    public string Persistence { get; set; }
}

注意事项:

  • 出现在 before 在您的 XML 中时,有必要合并新项目XmlListeCategoriesExt 的 setter 中先前反序列化的过时项.

  • As <ListeCategories> appears before <ListeCategoriesExt> in your XML, it is necessary to merge the new items into the previously-deserialized obsolete items in the setter for XmlListeCategoriesExt.

如果您要设置 XmlArrayAttribute.Order 要求 最后.

由于合并的必要性,反序列化算法不支持多个具有相同名称的CategoryInfo 对象.

Because of the necessity for the merge, the deserialization algorithm does not support multiple CategoryInfo objects with identical names.

如果您的 CategoryInfo 列表中必须有相同的名称,则新旧表示的合并会变得更加复杂.

If you must have identical names in your CategoryInfo list, merging of the old and new representations becomes more complex.

很遗憾,无法在 OnDeserialized 事件中合并新旧类别列表,因为令人讨厌的是,XmlSerializer 不支持 [OnDeserialized].

Unfortunately it is not possible to merge the old and new category lists in an OnDeserialized event because, annoyingly, XmlSerializer does not support [OnDeserialized].

演示小提琴 #2 此处.

这篇关于.NET - 是否可以在同一个对象中同时使用 XmlAnyElementAttribute 和 XmlSerializer.UnknownElement 事件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆