【问题标题】:Force XML serialization of XmlDefaultValue values强制 XmlDefaultValue 值的 XML 序列化
【发布时间】:2015-03-19 05:05:16
【问题描述】:

使用从 XSD 文档生成的 C# 类,我可以创建一个对象,并将其成功序列化。但是,某些属性定义了 XmlDefaultValue。如果任何对象具有默认值,则在序列化对象时不会创建这些属性。

这是documentation 的预期行为。但这不是我希望它的行为方式。我需要在 XML 文档中生成所有此类属性。
我已经检查了可以应用的任何可能强制输出它的代码属性,即使它是默认值,但我找不到类似的东西。

有什么办法可以做到吗?

【问题讨论】:

标签: c# xmlserializer


【解决方案1】:

您可以在序列化时为一组特定类型执行此操作,方法是构造一个XmlAttributeOverrides,为应用了DefaultValueAttribute 的每个字段或属性指定new XmlAttributes() { XmlDefaultValue = null },然后将其传递给XmlSerializer(Type, XmlAttributeOverrides) 构造函数:

    var overrides = new XmlAttributeOverrides();
    var attrs = new XmlAttributes() { XmlDefaultValue = null };

    overrides.Add(typeToSerialize, propertyNameWithDefaultToIgnore, attrs);
    var serializer = new XmlSerializer(typeToSerialize, overrides);

但是请注意,这个important warning from the documentation

动态生成的程序集

为了提高性能,XML 序列化基础结构动态生成程序集以序列化和反序列化指定类型。基础结构查找并重用这些程序集。此行为仅在使用以下构造函数时发生:

XmlSerializer.XmlSerializer(类型)

XmlSerializer.XmlSerializer(类型,字符串)

如果您使用任何其他构造函数,则会生成同一程序集的多个版本并且永远不会卸载,这会导致内存泄漏和性能下降。最简单的解决方案是使用前面提到的两个构造函数之一。否则,您必须将程序集缓存在 Hashtable 中,如下例所示。

但是,代码中给出的示例并没有给出如何键入哈希表的任何建议。它也不是线程安全的。 (也许它可以追溯到 .Net 1.0?)

以下代码为具有覆盖的 xml 序列化程序创建一个密钥方案,并制造(通过反射)序列化程序,所有属性和字段的 [DefaultValue] 值(如果有)都被覆盖为空,有效地取消了默认值.请注意,在创建空白 XmlAttributes() 对象时,所有属性都设置为 null。使用此 XmlAttributes 对象覆盖时,任何希望保留的属性都需要转移到此新对象中:

public abstract class XmlSerializerKey
{
    static class XmlSerializerHashTable
    {
        static Dictionary<object, XmlSerializer> dict;

        static XmlSerializerHashTable()
        {
            dict = new Dictionary<object, XmlSerializer>();
        }

        public static XmlSerializer GetSerializer(XmlSerializerKey key)
        {
            lock (dict)
            {
                XmlSerializer value;
                if (!dict.TryGetValue(key, out value))
                    dict[key] = value = key.CreateSerializer();
                return value;
            }
        }
    }

    readonly Type serializedType;

    protected XmlSerializerKey(Type serializedType)
    {
        this.serializedType = serializedType;
    }

    public Type SerializedType { get { return serializedType; } }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(this, obj))
            return true;
        else if (ReferenceEquals(null, obj))
            return false;
        if (GetType() != obj.GetType())
            return false;
        XmlSerializerKey other = (XmlSerializerKey)obj;
        if (other.serializedType != serializedType)
            return false;
        return true;
    }

    public override int GetHashCode()
    {
        int code = 0;
        if (serializedType != null)
            code ^= serializedType.GetHashCode();
        return code;
    }

    public override string ToString()
    {
        return string.Format(base.ToString() + ": for type: " + serializedType.ToString());
    }

    public XmlSerializer GetSerializer()
    {
        return XmlSerializerHashTable.GetSerializer(this);
    }

    protected abstract XmlSerializer CreateSerializer();
}

public abstract class XmlserializerWithExtraTypesKey : XmlSerializerKey
{
    static IEqualityComparer<HashSet<Type>> comparer;

    readonly HashSet<Type> extraTypes = new HashSet<Type>();

    static XmlserializerWithExtraTypesKey()
    {
        comparer = HashSet<Type>.CreateSetComparer();
    }

    protected XmlserializerWithExtraTypesKey(Type serializedType, IEnumerable<Type> extraTypes)
        : base(serializedType)
    {
        if (extraTypes != null)
            foreach (var type in extraTypes)
                this.extraTypes.Add(type);
    }

    public Type[] ExtraTypes { get { return extraTypes.ToArray(); } }

