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á.