Monday, June 05, 2006

A simple WSDL object

Wsdl is great, it's like reflection for web services. As I discussed in this post, you can use it to dynamically discover and call web services at runtime. The dot net framework provides a class, 'System.Web.Services.Description.ServiceDescription' that takes a wsdl stream and provides an object model to walk the wsdl. It's really powerfull, but kinda awkward to use if you just want to get a list of web methods with their parameter names and types. Faced with this requirement last week, I put together a simple facade for the ServiceDescription class. You just create a new WebServiceInfo instance using its factory method 'OpenWsdl' and then you can iterate through all the web methods and their parameters and return parameters. Here's the NUnit test which pretty much shows how it works:
[Test]
public void ServiceDescriptionSpike()
{
   string url = "http://localhost:1297/Test/TestService.asmx";

   WebServiceInfo webServiceInfo = WebServiceInfo.OpenWsdl(new Uri(url));

   Console.WriteLine(string.Format("WebService: {0}", webServiceInfo.Url));

   foreach (WebMethodInfo method in webServiceInfo.WebMethods)
   {
       Console.WriteLine(string.Format("\tWebMethod: {0}", method.Name));
       Console.WriteLine("\t\tInput Parameters");

       foreach (Parameter parameter in method.InputParameters)
       {
           Console.WriteLine(
               string.Format("\t\t\t{0} {1}", parameter.Name, parameter.Type));   
       }

       Console.WriteLine("\t\tOutput Parameters");

       foreach (Parameter parameter in method.OutputParameters)
       {
           Console.WriteLine(
               string.Format("\t\t\t{0} {1}", parameter.Name, parameter.Type));
       }
   }
}
And here's the code. As you can see it just wraps the ServiceDescription class, you can see what a pain it is to get this information out of it.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Web.Services.Description;
using System.Net;
/// <summary>
/// Information about a web service
/// </summary>
public class WebServiceInfo
{
   WebMethodInfoCollection _webMethods = new WebMethodInfoCollection();
   Uri _url;
   static Dictionary<string, webserviceinfo=""> _webServiceInfos =
       new Dictionary<string, webserviceinfo="">();

   /// <summary>
   /// Constructor creates the web service info from the given url.
   /// </summary>
   /// <param name="url">
   private WebServiceInfo(Uri url)
   {
       if (url == null)
           throw new ArgumentNullException("url");
       _url = url;
       _webMethods = GetWebServiceDescription(url);
   }

   /// <summary>
   /// Factory method for WebServiceInfo. Maintains a hashtable WebServiceInfo objects
   /// keyed by url in order to cache previously accessed wsdl files.
   /// </summary>
   /// <param name="url">
   /// <returns></returns>
   public static WebServiceInfo OpenWsdl(Uri url)
   {
       WebServiceInfo webServiceInfo;
       if (!_webServiceInfos.TryGetValue(url.ToString(), out webServiceInfo))
       {
           webServiceInfo = new WebServiceInfo(url);
           _webServiceInfos.Add(url.ToString(), webServiceInfo);
       }
       return webServiceInfo;
   }

   /// <summary>
   /// Convenience overload that takes a string url
   /// </summary>
   /// <param name="url">
   /// <returns></returns>
   public static WebServiceInfo OpenWsdl(string url)
   {
       Uri uri = new Uri(url);
       return OpenWsdl(uri);
   }