    public override bool Equals(object obj)
    {
        if (!base.Equals(obj))
            return false;
        XmlserializerWithExtraTypesKey other = (XmlserializerWithExtraTypesKey)obj;
        return comparer.Equals(this.extraTypes, other.extraTypes);
    }

    public override int GetHashCode()
    {
        int code = base.GetHashCode();
        if (extraTypes != null)
            code ^= comparer.GetHashCode(extraTypes);
        return code;
    }
}

public sealed class XmlSerializerIgnoringDefaultValuesKey : XmlserializerWithExtraTypesKey
{
    readonly XmlAttributeOverrides overrides;

    private XmlSerializerIgnoringDefaultValuesKey(Type serializerType, IEnumerable<Type> ignoreDefaultTypes, XmlAttributeOverrides overrides)
        : base(serializerType, ignoreDefaultTypes)
    {
        this.overrides = overrides;
    }

    public static XmlSerializerIgnoringDefaultValuesKey Create(Type serializerType, IEnumerable<Type> ignoreDefaultTypes, bool recurse)
    {
        XmlAttributeOverrides overrides;
        Type [] typesWithOverrides;

        CreateOverrideAttributes(ignoreDefaultTypes, recurse, out overrides, out typesWithOverrides);
        return new XmlSerializerIgnoringDefaultValuesKey(serializerType, typesWithOverrides, overrides);
    }

    protected override XmlSerializer CreateSerializer()
    {
        var types = ExtraTypes;
        if (types == null || types.Length < 1)
            return new XmlSerializer(SerializedType);
        return new XmlSerializer(SerializedType, overrides);
    }

    static void CreateOverrideAttributes(IEnumerable<Type> types, bool recurse, out XmlAttributeOverrides overrides, out Type[] typesWithOverrides)
    {
        HashSet<Type> visited = new HashSet<Type>();
        HashSet<Type> withOverrides = new HashSet<Type>();
        overrides = new XmlAttributeOverrides();

        foreach (var type in types)
        {
            CreateOverrideAttributes(type, recurse, overrides, visited, withOverrides);
        }

        typesWithOverrides = withOverrides.ToArray();
    }

    static void CreateOverrideAttributes(Type type, bool recurse, XmlAttributeOverrides overrides, HashSet<Type> visited, HashSet<Type> withOverrides)
    {
        if (type == null || type == typeof(object) || type.IsPrimitive || type == typeof(string) || visited.Contains(type))
            return;
        var attrs = new XmlAttributes() { XmlDefaultValue = null };
        foreach (var property in type.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public))
            if (overrides[type, property.Name] == null) // Check to see if overrides for this base type were already set.
                if (property.GetCustomAttributes<DefaultValueAttribute>(true).Any())
                {
                    withOverrides.Add(type);
                    overrides.Add(type, property.Name, attrs);
                }
        foreach (var field in type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public))
            if (overrides[type, field.Name] == null) // Check to see if overrides for this base type were already set.
                if (field.GetCustomAttributes<DefaultValueAttribute>(true).Any())
                {
                    withOverrides.Add(type);
                    overrides.Add(type, field.Name, attrs);
                }
        visited.Add(type);
        if (recurse)
        {
            var baseType = type.BaseType;
            if (baseType != type)
                CreateOverrideAttributes(baseType, recurse, overrides, visited, withOverrides);
        }
    }
}

然后你会这样称呼它:

var serializer = XmlSerializerIgnoringDefaultValuesKey.Create(typeof(ClassToSerialize), new[] { typeof(ClassToSerialize), typeof(AdditionalClass1), typeof(AdditionalClass2), ... }, true).GetSerializer();

例如,在以下类层次结构中:

public class BaseClass
{
    public BaseClass() { Index = 1; }
    [DefaultValue(1)]
    public int Index { get; set; }
}

public class MidClass : BaseClass
{
    public MidClass() : base() { MidDouble = 1.0; }
    [DefaultValue(1.0)]
    public double MidDouble { get; set; }
}

public class DerivedClass : MidClass
{
    public DerivedClass() : base() { DerivedString = string.Empty; }
    [DefaultValue("")]
    public string DerivedString { get; set; }
}

public class VeryDerivedClass : DerivedClass
{
    public VeryDerivedClass() : base() { this.VeryDerivedIndex = -1; }
    [DefaultValue(-1)]
    public int VeryDerivedIndex { get; set; }
}

默认的XmlSerializer 产生:

<VeryDerivedClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" />

但自定义序列化程序会产生

<?xml version="1.0" encoding="utf-16"?>
<VeryDerivedClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <Index>1</Index>
    <MidDouble>1</MidDouble>
    <DerivedString />
    <VeryDerivedIndex>-1</VeryDerivedIndex>
</VeryDerivedClass>

最后,请注意空值的写入由[XmlElement( IsNullable = true )] 控制,因此空值的写入不受此序列化程序的影响。

