问题
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