I am using XmlSerializer to serialize C# objects to XML. I have DefaultValueAttribute on some of the properties of the classes I am trying to serialize, when I try to serialize them it seems that XmlSerializer does not include value in xml if it equals default value.
Look at this example:
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace Test
{
public class Person
{
[System.Xml.Serialization.XmlAttribute()]
[System.ComponentModel.DefaultValue("John")]
public string Name { get; set; }
}
public static class Test
{
public static void Main()
{
var serializer = new XmlSerializer(typeof(Person));
var person = new Person { Name = "John" };
using (var sw = new StringWriter())
{
using (var writer = XmlWriter.Create(sw))
{
serializer.Serialize(writer, person);
var xml = sw.ToString();
}
}
}
}
}
It will produce the following xml (notice name attribute is not available):
<?xml version="1.0" encoding="utf-16"?>
<Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" />
I cannot modify the source code of the classes so I CANNOT remove DefaultValueAttribute. Is there a way to make XmlSerializer serialize this properties without changing the source code?
You can do it by passing in an XmlAttributeOverrides instance to the XmlSerializer when you create it as below. You can either change the default value to something else using this, or set it to null to effectively remove it.
XmlAttributeOverrides attributeOverrides = new XmlAttributeOverrides();
var attributes = new XmlAttributes()
{
XmlDefaultValue = null,
XmlAttribute = new XmlAttributeAttribute()
};
attributeOverrides.Add(typeof(Person), "Name", attributes);
var serializer = new XmlSerializer(typeof(Person), attributeOverrides);
var person = new Person { Name = "John" };
using (var sw = new StringWriter())
{
using (var writer = XmlWriter.Create(sw))
{
serializer.Serialize(writer, person);
var xml = sw.ToString();
}
}
Update: the above means you have to provide other unrelated attributes again on each property you are changing. This is a bit of a chore if you have a lot of properties and just want to remove default for all of them. The class below can be used to preserve other custom attributes while only removing one type of attribute. It can be further extended if required to only do it for certain properties etc.
public class XmlAttributeOverrideGenerator<T>
{
private static XmlAttributeOverrides _overrides;
private static Type[] _ignoreAttributes = new Type[] { typeof(DefaultValueAttribute) };
static XmlAttributeOverrideGenerator()
{
_overrides = Generate();
}
public static XmlAttributeOverrides Get()
{
return _overrides;
}
private static XmlAttributeOverrides Generate()
{
var xmlAttributeOverrides = new XmlAttributeOverrides();
Type targetType = typeof(T);
foreach (var property in targetType.GetProperties())
{
XmlAttributes propertyAttributes = new XmlAttributes(new CustomAttribProvider(property, _ignoreAttributes));
xmlAttributeOverrides.Add(targetType, property.Name, propertyAttributes);
}
return xmlAttributeOverrides;
}
public class CustomAttribProvider : ICustomAttributeProvider
{
private PropertyInfo _prop = null;
private Type[] _ignoreTypes = null;
public CustomAttribProvider(PropertyInfo property, params Type[] ignoreTypes)
{
_ignoreTypes = ignoreTypes;
_prop = property;
}
public object[] GetCustomAttributes(bool inherit)
{
var attribs = _prop.GetCustomAttributes(inherit);
if (_ignoreTypes == null) return attribs;
return attribs.Where(attrib => IsAllowedType(attrib)).ToArray();
}
private bool IsAllowedType(object attribute)
{
if (_ignoreTypes == null) return true;
foreach (Type type in _ignoreTypes)
if (attribute.GetType() == type)
return false;
return true;
}
public object[] GetCustomAttributes(Type attributeType, bool inherit)
{
throw new NotImplementedException();
}
public bool IsDefined(Type attributeType, bool inherit)
{
throw new NotImplementedException();
}
}
}
Usage:
XmlAttributeOverrides attributeOverrides = XmlAttributeOverrideGenerator<Person>.Get();
var serializer = new XmlSerializer(typeof(Person), attributeOverrides);
I dont have enough reputation to comment on steve16351's answer. but I feel I improved upon his code a little bit.
My use case involved an XML schema file generated via XSD.exe provided by Microsoft, and a class was then generated from the schema file. So the class had all the DefaultValue tags on it from the schema. There was also many nested types created. So to make my code succinct, I modified the 'Generate' method to get the overrides for the top-level class, and all the ones beneath it. Then everything is returned in a single XmlAttributesOverrides object.
static XmlAttributeOverrideGenerator()
{
_overrides = Generate(typeof(T), new XmlAttributeOverrides());
}
private static XmlAttributeOverrides Generate(Type targetType, XmlAttributeOverrides Overrides)
{
foreach (var property in targetType.GetProperties())
{
if (property.CustomAttributes.Any( CA => CA.AttributeType == typeof(DefaultValueAttribute)))
{
XmlAttributes propertyAttributes = new XmlAttributes(new CustomAttribProvider(property, _ignoreAttributes));
Overrides.Add(targetType, property.Name, propertyAttributes);
}
else
{
Type propType = property.PropertyType;
if (propType.IsClass && !propType.IsValueType && !propType.IsEnum && !propType.IsPrimitive && !propType.IsSealed) // Check if this is a custom class
{
//If this is a nested class or other class type, generate its overrides as well
Generate(propType, Overrides);
}
}
}
return Overrides;
}
Related
I have a scenario where I will receive a dictionary<string, object>();
that can also be recursive though not always, but the main issue is that it contains lambdas.
as we implement a transaction system I need to clone it, which works fine until I hit the lambdas.
I tried to move these to delegates but the error changes and still gives me runtime issue.
technically I can re inject the lambdas after the cloning, but don't know how to tell the DataContractSerializer to ignore these.
I also cannot remove the lambdas prior to the cloning as the original object still needs them if I cancel the transaction.
In your Clone() method you can use the data contract surrogate functionality to replace all System.Delegate objects in your serialization graph with a serializable type, such as a lookup in a dictionary of delegates that you build as you serialize. Then, as you deserialize, you could replace the deserialized serialization surrogates with the original delegates.
The following does this:
public static class DataContractExtensions
{
public static Dictionary<string, object> Clone(this Dictionary<string, object> dictionary)
{
if (dictionary == null)
return null;
var surrogate = new DelegateSurrogateSelector();
var types = new[]
{
typeof(Dictionary<string, object>),
typeof(DelegateSurrogateId),
// Add in whatever additional known types you want.
};
var serializer = new DataContractSerializer(
typeof(Dictionary<string, object>),
types, int.MaxValue, false, false,
surrogate);
var xml = dictionary.ToContractXml(serializer, null);
return FromContractXml<Dictionary<string, object>>(xml, serializer);
}
public static string ToContractXml<T>(this T obj, DataContractSerializer serializer, XmlWriterSettings settings)
{
serializer = serializer ?? new DataContractSerializer(obj == null ? typeof(T) : obj.GetType());
using (var textWriter = new StringWriter())
{
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
{
serializer.WriteObject(xmlWriter, obj);
}
return textWriter.ToString();
}
}
public static T FromContractXml<T>(string xml, DataContractSerializer serializer)
{
using (var textReader = new StringReader(xml ?? ""))
using (var xmlReader = XmlReader.Create(textReader))
{
return (T)(serializer ?? new DataContractSerializer(typeof(T))).ReadObject(xmlReader);
}
}
[DataContract]
class DelegateSurrogateId
{
[DataMember]
public int Id { get; set; }
}
class DelegateSurrogateSelector : IDataContractSurrogate
{
public Dictionary<int, System.Delegate> DelegateDictionary { get; private set; }
public DelegateSurrogateSelector()
{
this.DelegateDictionary = new Dictionary<int, Delegate>();
}
#region IDataContractSurrogate Members
public object GetCustomDataToExport(Type clrType, Type dataContractType)
{
throw new NotImplementedException();
}
public object GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType)
{
throw new NotImplementedException();
}
public Type GetDataContractType(Type type)
{
if (typeof(Delegate).IsAssignableFrom(type))
return typeof(DelegateSurrogateId);
return type;
}
public object GetDeserializedObject(object obj, Type targetType)
{
var id = obj as DelegateSurrogateId;
if (id != null)
return DelegateDictionary[id.Id];
return obj;
}
public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
{
throw new NotImplementedException();
}
public object GetObjectToSerialize(object obj, Type targetType)
{
var del = obj as Delegate;
if (del != null)
{
var id = DelegateDictionary.Count;
DelegateDictionary.Add(id, del);
return new DelegateSurrogateId { Id = id };
}
return obj;
}
public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
{
throw new NotImplementedException();
}
public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
{
throw new NotImplementedException();
}
#endregion
}
}
Using the above Clone() extension method, the following test will pass:
Func<bool> returnTrue = () => true;
Func<bool> returnFalse = () => false;
var dictionary = new Dictionary<string, object>()
{
{ "a", "hello"},
{ "b", 10101 },
{ "c", returnTrue },
{ "d", new Dictionary<string, object>()
{
{ "q", 101 },
{ "r", returnFalse },
}
}
};
var dictionary2 = dictionary.Clone();
Assert.AreEqual(returnTrue, dictionary2["c"]); // No failure
var inner = (Dictionary<string, object>)dictionary["d"];
var inner2 = (Dictionary<string, object>)dictionary2["d"];
Assert.AreEqual(inner["r"], inner2["r"]); // No failure
Notes:
Data contract serialization surrogates work differently in .NET Core. There, the IDataContractSurrogate interface has been replaced with ISerializationSurrogateProvider.
I have an interface IInput that is preventing the XmlSerializer from serializing the class natively (because it doesnt like interfaces). I found a hack/workaround that attempts to just create the underlying implementation when deserializing and then casts that back to the interface. The deserializer knows the underlying implementation because its encoded as a attribute AssemblyQualifiedName
In order to take advantage of this technique I have to implement IXmlSerializable, but only 1 property really needs help (IInput Input), I wish for all the other ones to act as if they were normal. Here is my class, it works as expected
but seems like a very messy way of getting types of which the normal XMLserializer can serialize to conform to an IXmlSerialiable interface.
Is there some sort of "Serialize all properties natively except x"? If not what is one way I can make this more readable and/or less copy and pasted
public class JobInput : IJobInput, IXmlSerializable
{
public int AgencyId { get; set; }
public Guid ExternalId { get; set; }
public string Requester { get; set; }
public IInput Input { get; set; }
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
reader.MoveToContent();
reader.ReadStartElement();
if (!reader.IsEmptyElement)
{
reader.ReadStartElement("AgencyId");
var xmlSerializer = new XmlSerializer(AgencyId.GetType());
AgencyId = ((int)xmlSerializer.Deserialize(reader));
reader.ReadEndElement();
reader.ReadStartElement("ExternalId");
xmlSerializer = new XmlSerializer(ExternalId.GetType());
ExternalId = ((Guid)xmlSerializer.Deserialize(reader));
reader.ReadEndElement();
reader.ReadStartElement("Requester");
xmlSerializer = new XmlSerializer(typeof(string));
Requester = ((string)xmlSerializer.Deserialize(reader));
reader.ReadEndElement();
var type = Type.GetType(reader.GetAttribute("AssemblyQualifiedName"), true);
reader.ReadStartElement("IInput");
xmlSerializer = new XmlSerializer(type);
Input = ((IInput)xmlSerializer.Deserialize(reader));
reader.ReadEndElement();
reader.ReadEndElement();
}
}
public void WriteXml(XmlWriter writer)
{
writer.WriteStartElement("AgencyId");
var xmlSerializer = new XmlSerializer(AgencyId.GetType());
xmlSerializer.Serialize(writer, AgencyId);
writer.WriteEndElement();
writer.WriteStartElement("ExternalId");
xmlSerializer = new XmlSerializer(ExternalId.GetType());
xmlSerializer.Serialize(writer, ExternalId);
writer.WriteEndElement();
writer.WriteStartElement("Requester");
xmlSerializer = new XmlSerializer(Requester.GetType());
xmlSerializer.Serialize(writer, Requester);
writer.WriteEndElement();
writer.WriteStartElement("IInput");
writer.WriteAttributeString("AssemblyQualifiedName", Input.GetType().AssemblyQualifiedName);
xmlSerializer = new XmlSerializer(Input.GetType());
xmlSerializer.Serialize(writer, Input);
writer.WriteEndElement();
}
}
Is it possible to have a generic function that can just detect the type for all the concrete types and serialize/deserialize appropriately. I would like
something like
public void WriteXml(XmlWriter writer) {
GenericSerialize("AgencyId", AgencyId, writer);
GenericSerialize("ExternalId", ExternalId, writer);
GenericSerialize("Requester", Requester, writer);
writer.WriteStartElement("IInput");
writer.WriteAttributeString("AssemblyQualifiedName", Input.GetType().AssemblyQualifiedName);
xmlSerializer = new XmlSerializer(Input.GetType());
xmlSerializer.Serialize(writer, Input);
writer.WriteEndElement();
}
You can use [XmlAnyElement] to add an XElement []-valued property to your class that handles serialization and deserialization of properties that cannot be automatically serialized, like so:
[XmlRoot(Namespace = JobInput.XmlNamespace)]
[XmlType(Namespace = JobInput.XmlNamespace)]
public class JobInput
{
const string XmlNamespace = "";
public int AgencyId { get; set; }
public Guid ExternalId { get; set; }
public string Requester { get; set; }
[XmlIgnore]
public IInput Input { get; set; }
[XmlAnyElement]
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
public XElement[] XmlCustomElements
{
get
{
var list = new List<XElement>();
if (Input != null)
list.Add(Input.SerializePolymorphicToXElement(XName.Get("Input", XmlNamespace)));
// Add others as needed.
return list.ToArray();
}
set
{
if (value == null)
return;
this.Input = value.DeserializePolymorphicEntry<IInput>(XName.Get("Input", XmlNamespace));
// Add others as needed.
}
}
}
Your standard properties will now get automatically serialized and your custom properties can be semi-automatically serialized through nested calls to XmlSerializer using the appropriate type. The following extension methods are required:
public static class XObjectExtensions
{
public static XmlSerializerNamespaces NoStandardXmlNamespaces()
{
var ns = new XmlSerializerNamespaces();
ns.Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
return ns;
}
public static object Deserialize(this XContainer element, Type type, XmlSerializer serializer)
{
using (var reader = element.CreateReader())
{
return (serializer ?? new XmlSerializer(type)).Deserialize(reader);
}
}
public static XElement SerializeToXElement<T>(this T obj, XmlSerializerNamespaces ns, XmlSerializer serializer)
{
var doc = new XDocument();
using (var writer = doc.CreateWriter())
(serializer ?? new XmlSerializer(obj.GetType())).Serialize(writer, obj, ns);
var element = doc.Root;
if (element != null)
element.Remove();
return element;
}
const string TypeAttributeName = "AssemblyQualifiedName";
public static T DeserializePolymorphicEntry<T>(this XElement[] arrayValue, XName name)
{
var element = arrayValue.Where(e => e.Name == name).FirstOrDefault();
return element.DeserializePolymorphic<T>(name);
}
public static T DeserializePolymorphic<T>(this XElement value, XName name)
{
if (value == null)
return default(T);
var typeName = (string)value.Attribute(TypeAttributeName);
if (typeName == null)
throw new InvalidOperationException(string.Format("Missing AssemblyQualifiedName for \"{0}\"", value.ToString()));
var type = Type.GetType(typeName, true); // Throw on error
return (T)value.Deserialize(type, XmlSerializerFactory.Create(type, name));
}
public static XElement SerializePolymorphicToXElement<T>(this T obj, XName name)
{
if (obj == null)
return null;
var element = obj.SerializeToXElement(XObjectExtensions.NoStandardXmlNamespaces(), XmlSerializerFactory.Create(obj.GetType(), name));
// Remove namespace attributes (they will be added back by the XmlWriter if needed)
foreach (var attr in element.Attributes().Where(a => a.IsNamespaceDeclaration).ToList())
attr.Remove();
element.Add(new XAttribute("AssemblyQualifiedName", obj.GetType().AssemblyQualifiedName));
return element;
}
}
public static class XmlSerializerFactory
{
static readonly object padlock;
static readonly Dictionary<Tuple<Type, XName>, XmlSerializer> serializers;
// An explicit static constructor enables fairly lazy initialization.
static XmlSerializerFactory()
{
padlock = new object();
serializers = new Dictionary<Tuple<Type, XName>, XmlSerializer>();
}
/// <summary>
/// Return a cached XmlSerializer for the given type and root name.
/// </summary>
/// <param name="type"></param>
/// <param name="name"></param>
/// <returns>a cached XmlSerializer</returns>
public static XmlSerializer Create(Type type, XName name)
{
// According to https://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer%28v=vs.110%29.aspx
// XmlSerializers created with new XmlSerializer(Type, XmlRootAttribute) must be cached in a hash table
// to avoid a severe memory leak & performance hit.
if (type == null)
throw new ArgumentNullException();
if (name == null)
return new XmlSerializer(type);
lock (padlock)
{
XmlSerializer serializer;
var key = Tuple.Create(type, name);
if (!serializers.TryGetValue(key, out serializer))
serializers[key] = serializer = new XmlSerializer(type, new XmlRootAttribute { ElementName = name.LocalName, Namespace = name.NamespaceName });
return serializer;
}
}
}
Doing it this way makes your class look simpler and reduces the possibility of bugs from mistakes in implementing IXmlSerializable - but it does require a little bit of reusable infrastructure.
Prototype fiddle.
Seems what I was after was possible
public void GenericWriter<T>(ref XmlWriter writer, string propertyName, T property)
{
writer.WriteStartElement(propertyName);
var xmlSerializer = new XmlSerializer(typeof(T));
xmlSerializer.Serialize(writer, property);
writer.WriteEndElement();
}
so same for reader. I ended up using ref just in case.
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?
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.
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. Note, when creating a blank XmlAttributes() object all attributes are set to null. When overriding with this XmlAttributes object any attributes that are desired to stay need to be transferred into this new object:
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.
Example how to force serialize all public properties with XmlDefaultValue attribute:
[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);
}
}
Output:
<?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>
...
Where Disabled, HostPortParams, TranslatorObjectNumber public properties of serialized class has default value attribute:
[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");
}
}
...
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)]
If I have a class defined
[DataContract()]
class MyObject {
[DataMember()]
ImmutableList<string> Strings { get; private set}
}
The ImmutableList<T> type comes from the immutables library https://www.nuget.org/packages/Microsoft.Bcl.Immutable. Note that the class ImmutableList does not have a default constructor or a mutable Add method. Adding things to the list take the form.
myList = myList.Add("new string");
Can I add some custom support to the .NET serialization mechanism to support this type and show it how to deserialize it?
Currently the collection is just skipped on deserialization though it is fine to serialize it.
There is another clean way to do this via IDataContractSurrogate interface. The DataContractSerializer allows you to provide a surrogate for non serializable objects. Below is the example and test case for ImmutableList<T>. It uses reflection and probably could be optimized by smarter folks than me but here it is.
TESTCASE
using FluentAssertions;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Xml;
using Xunit;
namespace ReactiveUI.Ext.Spec
{
[DataContract(Name="Node", Namespace="http://foo.com/")]
class Node
{
[DataMember()]
public string Name;
}
[DataContract(Name="Fixture", Namespace="http://foo.com/")]
class FixtureType
{
[DataMember()]
public ImmutableList<Node> Nodes;
public FixtureType(){
Nodes = ImmutableList<Node>.Empty.AddRange( new []
{ new Node(){Name="A"}
, new Node(){Name="B"}
, new Node(){Name="C"}
});
}
}
public class ImmutableSurrogateSpec
{
public static string ToXML(object obj)
{
var settings = new XmlWriterSettings { Indent = true };
using (MemoryStream memoryStream = new MemoryStream())
using (StreamReader reader = new StreamReader(memoryStream))
using (XmlWriter writer = XmlWriter.Create(memoryStream, settings))
{
DataContractSerializer serializer =
new DataContractSerializer
( obj.GetType()
, new DataContractSerializerSettings() { DataContractSurrogate = new ImmutableSurrogateSerializer() }
);
serializer.WriteObject(writer, obj);
writer.Flush();
memoryStream.Position = 0;
return reader.ReadToEnd();
}
}
public static T Load<T>(Stream data)
{
DataContractSerializer ser = new DataContractSerializer
( typeof(T)
, new DataContractSerializerSettings() { DataContractSurrogate = new ImmutableSurrogateSerializer() }
);
return (T)ser.ReadObject(data);
}
public static T Load<T>(string data)
{
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(data)))
{
return Load<T>(stream);
}
}
[Fact]
public void ShouldWork()
{
var o = new FixtureType();
var s = ToXML(o);
var oo = Load<FixtureType>(s);
oo.Nodes.Count().Should().Be(3);
var names = oo.Nodes.Select(n => n.Name).ToList();
names.ShouldAllBeEquivalentTo(new[]{"A", "B", "C"});
}
}
}
IMPLEMENTATION
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.Serialization;
namespace ReactiveUI.Ext
{
class ImmutableListListConverter<T>
{
public static ImmutableList<T> ToImmutable( List<T> list )
{
return ImmutableList<T>.Empty.AddRange(list);
}
public static List<T> ToList(ImmutableList<T> list){
return list.ToList();
}
public static object ToImmutable( object list )
{
return ToImmutable(( List<T> ) list);
}
public static object ToList(object list){
return ToList(( ImmutableList<T> ) list);
}
}
static class ImmutableListListConverter {
static ConcurrentDictionary<Tuple<string, Type>, Func<object,object>> _MethodCache
= new ConcurrentDictionary<Tuple<string, Type>, Func<object,object>>();
public static Func<object,object> CreateMethod( string name, Type genericType )
{
var key = Tuple.Create(name, genericType);
if ( !_MethodCache.ContainsKey(key) )
{
_MethodCache[key] = typeof(ImmutableListListConverter<>)
.MakeGenericType(new []{genericType})
.GetMethod(name, new []{typeof(object)})
.MakeLambda();
}
return _MethodCache[key];
}
public static Func<object,object> ToImmutableMethod( Type targetType )
{
return ImmutableListListConverter.CreateMethod("ToImmutable", targetType.GenericTypeArguments[0]);
}
public static Func<object,object> ToListMethod( Type targetType )
{
return ImmutableListListConverter.CreateMethod("ToList", targetType.GenericTypeArguments[0]);
}
private static Func<object,object> MakeLambda(this MethodInfo method )
{
return (Func<object,object>) method.CreateDelegate(Expression.GetDelegateType(
(from parameter in method.GetParameters() select parameter.ParameterType)
.Concat(new[] { method.ReturnType })
.ToArray()));
}
}
public class ImmutableSurrogateSerializer : IDataContractSurrogate
{
static ConcurrentDictionary<Type, Type> _TypeCache = new ConcurrentDictionary<Type, Type>();
public Type GetDataContractType( Type targetType )
{
if ( _TypeCache.ContainsKey(targetType) )
{
return _TypeCache[targetType];
}
if(targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(ImmutableList<>))
{
return _TypeCache[targetType]
= typeof(List<>).MakeGenericType(targetType.GetGenericArguments());
}
else
{
return targetType;
}
}
public object GetDeserializedObject( object obj, Type targetType )
{
if ( _TypeCache.ContainsKey(targetType) )
{
return ImmutableListListConverter.ToImmutableMethod(targetType)(obj);
}
return obj;
}
public object GetObjectToSerialize( object obj, Type targetType )
{
if ( targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(ImmutableList<>) )
{
return ImmutableListListConverter.ToListMethod(targetType)(obj);
}
return obj;
}
public object GetCustomDataToExport( Type clrType, Type dataContractType )
{
throw new NotImplementedException();
}
public object GetCustomDataToExport( System.Reflection.MemberInfo memberInfo, Type dataContractType )
{
throw new NotImplementedException();
}
public void GetKnownCustomDataTypes( System.Collections.ObjectModel.Collection<Type> customDataTypes )
{
throw new NotImplementedException();
}
public Type GetReferencedTypeOnImport( string typeName, string typeNamespace, object customData )
{
throw new NotImplementedException();
}
public System.CodeDom.CodeTypeDeclaration ProcessImportedType( System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit )
{
throw new NotImplementedException();
}
public ImmutableSurrogateSerializer() { }
}
}
One way to do this is to use a proxy mutable list and use the OnSerializing and OnDeserialized hooks
[DataContract()]
class MyObject {
public ImmutableList<string> Strings { get; private set}
[DataMember(Name="Strings")]
private List<String> _Strings;
[OnSerializing()]
public void OnSerializing(StreamingContext ctxt){
_Strings = Strings.ToList();
}
[OnDeserialized()]
public void OnDeserialized(StreamingContext ctxt){
Strings = ImmutableList<string>.Empty.AddRange(_Strings);
}
}
It's not super pretty but as Marc Gravell noted in his answer, DataContract serializer is broken with respects to immutable collections and there are no simple hooks to teach it how to behave without the above type of hack.
UPDATE
DataContract serializer is not broken. There is a way to hook surrogates in. See this separate answer showing an alternate technique.
https://stackoverflow.com/a/18957739/158285
Heh; I can imagine what is happening here... the generated code is probably doing (paraphrasing):
var list = obj.Strings;
while(CanReadNextItem()) {
list.Add(ReadNextItem());
}
The problem is that the BCL immutable API would require you to catch the result each time, i.e.
var list = obj.Strings;
while(CanReadNextItem()) {
list = list.Add(ReadNextItem());
}
obj.Strings = list; // the private set is not a problem for this
Pre-existing list deserialization code doesn't work this way because it has never needed to - and indeed, there are many different implementations of Add, some of which return non-void results which are required to be ignored.
The lack of a non-public constructor may also upset it a bit, but if this was the main problem, I would kinda expect an exception when it tries to create a non-null list.
Of course, in performance terms, the list = list.Add(...) API probably isn't the most appropriate one to use anyway (although it should work).
I blogged on this topic recently (in the context of protobuf-net, which has now been updated to work with these collection types): http://marcgravell.blogspot.co.uk/2013/09/fun-with-immutable-collections.html Hopefully this blog article should explain why the differences mean that it doesn't play nicely with existing serialization techniques, and how serialization libraries might be updated to work with this scenario.
To answer the question directly, I would say the answer is simply: because the required changes to support immutable collections have not yet been made to DataContractSerializer. I have no knowledge of whether there is a plan to address this. But: I happily declare: "works in protobuf-net" ;p
I use XML serialization for the reading of my Config-POCOs.
To get intellisense support in Visual Studio for XML files I need a schema file. I can create the schema with xsd.exe mylibrary.dll and this works fine.
But I want that the schema is always created if I serialize an object to the file system. Is there any way without using xsd.exe?
thank you, this was the right way for me.
solution:
XmlReflectionImporter importer = new XmlReflectionImporter();
XmlSchemas schemas = new XmlSchemas();
XmlSchemaExporter exporter = new XmlSchemaExporter(schemas);
Type type = toSerialize.GetType();
XmlTypeMapping map = importer.ImportTypeMapping(type);
exporter.ExportTypeMapping(map);
TextWriter tw = new StreamWriter(fileName + ".xsd");
schemas[0].Write(tw);
tw.Close();
The solution posted above by Will worked wonderfully, except I realized that the schema generated did not reflect the attributes on the different class members. For example a class decorated with serialization hint attributes (see the sample below), would have not rendered correctly.
public class Test
{
[XmlAttribute()]
public string Attribute { get; set; }
public string Description { get; set; }
[XmlArray(ElementName = "Customers")]
[XmlArrayItem(ElementName = "Customer")]
public List<CustomerClass> blah { get; set; }
}
To address this, I created a few helper functions that use reflection to traverse the class hierarchy, read the attributes, and populate a XmlAttributeOverrides object that can be passed into the XmlReflectionImporter.
public static void AttachXmlAttributes(XmlAttributeOverrides xao, Type t)
{
List<Type> types = new List<Type>();
AttachXmlAttributes(xao, types, t);
}
public static void AttachXmlAttributes(XmlAttributeOverrides xao, List<Type> all, Type t)
{
if(all.Contains(t))
return;
else
all.Add(t);
XmlAttributes list1 = GetAttributeList(t.GetCustomAttributes(false));
xao.Add(t, list1);
foreach (var prop in t.GetProperties())
{
XmlAttributes list2 = GetAttributeList(prop.GetCustomAttributes(false));
xao.Add(t, prop.Name, list2);
AttachXmlAttributes(xao, all, prop.PropertyType);
}
}
private static XmlAttributes GetAttributeList(object[] attributes)
{
XmlAttributes list = new XmlAttributes();
foreach (var attribute in attributes)
{
Type type = attribute.GetType();
if (type.Name == "XmlAttributeAttribute") list.XmlAttribute = (XmlAttributeAttribute)attribute;
else if (type.Name == "XmlArrayAttribute") list.XmlArray = (XmlArrayAttribute)attribute;
else if (type.Name == "XmlArrayItemAttribute") list.XmlArrayItems.Add((XmlArrayItemAttribute)attribute);
}
return list;
}
public static string GetSchema<T>()
{
XmlAttributeOverrides xao = new XmlAttributeOverrides();
AttachXmlAttributes(xao, typeof(T));
XmlReflectionImporter importer = new XmlReflectionImporter(xao);
XmlSchemas schemas = new XmlSchemas();
XmlSchemaExporter exporter = new XmlSchemaExporter(schemas);
XmlTypeMapping map = importer.ImportTypeMapping(typeof(T));
exporter.ExportTypeMapping(map);
using (MemoryStream ms = new MemoryStream())
{
schemas[0].Write(ms);
ms.Position = 0;
return new StreamReader(ms).ReadToEnd();
}
}
Hope this helps someone else.
Look at the System.Xml.Serialization.XmlSchemaExporter class. I can't recall the exact details, but there is enough functionality in that namespace to do what you require.
Improvement to Matt Murrell version: to apply XmlAttributes recursively for nested property user type (for example CustomerClass property).
private static void AttachXmlAttributes(XmlAttributeOverrides xao, List<Type> all, Type t)
{
if (all.Contains(t))
{
return;
}
else
{
all.Add(t);
}
var list1 = GetAttributeList(t.GetCustomAttributes(false));
xao.Add(t, list1);
foreach (var prop in t.GetProperties())
{
var propType = prop.PropertyType;
if (propType.IsGenericType) // is list?
{
var args = propType.GetGenericArguments();
if (args != null && args.Length == 1)
{
var genType = args[0];
if (genType.Name.ToLower() != "object")
{
var list2 = GetAttributeList(prop.GetCustomAttributes(false));
xao.Add(t, prop.Name, list2);
AttachXmlAttributes(xao, all, genType);
}
}
}
else
{
var list2 = GetAttributeList(prop.GetCustomAttributes(false));
xao.Add(t, prop.Name, list2);
AttachXmlAttributes(xao, all, prop.PropertyType);
}
}
}
private static XmlAttributes GetAttributeList(object[] attributes)
{
var list = new XmlAttributes();
foreach (var attr in attributes)
{
Type type = attr.GetType();
switch (type.Name)
{
case "XmlAttributeAttribute":
list.XmlAttribute = (XmlAttributeAttribute)attr;
break;
case "XmlRootAttribute":
list.XmlRoot = (XmlRootAttribute)attr;
break;
case "XmlElementAttribute":
list.XmlElements.Add((XmlElementAttribute)attr);
break;
case "XmlArrayAttribute":
list.XmlArray = (XmlArrayAttribute)attr;
break;
case "XmlArrayItemAttribute":
list.XmlArrayItems.Add((XmlArrayItemAttribute)attr);
break;
}
}
return list;
}