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:

homeuser said...

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

Unknown said...

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.

Anonymous said...

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

രാജ് said...

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

Anonymous said...

Mike,

Great job!

Can you please send me thid code too?

ewolthaus at gmail dot com

Thanks!

Anonymous said...

Thanks for interesting article.

Anonymous said...

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

Mike Hadlow said...

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

Unknown said...
This comment has been removed by the author.
Unknown said...

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

Unknown said...

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

Darshan's BLOG said...

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

Unknown said...

Hi Mike,


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


thanks in advance,
George

Mike Hadlow said...

Hi George,

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

Mike

Madhu Krishnappa said...

Hello Mike,

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

Thanks in advance.

Mike Hadlow said...

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.

Madhu Krishnappa said...

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.

Anonymous said...

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

Mike Hadlow said...

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.

Anonymous said...

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.

Mike Hadlow said...

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

Anonymous said...

thanks Mike

Bugs! said...

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

Bugs! said...

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

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

akpc said...

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!

Mike Hadlow said...

akpc,

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

Hussain Ahamed said...

Hi,

Reading WSDL from both webservice and WCF.

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

Anonymous said...

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

F'Post said...

This is a great post.. Thanks for sharing!

Anonymous said...

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.

Anonymous said...

Thanks man...You are the best

Anonymous said...

usefull

Hong said...

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