Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NServiceBus Xml Serialization issue with messages that have an IEnumerable<T> property

I'm trying to send a message with an IEnumerable property, am i correct that the NServiceBus Xml serializer cannot support this ? If i switch to using an array rather than IEnumerable it will work, if i use the binary serializer it also works

My message look like this

[Serializable]
public class Parent : IMessage
{
  public string Identifier { get; private set; }
  public IEnumerable<Child> Children { get; private set; }

  public Parent(string identifier, IEnumerable<Child> children)
  {
    this.Identifier = identifier;
    this.Children = children;
  }
}

[Serializable]
public class Child
{
  public string Identifier { get; private set; }
}  

If the default Xml serializer cannot cater for this, is there any way to configure an alternative Xml serializer such as the BCL's DataContractSerializer ?

Thanks in advance

Pat

like image 316
pmcgrath Avatar asked Jan 22 '23 08:01

pmcgrath


2 Answers

First, a note that XML serialization in NServiceBus is not the same thing as .NET XML Serialization. The .NET variant is all about being able to tailor the resultant XML with attributes to produce specific XML schemas, potentially for interoperability with other languages. The NServiceBus XML serializer is an extremely small subset of functionality tailored to transfer predefined message schemas to and from XML as efficiently as possible.

While the result of NServiceBus serialization is readable (which is really nice when inspecting error queues) it doesn't support all types or all formatting options. It does what it does and it does it pretty well.

That said, the problem with an IEnumerable is that it could be so many things. It could, in reality, just be an array, but it could just as easily be a complex Linq-to-SQL expression that will invoke a database query. In order to serialize the IEnumerable, you'd have to represent it as a collection (a list or array) anyway, so you have to enumerate the items. When exactly would you do that? What problems with transactions might that raise? That's why the performance-conscious NServiceBus XML serializer doesn't bother.

An NServiceBus message is just a contract to pass message data. I would suggest just using an array. It's easy enough to convert an IEnumerable to an array (with the ToArray() extension method) and back (with the AsEnumerable() extension method) so why is it important to have it as an IEnumerable?

To fully answer your question, it should be possible to swap out the serializer by writing your own class that implements IMessageSerializer and configuring the dependency injection framework to use it, but I have not tried this myself. It would be quite an undertaking, since every single endpoint would have to utilize this same serializer, and you'd also have to make modifications in order to use the Distributor, TimeoutManager, Gateway, etc.

Edit: Noticed this question was cross-posted on NSB group at http://tech.groups.yahoo.com/group/nservicebus/message/8838

like image 116
David Boike Avatar answered Apr 26 '23 12:04

David Boike


is there any way to configure an alternative Xml serializer such as the BCL's DataContractSerializer ?

Yes, that is certainly possible. We use the DataContractSerializer for some of our services. To get this working, you need to implement the IMessageSerialzer interface, which does the work, then register that serializer with NServiceBus during the NServiceBus.Configure method chain.

Here's the code for the message serializer. It is pretty straightforward.

public class WcfMessageSerializer : IMessageSerializer
{
    private readonly IList<Type> knownTypes = new List<Type>();

    public IList<Type> MessageTypes
    {
        get { return knownTypes; }
        set
        {
            knownTypes.Clear();
            foreach (var type in value)
            {
                if (!type.IsInterface && typeof(IMessage).IsAssignableFrom(type)
                    && !knownTypes.Contains(type))
                {
                    knownTypes.Add(type);
                }
            }
        }
    }

    public void Serialize(IMessage[] messages, Stream stream)
    {
        var xws = new XmlWriterSettings
        {
            ConformanceLevel = ConformanceLevel.Fragment
        };
        using (var xmlWriter = XmlWriter.Create(stream, xws))
        {
            var dcs = new DataContractSerializer(typeof(IMessage), knownTypes);
            foreach (var message in messages)
            {
                dcs.WriteObject(xmlWriter, message);
            }
        }
    }

    public IMessage[] Deserialize(Stream stream)
    {
        var xrs = new XmlReaderSettings
        {
            ConformanceLevel = ConformanceLevel.Fragment
        };
        using (var xmlReader = XmlReader.Create(stream, xrs))
        {
            var dcs = new DataContractSerializer(typeof(IMessage), knownTypes);
            var messages = new List<IMessage>();
            while (false == xmlReader.EOF)
            {
                var message = (IMessage)dcs.ReadObject(xmlReader);
                messages.Add(message);
            }
            return messages.ToArray();
        }
    }
}

In order to plug this in, you could use an extension method such as the following:

public static class ConfigureWcfSerializer
{
    public static Configure WcfSerializer(this Configure config)
    {
        var messageTypes = Configure.TypesToScan
            .Where(t => typeof(IMessage).IsAssignableFrom(t))
            .ToList();

        config.Configurer
            .ConfigureComponent<WcfMessageSerializer>(ComponentCallModelEnum.Singleton)
            .ConfigureProperty(ms => ms.MessageTypes, messageTypes);

        return config;
    }
}

This would be invoked when you configure NServiceBus like so:

NServiceBus.Configure
    // Other configuration...
    .WcfSerializer()
    // Other configuration...
    .CreateBus()
    .Start();
like image 24
Damian Powell Avatar answered Apr 26 '23 13:04

Damian Powell