Force XML serialization of XmlDefaultValue values

蹲街弑〆低调 提交于 2019-11-27 19:35:59

问题


Using a C# class generated from an XSD document, I can create an object, and serialize it successfully. However, some attributes have an XmlDefaultValue defined. If any objects have the default value, then those attributes do not get created when the object is serialized.

This is expected behavior according to the documentation. But this is not how I want it to behave. I need to have all such attributes generated in the XML document.
I've checked for any code attributes that can be applied that might force it to be outputted, even if it is the default value, but I couldn't find anything like that.

Is there any way to make that work?


回答1:


The last answer regarding DataContract is NOT the answer. The XSD is generated automatically and the person consuming the classes is not in control of the attributes used by the original author. The question was about auto-generated classes based on an XSD.

The other answer is problematic too because properties that have defaults defined also may not allow null values (this happens often). The only real solution is to have a serializer where you can tell it what properties to ignore with respect to serialization. This has been and always be a serious problem with current XML serializers that simply don't allow one to pass in what properties to force being serialized.

Actual scenario:

A REST service accepts XML in the body to update an object. The XML has an XSD defined by the author of the rest service. The current object stored by the rest service has a non-default value set. The users modifies the XML to change it back to the default... but the serialized version put in the body of the REST post skips the value and doesn't include it because its set to a default value.

What a quagmire... can't update the value because the logic behind not exporting default values completely ignores the idea that XML can be used to update an object, not just create new ones based on the XML. I can't believe its been this many years and nobody modified XML serializers to handle this basic scenario with ease.




回答2:


You can do this for a specific set of types when serializing by constructing an XmlAttributeOverrides that specifies new XmlAttributes() { XmlDefaultValue = null } for every field or property that has DefaultValueAttribute applied, then passing this to the XmlSerializer(Type, XmlAttributeOverrides) constructor:

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

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

Note, however, this important warning from the documentation:

Dynamically Generated Assemblies

To increase performance, the XML serialization infrastructure dynamically generates assemblies to serialize and deserialize specified types. The infrastructure finds and reuses those assemblies. This behavior occurs only when using the following constructors:

XmlSerializer.XmlSerializer(Type)

XmlSerializer.XmlSerializer(Type, String)

If you use any of the other constructors, multiple versions of the same assembly are generated and never unloaded, which results in a memory leak and poor performance. The easiest solution is to use one of the previously mentioned two constructors. Otherwise, you must cache the assemblies in a Hashtable, as shown in the following example.

However, the example given in the code doesn't give any suggestion of how to key the hashtable. It also isn't thread-safe. (Perhaps it dates from .Net 1.0?)

The following code creates a key scheme for xml serializers with overrides, and manufactures (via reflection) serializers for which the [DefaultValue] values (if any) of all properties and fields are overridden to be null, effectively cancelling the default value:

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);
        }
    }
}

And then you would call it like:

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

For example, in the following class hierarchy:

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; }
}

The default XmlSerializer produces:

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

But the custom serializer produces

<?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>

Finally, note that writing of null values is controlled by [XmlElement( IsNullable = true )] so writing of nulls is not affected by this serializer.




回答3:


I found the answer: https://msdn.microsoft.com/en-us/library/system.runtime.serialization.datamemberattribute.emitdefaultvalue%28v=vs.110%29.aspx

Set the attribute in the DataContract like this: [DataMember(EmitDefaultValue=true)]



来源:https://stackoverflow.com/questions/28054335/force-xml-serialization-of-xmldefaultvalue-values

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!