Serialization NServiceBus Xml issue with messages that have IEnumerable <T> property
I am trying to send a message with the IEnumerable property, am I sure that the NServiceBus Xml serializer cannot support this? If I switch to using an array rather than IEnumerable, it will work, if I use a binary serializer, it also works
My message looks 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 satisfy this, is there a way to configure an alternative XML serializer such as BCL DataContractSerializer?
Thank you in advance
Pat
First, the note that XML serialization in NServiceBus is not the same as .NET Serialization. The .NET option is the ability to adapt the resulting XML with attributes to create specific XML schemas that are potentially compatible with other languages. The NServiceBus XML Serializer is an extremely small piece of functionality designed to transfer predefined message schemas to and from XML as efficiently as possible.
While the result of NServiceBus serialization is readable (which is very nice when checking error queues), it does not support all types or all formatting options. He does what he does, and does it pretty well.
However, the problem with IEnumerable is that it could be so much. In reality, it can be just an array, but just as easily can be a complex Linq-to-SQL expression that will invoke a database query. To serialize IEnumerable, you still have to present it as a collection (list or array), so you need to list the elements. When will you do it? What transaction problems might arise? Therefore, do not worry about the performance of the NServiceBus XML serializer.
An NServiceBus message is just a data transfer contract. I would suggest just using an array. It's easy enough to convert IEnumerable to an array (using the ToArray () extension method) and vice versa (using the AsEnumerable () extension method), so why is it important to have it as IEnumerable?
To fully answer your question, it should be possible to change the serializer by writing your own class that implements IMessageSerializer and sets up the dependency injection infrastructure to use it, but I have not tried it myself. This would be a prerequisite, since each endpoint would have to use this same serializer, and you will also have to make changes to use Distributor, TimeoutManager, Gateway, etc.
Edit: It is noticed that this issue was referred to the NSB group at http://tech.groups.yahoo.com/group/nservicebus/message/8838
Is there a way to set up an alternative XML serializer like BCL DataContractSerializer?
Yes, it is certainly possible. We use DataContractSerializer for some of our services. To get this working, you need to implement the IMessageSerialzer interface that does this work, and then register this serializer with NServiceBus in the NServiceBus.Configure method NServiceBus.Configure .
Here is the code for the message serializer. It is pretty simple.
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(); } } } To enable this function, you can use the extension method, for example:
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 will be called when configuring NServiceBus as follows:
NServiceBus.Configure // Other configuration... .WcfSerializer() // Other configuration... .CreateBus() .Start();