UPDATE
The problem is no longer in the struct itself, but in my GetSerializerFor(Type typeOfT)
method shown below
Original Question
I have the following struct that serializes to
<Base64String><string>valuehere</string></Base64String>
I want it to serialize to simply
<string>valuehere</string>
But I can't get it to work, I have tried implement IXmlSerializable but that lead to issues creating the Serializer. How can I accomplish this?
Here is the struct, basically this is to allow byte[]
to be serialized, and treated as either a string
or a byte[]
depending on the context in which it is being used.
using System.Linq;
using System.Collections.Generic;
using System.Xml.Serialization;
public struct Base64String : IEqualityComparer<Base64String>
{
private byte[] bytes;
private Base64String(byte[] value)
{
bytes = value;
}
private Base64String(string value)
{
bytes = System.Text.Encoding.ASCII.GetBytes(value);
}
public static implicit operator string(Base64String value)
{
return System.Text.Encoding.ASCII.GetString(value.bytes);
}
public static implicit operator byte[](Base64String value)
{
return value.bytes;
}
public static implicit operator Base64String(string value)
{
return new Base64String(value);
}
public static implicit operator Base64String(byte[] value)
{
return new Base64String(value);
}
public static bool operator ==(Base64String left, Base64String right)
{
// Both null, thus equal
if ((object)left == null && (object)right == null)
return true;
// Only one is null, thus not equal
if ((object)left == null || (object)right == null)
return false;
// Neither null, compare
return left.bytes.SequenceEqual(right.bytes);
}
public static bool operator !=(Base64String left, Base64String right)
{
return !(left == right);
}
public override bool Equals(object obj)
{
return this == (Base64String)obj;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public bool Equals(Base64String x, Base64String y)
{
return (x == y);
}
public int GetHashCode(Base64String obj)
{
return base.GetHashCode();
}
public override string ToString()
{
return this;
}
[XmlElement("string")]
public string Value
{
get { return this; }
set { this = value; }
}
}
Ideally, this I would like to get rid of the Value
property as it's only used to make this serialize...albeit, not to what I want.
Thanks, any help is appreciated.
EDIT - Added Serialization Test Code
[TestMethod]
public void SerializeBase64String()
{
Base64String base64FromString = "This is a test string";
var serializer = new XmlSerializer(typeof(Base64String));
var xdoc = new XDocument();
using (var writer = xdoc.CreateWriter())
{
serializer.Serialize(writer, base64FromString, new XmlSerializerNamespaces(new[] { new XmlQualifiedName("", "") }));
}
var result = (xdoc.Document != null) ? xdoc.Document.Root : new XElement("Error", "Document Missing");
Assert.AreNotEqual(result.Name.ToString(), "Error", result.ToString());
Assert.IsTrue(result.ToString().Contains(base64FromString), string.Format("Xml does not contain value '{0}'.", base64FromString));
Assert.AreEqual(new XElement("string", base64FromString).ToString(), result.ToString());
}
The reason for the odd "Error" xml is that the serialization code is actually in a more complex extension method, and in my test, I can just call .Serialize()
But this is the same code path that would execute
Edit - Implementing IXmlSerializable
If I implement the IXmlSerializable interface, and add the following code
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
if (reader.IsStartElement("string"))
this = reader.ReadContentAsString();
}
public void WriteXml(XmlWriter writer)
{
var stringValue = this;
writer.WriteStartElement("string");
writer.WriteValue(stringValue);
writer.WriteEndElement();
}
I get this error
System.InvalidOperationException: There was an error reflecting type 'Base64String'. ---> System.InvalidOperationException: Only XmlRoot attribute may be specified for the type Base64String. Please use XmlSchemaProviderAttribute to specify schema type.
Edit - GetSerializerFor static method
My .Serialize()
extension method, uses the following factory to get the serializer for an object
The problem is on the line var newSerializer = new XmlSerializer(typeOfT, xmlAttributeOverrides);
, I just don't know what it is that's wrong? This works great for dozens and dozens of classes. The reason, if I recall correctly for the complexity, is this allows the serializer to properly deserialize nested classes so I get full deserialization of a class. My guess is that this fails because Base64String
is a struct???
public static XmlSerializer GetSerializerFor(Type typeOfT)
{
if (!_serializers.ContainsKey(typeOfT))
{
Debug.WriteLine(string.Format("XmlSerializerFactory.GetSerializerFor(typeof({0}));", typeOfT));
var types = new List<Type> { typeOfT, typeOfT.BaseType };
foreach (var property in typeOfT.GetProperties())
{
types.Add(property.PropertyType);
}
types.RemoveAll(t => t.ToString().StartsWith("System."));
var xmlAttributeOverrides = new XmlAttributeOverrides();
foreach (var type in types)
{
if (xmlAttributeOverrides[type] != null)
continue;
var xmlAttributes = new XmlAttributes
{
XmlType = new XmlTypeAttribute
{
Namespace = "",
TypeName = GetSerializationTypeName(type)
},
Xmlns = false
};
xmlAttributeOverrides.Add(type, xmlAttributes);
}
var newSerializer = new XmlSerializer(typeOfT, xmlAttributeOverrides);
//var newSerializer = new XmlSerializer(typeOfT, xmlAttributeOverrides, types.ToArray(), new XmlRootAttribute(), string.Empty);
//var newSerializer = new XmlSerializer(typeOfT, string.Empty);
_serializers.Add(typeOfT, newSerializer);
}
return _serializers[typeOfT];
}