   /// <summary>
   /// Load the WSDL file from the given url.
   /// Use the ServiceDescription class to walk the wsdl and create the WebServiceInfo
   /// instance.
   /// </summary>
   /// <param name="url">
   private WebMethodInfoCollection GetWebServiceDescription(Uri url)
   {
       UriBuilder uriBuilder = new UriBuilder(url);
       uriBuilder.Query = "WSDL";

       WebMethodInfoCollection webMethodInfos = new WebMethodInfoCollection();

       HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(uriBuilder.Uri);
       webRequest.ContentType = "text/xml;charset=\"utf-8\"";
       webRequest.Method = "GET";
       webRequest.Accept = "text/xml";

       ServiceDescription serviceDescription;

       using (System.Net.WebResponse response = webRequest.GetResponse())
       using (System.IO.Stream stream = response.GetResponseStream())
       {
           serviceDescription = ServiceDescription.Read(stream);
       }

       foreach (PortType portType in serviceDescription.PortTypes)
       {
           foreach (Operation operation in portType.Operations)
           {
               string operationName = operation.Name;
               string inputMessageName = operation.Messages.Input.Message.Name;
               string outputMessageName = operation.Messages.Output.Message.Name;

               // get the message part
               string inputMessagePartName =
                   serviceDescription.Messages[inputMessageName].Parts[0].Element.Name;
               string outputMessagePartName =
                   serviceDescription.Messages[outputMessageName].Parts[0].Element.Name;

               // get the parameter name and type
               Parameter[] inputParameters = GetParameters(serviceDescription, inputMessagePartName);
               Parameter[] outputParameters = GetParameters(serviceDescription, outputMessagePartName);

               WebMethodInfo webMethodInfo = new WebMethodInfo(
                   operation.Name, inputParameters, outputParameters);
               webMethodInfos.Add(webMethodInfo);
           }
       }

       return webMethodInfos;
   }

   /// <summary>
   /// Walk the schema definition to find the parameters of the given message.
   /// </summary>
   /// <param name="serviceDescription">
   /// <param name="messagePartName">
   /// <returns></returns>
   private static Parameter[] GetParameters(ServiceDescription serviceDescription, string messagePartName)
   {
       List<parameter> parameters = new List<parameter>();

       Types types = serviceDescription.Types;
       System.Xml.Schema.XmlSchema xmlSchema = types.Schemas[0];

       foreach (object item in xmlSchema.Items)
       {
           System.Xml.Schema.XmlSchemaElement schemaElement = item as System.Xml.Schema.XmlSchemaElement;
           if (schemaElement != null)
           {
               if (schemaElement.Name == messagePartName)
               {
                   System.Xml.Schema.XmlSchemaType schemaType = schemaElement.SchemaType;
                   System.Xml.Schema.XmlSchemaComplexType complexType = schemaType as System.Xml.Schema.XmlSchemaComplexType;
                   if (complexType != null)
                   {
                       System.Xml.Schema.XmlSchemaParticle particle = complexType.Particle;
                       System.Xml.Schema.XmlSchemaSequence sequence = particle as System.Xml.Schema.XmlSchemaSequence;
                       if (sequence != null)
                       {
                           foreach (System.Xml.Schema.XmlSchemaElement childElement in sequence.Items)
                           {
                               string parameterName = childElement.Name;
                               string parameterType = childElement.SchemaTypeName.Name;
                               parameters.Add(new Parameter(parameterName, parameterType));
                           }
                       }
                   }
               }
           }
       }
       return parameters.ToArray();
   }

   /// <summary>
   /// WebMethodInfo
   /// </summary>
   public WebMethodInfoCollection WebMethods
   {
       get { return _webMethods; }
   }

   /// <summary>
   /// Url
   /// </summary>
   public Uri Url
   {
       get { return _url; }
       set { _url = value; }
   }
}

/// <summary>
/// Information about a web service operation
/// </summary>
public class WebMethodInfo
{
   string _name;
   Parameter[] _inputParameters;
   Parameter[] _outputParameters;

   /// <summary>
   /// OperationInfo
   /// </summary>
   public WebMethodInfo(string name, Parameter[] inputParameters, Parameter[] outputParameters)
   {
       _name = name;
       _inputParameters = inputParameters;
       _outputParameters = outputParameters;
   }

   /// <summary>
   /// Name
   /// </summary>
   public string Name
   {
       get { return _name; }
   }

   /// <summary>
   /// InputParameters
   /// </summary>
   public Parameter[] InputParameters
   {
       get { return _inputParameters; }
   }

   /// <summary>
   /// OutputParameters
   /// </summary>
   public Parameter[] OutputParameters
   {
       get { return _outputParameters; }
   }
}

