Question

I'm attempting to use a generic serialization surrogate to control the naming of fields that are serialized through use of the NetDataContractSerializer. The concept is working well so far except for that I'm running into a problem any time I encounter a field that holds a reference to a collection within the GetObjectData method in that calling AddValue and feeding it my collection type seems to result in no items in the collection being serialized. Is there something special I need to do for collections such as arrays or lists?

I've added a copy of my complete code with an example below in hopes that somebody may be able to point me in the right direction.

static void Main()
{
    var obj = new Club(
        "Fight Club", 
        Enumerable.Range(1, 3).Select(i => new Member() { Age = i, Name = i.ToString() }));

    var streamingContext = new StreamingContext(StreamingContextStates.Clone);
    //var serializer = new NetDataContractSerializer(streamingContext);
    var serializer = new NetDataContractSerializer(streamingContext, int.MaxValue, false, System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Full, new MySurrogateSelector(streamingContext));
    var ms = new MemoryStream();
    serializer.Serialize(ms, obj);

    Console.WriteLine("Before serializing: \n{0}\n", obj);

    ms.Position = 0;
    var xml = XElement.Load(ms);
    Console.WriteLine("Serialized object: \n\n{0}\n", xml);

    ms.Position = 0;
    var deserializedObj = serializer.Deserialize(ms);

    Console.WriteLine("After deserializing: \n{0}\n", deserializedObj);
    Console.ReadKey();
}

public class MySurrogateSelector : ISurrogateSelector
{
    private ISerializationSurrogate _surrogate = new BackingFieldSerializationSurrogate();

    public MySurrogateSelector(StreamingContext streamingContext)
    {
    }

    public void ChainSelector(ISurrogateSelector selector)
    {
        throw new System.NotImplementedException();
    }

    public ISurrogateSelector GetNextSelector()
    {
        throw new System.NotImplementedException();
    }

    public ISerializationSurrogate GetSurrogate(System.Type type, StreamingContext context, out ISurrogateSelector selector)
    {
        selector = null;
        return _surrogate;
    }
}


public class BackingFieldSerializationSurrogate : ISerializationSurrogate
{
    private static Regex _backingFieldRegex = new Regex("<(.*)>k__BackingField", RegexOptions.Singleline | RegexOptions.Compiled);

    public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
    {
        info.SetType(obj.GetType());
        var fields = obj.GetType().GetFields(BindingFlags.FlattenHierarchy | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
        foreach (var field in fields)
        {
            string propertyName;
            info.AddValue(
                TryGetPropertyNameFromBackingField(field.Name, out propertyName) ? GenerateCustomBackingFieldName(propertyName) :
                field.Name,
                field.GetValue(obj),
                field.FieldType);
        }
    }

    public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
    {
        var fields = obj.GetType().GetFields(BindingFlags.FlattenHierarchy | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);

        foreach (var field in fields)
        {
            string propertyName;
            field.SetValue(
                obj,
                info.GetValue(
                TryGetPropertyNameFromBackingField(field.Name, out propertyName) ? GenerateCustomBackingFieldName(propertyName) :
                field.Name,
                field.FieldType));
        }

        return obj;
    }

    private static bool TryGetPropertyNameFromBackingField(string fieldName, out string propertyName)
    {
        var match = _backingFieldRegex.Match(fieldName);
        if (match.Groups.Count == 1)
        {
            propertyName = null;
            return false;
        }

        propertyName = match.Groups[1].Value;
        return true;
    }

    private static string GenerateCustomBackingFieldName(string propertyName)
    {
        return "_" + propertyName + "_k__BackingField";
    }
}

[Serializable]
public class Member
{
    public int Age { get; set; }

    public string Name { get; set; }

    public override string ToString()
    {
        return string.Format("Member {0}, Age: {1}", Name, Age);
    }
}

[Serializable]
public class Club
{
    public string Name { get; set; }

    public IList<Member> Members { get; private set; }

    public Club(string name, IEnumerable<Member> members)
    {
        Name = name;
        //Members = new List<Member>(members);
        Members = members.ToArray();
    }

    public override string ToString()
    {
        if (Members == null)
        {
            return Name;
        }

        return Name + ", Total members: " + Members.Count + "\n\t" +  string.Join("\n\t", Members);
    }
}

If anyone is curious the reason that I'm trying this technique is to get around an issue which causes internally handled XmlException of which have me concerned about performance implications.

System.Xml.XmlException occurred
  HResult=-2146232000
  Message=Name cannot begin with the '<' character, hexadecimal value 0x3C.
  Source=System.Xml
  LineNumber=0
  LinePosition=1
  StackTrace:
       at System.Xml.XmlConvert.VerifyNCName(String name, ExceptionType exceptionType)
  InnerException: 
Was it helpful?

Solution

Sorry I didn't give anybody else much of a chance to respond but I found out what seems to have been the underlying problem. After looking at the base SurrogateSelector class I realized that perhaps I was not returning the right kind of surrogate for a collection and that led me down a few paths until I eventually just tried detecting arrays and returning null from GetSurrogate in those cases. And like magic everything seemed to begin working as expected.

public ISerializationSurrogate GetSurrogate(Type type, StreamingContext context, out ISurrogateSelector selector)
{
    if (type.IsArray)
    {
        selector = null;
        return null;
    }

    selector = null;
    return _surrogate;
}

At first I was a bit surprised that this also seemed to correct other types of generic collections like List that I was also having issues with but essentially it appears that these collections are still broken down into arrays and direct object references so perhaps arrays are the only special case I was missing. I hope this may help somebody else who may come across similar issues dealing with the complexity of using the serialization surrogate classes.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top