Kader Yildirim
A la découverte de BizTalk Server 2006 1/3
Développer un désassembleur pour BizTalk Server 2006 R2
Par Kader Yildirim publié le 02/03/2008 à 21:58
 
La seconde étape consiste à développer le désassembleur qui va héberger la classe développée précédemment. Pour avoir plus de détails sur les types de pipelines je vous conseille ce site msdn.

Vous pouvez créer un tel composant à la main en partant d'un projet BizTalk vierge ou bien vous pouvez installer le Biztalk Server Pipeline Component Wizard.
Une fois l'installation effectuée un nouveau projet nommé Biztalk Server Pipeline Component Project est accessible depuis Visual Studio 2005. Dès que l'utilisateur choisi ce type de projet un wizard se lance et lui demande si le composant va être utilisé par un pipeline de réception ou d'émission et à quelle étape du traitement il est susceptible d'intervenir. Dans notre cas il va être utilisé en réception (dans le troisième article nous verrons le cas d'un composant utilisé en émission) à l'étape du désassemblage (ces étapes vont être décrites un peu plus loin) :
 
/content/af013d85-430c-482b-8450-a88c26dc0881/PipelineDecomposerWizard2.PNG
 
La dernière étape du wizard consiste à préciser les paramètres qui seront accessibles aux utilisateurs de notre composant. Nous allons créer les variables ci-dessous et verrons leur utilité au fur et à mesure de notre avancée dans cet article et ceux à venir :
 
/content/af013d85-430c-482b-8450-a88c26dc0881/PipelineDecomposerWizard4.PNG
 
Toutes les variables sont simples (booléen, chaînes de caractères) sauf Schemas qui est de type SchemaList. Cette classe représente la liste des schémas donnés en paramètre par l'utilisateur et qui vont être utilisés pour extraire les fragments du document XML.
La classe SchemaList de l'assembly Microsoft.BizTalk.Component.Utilities.dll contient des éléments de type Schema et non System.Xml.XmlSchema que nous avons l'habitude d'utiliser sous .Net. Nous verrons un peu plus loin comment utiliser ces classes.

A la fin de toutes les étapes le wizard lève une exception mais nous pouvons l'ignorer car elle n'est nullement pénalisante pour la suite.
Si on regarde le code généré, le wizard créé essentiellement une classe qui implémente les interfaces suivantes :

[ComponentCategory(CategoryTypes.CATID_PipelineComponent)]

    [System.Runtime.InteropServices.Guid("a06fc47a-8349-4ed0-a3b3-e2de3f13beaa")]

    [ComponentCategory(CategoryTypes.CATID_DisassemblingParser)]

    public class XmlDecomposer :

        Microsoft.BizTalk.Component.Interop.IDisassemblerComponent,

        IBaseComponent, IPersistPropertyBag, IComponentUI

    {

  • IBaseComponent : contient trois propriétés qui donnent des informations sur le nom, la version et la description du composant.
  • IComponentUI : a deux fonctions dont une pour valider la configuration du composant et une autre qui donne l'icône. Cette interface est notamment utilisée par le designer de Visual Studio.
  • IPersistPropertyBag : permet entre autres de sauvegarder et de recharger les valeurs des paramètres donnés au composant à l'aide d'un PropertyBag.
  • IDisassemblerComponent : la méthode Disassemble est responsable du traitement du message reçu. Pour un message en entrée elle génère un ou plusieurs messages en sortie. La fonction GetNext retourne ces messages un par un et elle est appelée par BizTalk jusqu'à ce qu'elle retourne null.
Les methodes Load et Save sont déjà implémentées pour les types simples (chaînes de caractères par exemple) mais un #error avec un message adéquat est créé pour les types complexes tels que SchemaList :

public virtual void Save(Microsoft.BizTalk.Component.Interop.IPropertyBag pb, bool fClearDirty, bool fSaveAllProperties)

        {

            this.WritePropertyBag(pb, "EnforeValidatation", this.EnforeValidatation);

            this.WritePropertyBag(pb, "MessagePropertyName", this.MessagePropertyName);

            this.WritePropertyBag(pb, "PartPropertyName", this.PartPropertyName);

            this.WritePropertyBag(pb, "MessagePropertyNameSpace", this.MessagePropertyNameSpace);

            this.WritePropertyBag(pb, "PartPropertyNameSpace", this.PartPropertyNameSpace);

#error please implement IPersistPropertyBag.Save for property "Schemas"

        }

La méthode Disassemble retourne par défaut le message reçu en entrée. C'est là que nous allons intervenir par la suite. Vous noterez que cette méthode ajoute le message dans une queue qui est parcourue dans la méthode GetNext. Ainsi, si nous avons plusieurs messages, il suffit de les insérer dans cette queue afin qu'ils soient dépilés plutard :

public void Disassemble(Microsoft.BizTalk.Component.Interop.IPipelineContext pc,

                                Microsoft.BizTalk.Message.Interop.IBaseMessage inmsg)

        {

            //

            // TODO: implement message retrieval logic

            //

            _msgs.Enqueue(inmsg);

        }

Si vous commentez les #error des méthodes Load et Save et que vous essayez de compiler ce projet alors cela échoue car il manque une référence à l'assembly Microsoft.BizTalk.Component.Utilities.dll qui se trouve dans le GAC. Si comme pour moi cette dll n'est pas visible dans la liste quand on fait Add Reference dans Visual Studio il est possible de l'ajouter en pointant l'assembly comme suit :
%WINDOWS%\assembly\GAC_MSIL\Microsoft.BizTalk.Component.Utilities\3.0.1.0__31bf3856ad364e35\Microsoft.BizTalk.Component.Utilities.dll :
 
/content/af013d85-430c-482b-8450-a88c26dc0881/AddRef.PNG
 
Pour implémenter la persistance des SchemaList nous allons tout d'abord écrire deux fonctions qui permettent de sérialiser/désérialiser un Schema vers/depuis une chaîne de caractères. Pour cela nous allons nous appuyer sur le fait que cette classe est marquée par l'attribut [Serializable] :

public string Convert(Schema s)

            {

                using (MemoryStream ms = new MemoryStream())

                {

                    BinaryFormatter f = new BinaryFormatter();

                    f.Serialize(ms, s);

                    return System.Convert.ToBase64String(ms.ToArray());

                }

            }

 

            public Schema Convert(string s)

            {

                using (MemoryStream ms = new MemoryStream(System.Convert.FromBase64String(s)))

                {

                    BinaryFormatter f = new BinaryFormatter();

                    return (Schema)f.Deserialize(ms);

                }

            }

Etant donné que la classe SchemaList n'est pas sérialisable nous allons implémenter la persistance manuellement en nous appuyant que ce que nous avons fait précédemment. Ainsi les opérations de sérialisation et de désérialisation de cette classe deviennent :

public string Convert(SchemaList sl)

        {

            StringBuilder sb = new StringBuilder();

 

            int len = sl.Count;

            for (int i = 0; i < len; ++i)

            {

                sb.Append(SchemaUtil.Instance.Convert(sl[i]));

                if ((i + 1) != len) sb.Append(r_Separator);

            }

 

            return sb.ToString();

        }

 

        public SchemaList Convert(string s)

        {

            SchemaList sl = new SchemaList();

 

            string[] schemaList = s.Split(new char[] { r_Separator });

            foreach (string schema in schemaList)

                sl.Add(SchemaUtil.Instance.Convert(schema));

 

            return sl;

        }

Grâce à ces changements nous pouvons remplacer le #error du Load par :

val = this.ReadPropertyBag(pb, "Schemas");

            if (val != null)

            {

                Schemas = SchemaListUtil.Instance.Convert(val.ToString());

            }

De même pour le Save :

this.WritePropertyBag(pb, "Schemas", SchemaListUtil.Instance.Convert(Schemas));

Le schéma peut provenir directement de l'assembly appelant notre composant ou bien indirectement s'il provient d'une autre assembly référencée par le projet de test.Un schéma est représenté par la classe Microsoft.Biztalk.Utilities.Schema qui contient, entre autres, le nom du schéma et de l'assembly qui l'héberge.Si le schéma est directement hébergé par l'assembly appelant notre composant alors la valeur de sa propriété contenant le nom de l'assembly est vide :
 
/content/af013d85-430c-482b-8450-a88c26dc0881/SchemaNullAssemblyName.PNG
 
Par contre si le schéma est hébergé dans une assembly autre que l'appelant direct du composant alors cette propriété est valorisée correctement :
 
/content/af013d85-430c-482b-8450-a88c26dc0881/SchemaNotNullAssemblyName.PNG
 
Ainsi il vaut mieux héberger nos schémas dans un projet dédié.

Par ailleurs la classe Microsoft.Biztalk.Utilities.Schema ne possède pas de méthode ou de propriété retournant le contenu du XSD.
Pour récupérer ce contenu le plus simple est de se baser sur la méthode GetSchemaCollection de l'interface IDocumentSpec . Cette dernière est elle-même accessible via la méthode GetDocumentSpecByName d'IPipelineContext :

foreach (Schema schema in schemas)

                {

                    if (s.ContainsKey(schema))

                        continue;

 

                    IDocumentSpec documentSpecByName = pc.GetDocumentSpecByName(schema.SchemaName);

                    s.Add(schema, documentSpecByName.GetSchemaCollection());

                }

Une autre méthode (plus lourde) se base sur le fait que chaque XSD est compilée en une classe qui hérite de SchemaBase. Ainsi il est possible de récupérer son contenu par réflexion en invoquant la propriété XmlContent. Cette classe provenant de l'assembly Microsoft.XLANGs.BaseTypes.dll, il faut penser à l'ajouter dans les références de notre projet.

Dictionary<string, Assembly> Assemblies = new Dictionary<string, Assembly>();

 

                foreach (Schema schema in schemas)

                {

                    if (s.ContainsKey(schema))

                        continue;

 

                    if (string.IsNullOrEmpty(schema.AssemblyName)) continue;

 

                    if (!Assemblies.ContainsKey(schema.AssemblyName))

                        Assemblies.Add(schema.AssemblyName, Assembly.Load(schema.AssemblyName));

 

                    SchemaBase baseSchema = (SchemaBase)

                        Activator.CreateInstance(Assemblies[schema.AssemblyName].GetType(schema.DocSpecName));

 

                    s.Add(schema, baseSchema.SchemaCollection);

                }

Le code de désassemblage suit la logique suivante :
  1. Récupération du message d'entrée.
  2. Recherche des fragments que l'on est susceptible de reconnaître.
  3. Construction du nouveau message multi-part.
Il ne nous reste plus qu'à implémenter la dernière étape.

Pour construire un nouveau message ou une nouvelle part IPipelineContext fournit une méthode nommée GetMessageFactory pour créer une factory de messages :

public void Disassemble(Microsoft.BizTalk.Component.Interop.IPipelineContext pc,

                                Microsoft.BizTalk.Message.Interop.IBaseMessage inmsg)

        {

            // not supported otherwise

            if (inmsg.PartCount != 1)

                return;

 

            Dictionary<Guid, Pair<Schema, Stream>> dvn = XmlValidableNodesFinder.Instance.Find(

                inmsg.BodyPart.GetOriginalDataStream(), this.Schemas);

 

            if (EnforeValidatation)

            {

                Dictionary<Schema, XmlSchemaCollection> s =

                    XmlValidableNodesFinder.Instance.Convert(pc, this.Schemas);

                XmlValidableNodesFinder.Instance.ValidateXmlDocumentsAgainstSchema(dvn, s);

            }

 

            IBaseMessageFactory bmf = pc.GetMessageFactory();

            IBaseMessage bm = bmf.CreateMessage();

            //! body part must be added first !

            bm.AddPart(Guid.Empty.ToString(), BuildMessagePart(bmf, dvn[Guid.Empty], pc), true);

 

            //add validated sub xml as new parts if any

            foreach (KeyValuePair<Guid, Pair<Schema, Stream>> kvp in dvn)

            {

                if (kvp.Key == Guid.Empty)

                    continue;

                bm.AddPart(kvp.Key.ToString(), BuildMessagePart(bmf, kvp.Value, pc), false);

            }

 

            bm.Context = PipelineUtil.CloneMessageContext(inmsg.Context);

            if (!string.IsNullOrEmpty(MessagePropertyName) &&

                MessagePropertyName != r_DefaultMessagePropertyName &&

                !string.IsNullOrEmpty(MessagePropertyNameSpace) &&

                MessagePropertyNameSpace != r_DefaultMessagePropertyNameSpace)

            {

                bm.Context.Promote(this.MessagePropertyName, this.MessagePropertyNameSpace, true);

            }

            _msgs.Enqueue(bm);

        }

Il ne faut pas oublier de dupliquer dans le contexte du nouveau message celui du message donné en entrée. En effet le contexte contient toutes les informations de routage du message utile à BizTalk :

bm.Context = PipelineUtil.CloneMessageContext(inmsg.Context);

MessagePropertyName est promue afin de marquer tous les messages que nous avons traités avec notre désassembleur. La promotion permet de rendre cette propriété accessible à BizTalk qui peut l'utiliser dans le traitement et le routage des messages par exemple.

A chaque fragment XML trouvé on associe le XSD qui l'a validé en l'écrivant dans les propriétés du part. Ceci sera utile pour router les parts de l'orchestration maître vers les sous orchestrations :

rivate IBaseMessagePart BuildMessagePart(IBaseMessageFactory bmf, Pair<Schema, Stream> pair, IPipelineContext pc)

        {

            if (null == bmf || null == pair.Right)

                return null;

 

            IBaseMessagePart bmp = bmf.CreateMessagePart();

            if (!string.IsNullOrEmpty(PartPropertyName) &&

                PartPropertyName != r_DefaultPartPropertyName &&

                !string.IsNullOrEmpty(PartPropertyNameSpace) &&

                PartPropertyNameSpace != r_DefaultPartPropertyNameSpace)

            {

                bmp.PartProperties.Write(this.PartPropertyName, this.PartPropertyNameSpace, pair.Left.DocSpecName);

            }

 

            bmp.Data = pair.Right;

            pc.ResourceTracker.AddResource(pair.Right);

            bmp.Data.Position = 0;

            return bmp;

        }

Vous noterez au passage l'utilisation du ResourceTracker qui permet à BizTalk de connaître les ressources qu'il doit gérer.

 Commentaire - A la découverte de BizTalk Server 2006 1/3 

 Dernières Publications      

Windows Media Center et WCF : développez votre maison intelligente
  Le développement d'applications pour Windows Media Center est facilité avec l'arrivée du SDK 5.3. Même si l'on sent un modèle objet bien lourd derrière, il devient plus facile d'exposer les fonctionnalités de WMC sous la forme de services WCF.
par Frédéric Colin posté le 23/06/2008 à 08:04, lu 295 fois, #0
Notions avancées avec Biztalk Server 2006 R2
  Utilisation des notions d'interchange, corrélation et convoi avec BizTalk Server 2006 R2
par Kader Yildirim posté le 09/06/2008 à 08:04, lu 288 fois, #0
Lucene Persistence Engine pour Evaluant Universal Storage Services
  Suite à l'article de Laurent Kempé, voici un moteur de stockage pour EUSS permettant l'indexation d'entités métier avec Lucene.
par Nicolas Penin posté le 01/06/2008 à 23:38, lu 510 fois, #1
Tags: C#, Linq
XMLA Trivia : Découverte du XMLA
  Le XMLA (XML for Analysis) est un langage normalisé par plusieurs éditeurs BI pour simplifier l'accès aux données aux cubes et aux métadonnées des bases multidimensionnelles.
par Renaud Harduin posté le 25/05/2008 à 11:57, lu 523 fois, #0
Exploiter les données CSV via Linq en toute simplicité
  A partir du requêteur dynamique fourni en exemple avec Visual Studio 2008, nous allons essayer de remplir les propriétés d'un ensemble d'objets à partir des données d'un fichier CSV. Nous enrichirons aussi le parseur de nos propres fonctions.
par Frédéric Mélantois posté le 17/05/2008 à 11:41, lu 1713 fois, #0
Comment manipuler simplement le contenu d'un fichier WordML ?
  Manipulations autour du format WordML
par Fabien Reinle posté le 14/05/2008 à 23:55, lu 781 fois, #0
Polymorphisme et contrats de données WCF
  WCF aborde les types polymorphes du point de vue de la sérialisation. En effet, la connaissance du type réel potentiel est rendue nécessaire dès la description du contrat de données. Une fois n'est pas coutume, j'ai réalisé l'exemple en VB.NET.
par Frédéric Colin posté le 14/05/2008 à 08:40, lu 1881 fois, #2
A la découverte de BizTalk Server 2006 3/3
  Développer un assembleur pour BizTalk Server 2006 R2
par Kader Yildirim posté le 06/05/2008 à 13:20, lu 511 fois, #0

 Dernières Actualités      

Deep Earth – Une belle utilisation de Virtual Earth et de Silverlight Deep Zoom
  Ce projet très intéressant est disponible sur Codeplex et vous pouvez voir une démo sur la page suivante . Bien entendu comme touts les projets sur Codeplex vous avez accès aux sources....
Tags: Silverlight
Sortie de JetBrains ReSharper 4.0 en version finale, l’outil ultime pour Visual Studio
  Après plusieurs mois de Early Access Program (EAP) , JetBrains met enfin à disposition la version finale de son outil ReSharper 4.0 . Cette nouvelle version est disponible pour Visual Studio 2005 &...
Tags: Visual Studio 2008, Visual Studio 2005, Outils
BoutDuTunnel v1.4
  BoutDuTunnel est un petit logiciel de tunneling réseau écrit en C#. Il permet par exemple d’accéder aux services ftp/smtp/pop/telnet/nntp/… sur des réseaux qui n’autorisent...
BI Framework & sample sur CodePlex
  Après plusieurs demandes, je me suis décidé à déposer l'ensemble des sources et du BI Framework MS proposés dans mes articles sur codeplex : http://www.codeplex.com/BILAB Je le mettrais à jour au fil des...
Injection de code et API de profiling .NET
  Si vous êtes intéressés par la sécurité du Framework, par le reverse engineering et la manipulation/injection de code .NET et les packers, alors jetez un coup d’œil...
NDepend pour l'analyse statique de code .NET
  Pour ceux qui ne connaissent pas NDepend , il s’agit d’un outil d’analyse statique de code .NET qui permet de remonter des informations à toute une équipe de développement. NDepend aide à travailler sur...
Tags: Outils