WSDL Merger

O contrato WSDL gerado automaticamente pelos serviços desenvolvidos com WCF é regra geral, composto por 4 ficheiros: 2 .wsdl e dois .xsd, cada um deles disponível num url diferente. Uma necessidade que tenho sentido é a de disponibilizar o contrato do serviço em apenas um ficheiro, de modo a ser mais fácil distribuir e arquivar.

Depois de fazer o merge manual destes ficheiros algumas vezes, cheguei à conclusão que faz sentido usar um utilitário que faça a mesma tarefa por mim. O código que se segue não será provavelmente bullet-proof para todo e qualquer .wsdl que contenha referências, mas para os serviços que desenvolvi, funciona lindamente:

using System;
using System.Collections.Generic;
using System.Xml;

namespace MyCompany.ApplicationBlocks.Metadata
{
    public class WsdlMerger
    {
        private static XmlNamespaceManager _namespaceManager = null;

        public static string Merge(string mainWsdlUrlAddress)
        {
            _namespaceManager = new XmlNamespaceManager(new NameTable());
            _namespaceManager.AddNamespace("wsdl", "http://schemas.xmlsoap.org/wsdl/");
            _namespaceManager.AddNamespace("xsd", "http://www.w3.org/2001/XMLSchema");

            XmlDocument wsdl = Load(mainWsdlUrlAddress);
            ResolveReferences(wsdl, wsdl);

            Clean(wsdl);

            return wsdl.OuterXml;
        }

        private static XmlDocument Load(string urlAddress)
        {
            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.Load(urlAddress);

            return xmlDocument;
        }

        private static void ResolveReferences(XmlDocument mainDocument, XmlDocument currentDocument)
        {
            XmlNodeList wsdlImports = currentDocument.SelectNodes("/wsdl:definitions/wsdl:import", _namespaceManager);

            foreach (XmlNode wsdlImport in wsdlImports)
            {
                string wsdlAddress = wsdlImport.SelectSingleNode("@location").InnerText;

                XmlDocument wsdl = Load(wsdlAddress);
                ImportWsdlNode(mainDocument, wsdl, wsdlImport);
                ResolveReferences(mainDocument, wsdl);
            }

            XmlNodeList xsdImports = currentDocument.SelectNodes("/wsdl:definitions/wsdl:types/xsd:schema/xsd:import", _namespaceManager);

            foreach (XmlNode xsdImport in xsdImports)
            {
                string xsdAddress = xsdImport.SelectSingleNode("@schemaLocation").InnerText;

                XmlDocument xsd = Load(xsdAddress);
                ImportXsdNode(mainDocument, xsd, xsdImport);
                ResolveReferences(mainDocument, xsd);
            }
        }

        private static void ImportWsdlNode(XmlDocument mainDocument, XmlDocument currentWsdlDocument, XmlNode wsdlImport)
        {
            XmlNodeList nodesToImport = currentWsdlDocument.SelectNodes("/wsdl:definitions/*[not(self::wsdl:types) and not(self::wsdl:import)]", _namespaceManager);

            foreach (XmlNode nodeToImport in nodesToImport)
            {
                XmlNode referenceNode = mainDocument.SelectSingleNode("/wsdl:definitions/wsdl:types", _namespaceManager);

                if (referenceNode == null)
                {
                    referenceNode = mainDocument.SelectSingleNode("/wsdl:definitions/wsdl:import", _namespaceManager);
                }

                string wsdlTargetNamespace = mainDocument.SelectSingleNode("/wsdl:definitions/wsdl:import/@namespace", _namespaceManager).Value;

                if (wsdlTargetNamespace != null)
                {
                    XmlNode targetNamespaceAttribute = mainDocument.SelectSingleNode("wsdl:definitions/@targetNamespace", _namespaceManager);

                    if (targetNamespaceAttribute != null)
                    {
                        targetNamespaceAttribute.Value = wsdlTargetNamespace;
                    }
                }

                XmlNode tnsPrefixNamespace = mainDocument.SelectSingleNode("/wsdl:definitions/namespace::tns", _namespaceManager);

                if (tnsPrefixNamespace != null)
                {
                    tnsPrefixNamespace.Value = wsdlTargetNamespace;
                }

                XmlNode importedNode = mainDocument.ImportNode(nodeToImport, true);
                mainDocument.SelectSingleNode("/wsdl:definitions", _namespaceManager).InsertAfter(importedNode, referenceNode);
            }
        }

        private static void ImportXsdNode(XmlDocument mainDocument, XmlDocument currentXsdDocument, XmlNode xsdImport)
        {
            List<string> namespacesToExclude = new List<string>();
            namespacesToExclude.Add("http://schemas.microsoft.com/2003/10/Serialization/");
           
            XmlNode targetNamespace = currentXsdDocument.SelectSingleNode("/xsd:schema/@targetNamespace", _namespaceManager);

            if (targetNamespace != null && !namespacesToExclude.Contains(targetNamespace.Value))
            {
                XmlNodeList nodesToImport = currentXsdDocument.SelectNodes("/xsd:schema", _namespaceManager);

                foreach (XmlNode nodeToImport in nodesToImport)
                {
                    XmlNode importedNode = mainDocument.ImportNode(nodeToImport, true);
                    mainDocument.SelectSingleNode("/wsdl:definitions/wsdl:types", _namespaceManager).PrependChild(importedNode);
                }
            }
        }

        private static void Clean(XmlDocument wsdl)
        {
            XmlNode wsdlImportNode = wsdl.SelectSingleNode("/wsdl:definitions/wsdl:import", _namespaceManager);

            if (wsdlImportNode != null)
            {
                wsdl.SelectSingleNode("/wsdl:definitions", _namespaceManager).RemoveChild(wsdlImportNode);
            }
        }
    }
}

Aqui está um exemplo de consumo:

using System;
using System.IO;

using MyCompany.ApplicationBlocks.Metadata;

namespace WSDLMerger
{
    class Program
    {
        static void Main()
        {
            string fullWsdl = WsdlMerger.Merge("http://mycompany/myservice.svc?wsdl");
            File.WriteAllText(@"C:\FullWSDL.wsdl", fullWsdl);
        }
    }
}

Podem facilmente ver que o código é susceptível de várias melhorias. A ideia foi disponibilizá-lo a quem possa ser útil desde já.



Published Tuesday, February 06, 2007 5:57 PM by António Cruz
Filed under , , , ,

Comments

 

Paulo Morgado said:

O António Cruz disponibilizou a sua ferramenta para juntar num só ficheiro as definições de um WSDL distribuído

February 8, 2007 10:38 AM
 

Paulo Morgado said:

António Cruz posted his tool to merge WSDL definitions into a single WSDL file.

February 8, 2007 10:40 AM
 

Hitchhiker guide to enterprise development said:

Working with Web Services is supposed to be interoperable, but that's not quite 100% true, there are

May 22, 2007 4:19 PM
 

Paulo Morgado said:

O António Cruz disponibilizou a sua ferramenta para juntar num só ficheiro as definições de um WSDL distribuído

June 30, 2009 1:24 AM