【讨论】:

    【解决方案2】:

    我找到了答案: https://msdn.microsoft.com/en-us/library/system.runtime.serialization.datamemberattribute.emitdefaultvalue%28v=vs.110%29.aspx

    像这样在 DataContract 中设置属性:[DataMember(EmitDefaultValue=true)]

    【讨论】:

      【解决方案3】:

      关于 DataContract 的最后一个答案不是答案。 XSD 是自动生成的,使用这些类的人无法控制原作者使用的属性。问题是关于基于 XSD 的自动生成的类。

      另一个答案也有问题,因为定义了默认值的属性也可能不允许空值(这种情况经常发生)。唯一真正的解决方案是拥有一个序列化程序,您可以在其中告诉它在序列化方面要忽略哪些属性。这一直是当前 XML 序列化程序的一个严重问题,它们根本不允许传递强制序列化的属性。

      实际场景:

      REST 服务在主体中接受 XML 来更新对象。 XML 有一个由 rest 服务的作者定义的 XSD。休息服务存储的当前对象有一个非默认值集。用户修改 XML 以将其更改回默认值...但是放在 REST 帖子正文中的序列化版本会跳过该值并且不包含它,因为它设置为默认值。

      真是个泥潭……无法更新值,因为不导出默认值背后的逻辑完全忽略了 XML 可用于更新对象的想法,而不仅仅是基于 XML 创建新对象。我不敢相信已经这么多年了,没有人修改 XML 序列化器来轻松处理这种基本场景。

      【讨论】:

      • 不,我回答了我自己的问题。 DataContract 属性正是我最初寻找的正确答案。 DataContract 是从 XSD 生成的,但它不是连续构建,只是一次生成,因此在第一次构建类后自定义修改类没有问题。
      • 这一直是当前 XML 序列化器的一个严重问题,它们根本不允许传递哪些属性来强制序列化。 -- XmlSerializer确实支持条件序列化,见ShouldSerialize*() vs *Specified Conditional Serialization Pattern
      【解决方案4】:

      示例如何使用 XmlDefaultValue 属性强制序列化所有公共属性:

      [Test]
      public void GenerateXMLWrapTest()
      {
        var xmlWrap = new XmlWrap();
      
        using (var sw = new StringWriter())
        {
          var overrides = new XmlAttributeOverrides();
          var attrs = new XmlAttributes { XmlDefaultValue = null };
      
          var type = typeof(XmlWrap);
      
          foreach (var propertyInfo in type.GetProperties())
          {
            if (propertyInfo.CanRead && propertyInfo.CanWrite && propertyInfo.GetCustomAttributes(true).Any(o => o is DefaultValueAttribute))
            {
              var propertyNameWithDefaultToIgnore = propertyInfo.Name;
              overrides.Add(type, propertyNameWithDefaultToIgnore, attrs);
            }
          }
      
          var serializer = new XmlSerializer(type, overrides);
      
          serializer.Serialize(sw, xmlWrap);
          sw.Flush();
          var xmlString = sw.ToString();
          Console.WriteLine(xmlString);
        }
      }
      

      输出:

      <?xml version="1.0" encoding="utf-16"?>
      <ConIdTranslator xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="urn:devices-description-1.0">
        <Disabled>false</Disabled>
        <HostPortParams>COM1 baud=115200 parity=None data=8 stop=One</HostPortParams>
        <TranslatorObjectNumber>9000</TranslatorObjectNumber>
      ...
      

      序列化类的Disabled、HostPortParams、TranslatorObjectNumber公共属性有默认值属性:

        [Serializable]
        [XmlRoot("ConIdTranslator", Namespace = "urn:devices-description-1.0", IsNullable = false)]
        public class ConIdTranslatorXmlWrap : HardwareEntityXmlWrap
        {
          #region Fields
      
          [EditorBrowsable(EditorBrowsableState.Never)]
          [XmlIgnore]
          private string hostPortParams = "COM1 baud=115200 parity=None data=8 stop=One";
      
          [EditorBrowsable(EditorBrowsableState.Never)]
          [XmlIgnore]
          private bool disabled = false;
      
          ...
      
          #endregion
      
          #region Properties
      
          [XmlElement]
          [DefaultValue(false)]
          public bool Disabled
          {
            get => this.disabled;
            set
            {
              this.disabled = value;
              this.OnPropertyChanged("Disabled");
            }
          }
      
          [XmlElement]
          [DefaultValue("COM1 baud=115200 parity=None data=8 stop=One")]
          public string HostPortParams
          {
            get => this.hostPortParams;
            set
            {
              this.hostPortParams = value;
              this.OnPropertyChanged("HostPortParams");
            }
          }
      
          ...
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-05-06
        • 2013-10-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多