/// <summary>
/// A collection of WebMethodInfo objects
/// </summary>
public class WebMethodInfoCollection : KeyedCollection<string, webmethodinfo="">
{
   /// <summary>
   /// Constructor
   /// </summary>
   public WebMethodInfoCollection() : base() { }

   protected override string GetKeyForItem(WebMethodInfo webMethodInfo)
   {
       return webMethodInfo.Name;
   }
}

/// <summary>
/// represents a parameter (input or output) of a web method.
/// </summary>
public struct Parameter
{
   /// <summary>
   /// constructor
   /// </summary>
   /// <param name="name">
   /// <param name="type">
   public Parameter(string name, string type)
   {
       this.Name = name;
       this.Type = type;
   }

   /// <summary>
   /// Name
   /// </summary>
   public string Name;
   /// <summary>
   /// Type
   /// </summary>
   public string Type;
}

33 comments:

  1. Your code was very helpful for me to develop a code in asp.net1.0 and vb.net. Thanks a lot.

    ReplyDelete
  2. Hi: I have a requirement for getting all method names give the name of a webservice. So i would like to use the "www........asmx?wsdl" to query the wsdl and parse the resultant XML to get all method sigantures. Is this what this piece of code does? thanks for the inputs.

    ReplyDelete
  3. Anonymous10:44 pm

    (anything between angle brackets -- such as types for generics, comments -- disappears when code is posted this way)

    ReplyDelete
  4. Hello Mike, Can you please provide me code as zipped file for the example given here. Since you have posted type declarations along with HTML it has gone inaccessible.

    thanks in advance.

    Raj

    ReplyDelete
  5. Anonymous12:52 pm

    Mike,

    Great job!

    Can you please send me thid code too?

    ewolthaus at gmail dot com

    Thanks!

    ReplyDelete
  6. Anonymous2:09 pm

    Thanks for interesting article.

    ReplyDelete
  7. Anonymous2:56 am

    To see the type declarations select 'View Source' from your context menu. The actual code is in there.

    ReplyDelete
  8. Thanks for your comments guys. I very glad that you found this post useful. Sorry about the angle brackets, it's not the first time I've posted code without checking for that :P

    ReplyDelete
  9. This comment has been removed by the author.

    ReplyDelete
  10. Hi Mike

    your code is very usefull to me .
    i also want to know that how can we send parameters to web method through HttpWebRequest and access response through HttpWebResponse

    can you plz send me this code in zip file

    Thanks

    ReplyDelete
  11. Hello Mike

    I have one query regarding with web service API call.

    I have third party Web Service of PHP. i am calling this php web service in asp.net

    i have added web reference . Web Method returns ComplexType. i.e it returns Class object of array type.
    it execute call successfully. maintain data in log file . but object returns null value.

    that means deserilization of xml to object not working properly.
    php web service support document/literal format.
    can you plz help me to solve this issue.

    from last 5 days i m struggling for this
    Thanks

    ReplyDelete
  12. Hi I am Darshan,
    i have a query..
    does any body knows what
    htpp://localhost/XYZ/abc.asmx?wsdl=0

    i.e. wsdl=0 means...

    Thanks in advance

    ReplyDelete
  13. Hi Mike,


    I want to extend your code to parse SOAP headers. Any ideas how to do it?


    thanks in advance,
    George

    ReplyDelete
  14. Hi George,

    This code is no good for that. It's specialised for parsing WSDL files.

    Mike

    ReplyDelete
  15. Hello Mike,

    Can you please provide the code in a zipped file or even email it to kmadhu84@yahoo.co.in

    Thanks in advance.

    ReplyDelete
  16. Madhu,

    My current practice to create a test solution of any code I publish on my blog so that you can download it and get it working straight away.

    Unfortunately this post is nearly three years old and the code has gone to the great repository in the sky (I've lost it).

    I don't really have the time or inclination to re-visit this right now, but there should be enough there for you to work it out for yourself.

    Sorry for the unhelpful reply.

    ReplyDelete
  17. Mike, thanks for that quick reply.

    Though i'm new to the development stream, with the code provided in this blog (and peers help if required) i should come with an outcome.

    Thanks once again.

    ReplyDelete
  18. Anonymous6:37 am

    Hi Mike,
    It's realy good code, but you code can not return object parameter.

    i.e.
    HelloWorld1 "Testing"
    check int /check
    myString
    userName string /userName
    password string /password
    /myString
    /HelloWorld1
    HelloWorld1Response "Testing"
    HelloWorld1Result string /HelloWorld1Result
    /HelloWorld1Response

    Output:
    Web Method:HelloWorld1
    Input Parameters:
    Check int
    myString Certificate
    Out Parameters:
    HelloWorld1Result string

    Is there any way to get objet information like,

    Input Parameters:
    Check int
    myString Certificate
    userName string
    password string

    Please send an zip code on mail account @ abhijit.more11@gmail.com
    Thanks in Advance

    ReplyDelete
  19. Hi Abhijit,

    WSDL decribes complex types as XSD schemas. You can retrieve the XSD and use a tool such as XSD.exe to generate types from it, that's how the add-web-reference tool in VS works internally.

    If you want to be able to generate a message automatically, you simply have to parse the XSD yourself. There are plenty of good libraries out there for doing that.

    Sorry I don't have the time to show you code.

    Good luck.

    ReplyDelete
  20. Anonymous7:34 pm

    Hello Mike,

    It's a very useful code. However, this code doesn't retrieve the elements contained within elements. Here is WSDL I tried :
    http://ws.strikeiron.com/ReversePhoneLookup?WSDL

    I am interested in these values : -
    name="ListingName"
    name="FirstName" name="Address" etc.

    Do you know a way to retrieve this values?

    Thanks in advance.

    ReplyDelete
  21. Hi Anonymous,

    No, you are right, this code simply demonstrates passing simple values as parameters. It is far from a complete solution. Check out my comment to Abhijit above for some pointers about how to work with complex types.

    Mike

    ReplyDelete
  22. Anonymous3:15 pm

    thanks Mike

    ReplyDelete
  23. Mike,
    How to get the complex class details (i.e. for the class which falls as input / output to any web-method), the details I am looking for are :
    1. Class attributes which are useful in xml-serialization
    2. How to perform nesting on complex types to retrieve the info. same as point 1

    > vaibhav.gaikwad@gmail.com

    ReplyDelete
  24. Hi Mike,
    It does not work for the WSDL hosted below...

    http://api.clickatell.com/soap/webservice.php?WSDL

    ReplyDelete
  25. Note sure how you stubled upon this solution to the problem.
    It most of taken a good amount of effort to work tis one out.
    Either way thanks for the help!

    ReplyDelete
  26. akpc,

    Wasn't too bad. Reflector was the secret weapon :)

    ReplyDelete
  27. Hi,

    Reading WSDL from both webservice and WCF.

    http://hussainahamed.blogspot.com/2010/10/reading-wsdl-from-both-webservice-and.html

    ReplyDelete
  28. Anonymous10:00 am

    Hi,
    Thanks for this superb post.I have one query,
    What would be the modification if we have wsdl with authentication required.

    ReplyDelete
  29. This is a great post.. Thanks for sharing!

    ReplyDelete
  30. Anonymous6:17 pm

    Hi guys this is a great source for getting types but i was wondering code Rant has specified we can achieve this using service description classes do any one have any sources how we can do that.

    ReplyDelete
  31. Anonymous9:11 pm

    Thanks man...You are the best

    ReplyDelete
  32. Anonymous9:11 am

    usefull

    ReplyDelete
  33. Great post! Thank you very much.
    It would take anyone unfamiliar wiht ServiceDescription one or two days to figure out how to drill down ServiceDescription to get all the parameter information.
    The code has some case issues (primarily some lowercase initials should be uppercase), but they can be found easily when it is compiled.
    It fails to catch array parameters. It should have something like the following:

    if(childElement.MaxOccurs > 1)
    {
    parameters.Add(new Parameter(parameterName, parameterType + "[]"));
    }
    else
    {
    parameters.Add(new Parameter(parameterName, parameterType));
    }

    ReplyDelete

Note: only a member of this blog may post a comment.