views:

93

answers:

1

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];
}
A: 

I suspect you don't need this struct at all, check out http://stackoverflow.com/questions/1405051/xmlserializer-base64-encode-a-string-member.

Daniel Renshaw
I get the error "This XmlWriter does not support base64 encoded data."
Chad
Perhaps you could share a bit more code: where the Base64String is used and the code used to serialize the object graph?
Daniel Renshaw
@Daniel Renshaw, added test code
Chad