2010-10-05 14 views
4

J'essaie d'envoyer un message avec une propriété IEnumerable. Ai-je raison de dire que le sérialiseur NServiceBus Xml ne peut pas prendre en charge ce problème? Si je passe à l'aide d'un tableau plutôt que IEnumerable cela fonctionnera, si j'utilise le sérialiseur binaire, il fonctionne aussiNServiceBus Xml Problème de sérialisation avec les messages possédant une propriété IEnumerable <T>

Mon message ressemble à ceci

[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; } 
} 

Si le sérialiseur par défaut Xml ne peut pas répondre à cela, est-il possible de configurer un autre sérialiseur Xml tel que DataContractSerializer de la BCL?

Merci à l'avance

Pat

Répondre

6

Tout d'abord, une note que la sérialisation XML dans NServiceBus n'est pas la même chose que XML .NET sérialisation. La variante .NET consiste à pouvoir personnaliser le XML résultant avec des attributs pour produire des schémas XML spécifiques, potentiellement pour l'interopérabilité avec d'autres langages. Le sérialiseur XML NServiceBus est un très petit sous-ensemble de fonctionnalités conçues pour transférer les schémas de messages prédéfinis vers et depuis XML le plus efficacement possible. Bien que le résultat de la sérialisation de NServiceBus soit lisible (ce qui est vraiment agréable lors de l'inspection des files d'erreurs), il ne prend pas en charge tous les types ou toutes les options de formatage. Il fait ce qu'il fait et il le fait plutôt bien. Cela dit, le problème avec un IEnumerable est qu'il pourrait y avoir tellement de choses. Il pourrait, en réalité, être simplement un tableau, mais il pourrait aussi bien s'agir d'une expression Linq-to-SQL complexe qui invoquera une requête de base de données. Pour sérialiser IEnumerable, vous devez le représenter comme une collection (une liste ou un tableau) de toute façon, vous devez donc énumérer les éléments. Quand feriez-vous exactement cela? Quels problèmes avec les transactions cela pourrait-il soulever? C'est pourquoi le sérialiseur XML NServiceBus, soucieux des performances, ne dérange pas.

Un message NServiceBus est simplement un contrat pour transmettre des données de message. Je suggère simplement d'utiliser un tableau. Il est assez facile de convertir un IEnumerable en un tableau (avec la méthode d'extension ToArray()) et en arrière (avec la méthode d'extension AsEnumerable()) alors pourquoi est-il important de l'avoir en IEnumerable?

Pour répondre pleinement à votre question, il devrait être possible d'échanger le sérialiseur en écrivant votre propre classe qui implémente IMessageSerializer et en configurant le framework d'injection de dépendance pour l'utiliser, mais je ne l'ai pas essayé moi-même. Ce serait tout à fait une entreprise, puisque chaque point de terminaison devrait utiliser ce même sérialiseur, et vous devrez également faire des modifications pour utiliser le Distributeur, TimeoutManager, Gateway, etc.

Editer: A noté cette question a été permuté sur le groupe NSB à http://tech.groups.yahoo.com/group/nservicebus/message/8838

+1

I Vous savez que la sérialisation Xml est prise en charge dans le type NServiceBus.Serializers.XML.MessageSerializer. La raison pour laquelle nous voulions aller avec IEnumerable était que nos messages seraient immuables (Notez les ensembles privés dans les propriétés). Si j'utilise un tableau, les messages ne sont plus immuables car je peux modifier un élément de tableau en utilisant l'indexeur et en remplaçant l'élément existant par un nouvel élément. Comme vous le faites remarquer l'évaluation retardée avec IEnumerable est un problème. Merci Pat – pmcgrath

+1

@pmcgrath: Je me suis battu avec l'argument mutable/immutable tout entier, mais je suis arrivé à la conclusion que cela n'aurait pas d'importance si vos messages sont modifiables car ils ne devraient pas être vos objets de domaine. Le récepteur doit prendre le message et le reconstruire en une entité de domaine qui est pertinente pour la ou les tâches qu'il est sur le point de préformer. –

+0

Cela semble être un problème avec une propriété Collection sans setter également, que XmlSerializer prend en charge. – TrueWill

3

est-il possible de configurer un sérialiseur Xml alternatif tel que DataContractSerializer de la BCL?

Oui, c'est certainement possible. Nous utilisons le DataContractSerializer pour certains de nos services. Pour que cela fonctionne, vous devez implémenter l'interface IMessageSerialzer, qui effectue le travail, puis enregistrez ce sérialiseur avec NServiceBus pendant la chaîne de méthode NServiceBus.Configure.

Voici le code pour le sérialiseur de message. C'est assez 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(); 
     } 
    } 
} 

Pour brancher ce, vous pouvez utiliser une méthode d'extension telles que les suivantes:

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; 
    } 
} 

Ce serait invoqué lorsque vous configurez NServiceBus comme ceci:

NServiceBus.Configure 
    // Other configuration... 
    .WcfSerializer() 
    // Other configuration... 
    .CreateBus() 
    .Start(); 
+0

Ceci est un excellent exemple de la façon d'implémenter un sérialiseur personnalisé. Gardez à l'esprit que cela se décompose rapidement si vous essayez d'utiliser le mode discret, car il n'y a alors aucune interface 'IMessage' que tout implémente, donc l'analyse des types connus échouera. –

+0

@DavidBoike C'est un excellent point. Bien qu'activer ceci en mode discret est simplement un cas de remplacer l'expression dans la méthode 'Where' par la même expression que celle utilisée pour configurer le mode discret, n'est-ce pas? –

+0

Oui, je pense que ce serait prendre soin du problème, juste basé sur le «compilateur dans le cerveau». Cela dit, je pense qu'essayer d'utiliser le WCF DataContractSerializer en premier lieu est un anti-pattern et devrait être évité. –