Generating WebService
proxies in .NET
WebServices have become the de-facto mechanism for
remote object method invocation across the Internet. Infact, so much is their impact
that newer standards like WS-Security, WS-SecureConversation and more, have come
up, to help them participate more in enterprise level applications.
From the perspective of a client consuming the webservice,
may that client be a rich desktop application or a thin web application, an important
component involved in the communication between the client and the webservice is
the webservice proxy. A Webservice proxy is the entity that allows for the client
to invoke the methods of the webservice, which might be residing anywhere across
the Internet, as if the method invocation was local. And this proxy is created,
in .NET Framework, using the WSDL utility. But what if you wanted to generate a
proxy for a webservice programmatically? How would you do that?
In this article, we shall delve into using one of
the FCL (.NET Framework Class Library) classes that is hidden deep inside the FCL
forest and use it generate the proxy for a webservice programmatically. And not
just that, we shall use CodeDOM to generate it in the language of our choice! So,
this implies that you could create a webservice proxy in Managed C++! Ofcourse,
you will need to have .NET Framework 1.1 for that to happen :)
Introducing ServiceDescriptionImporter
System.Web.Services.dll assembly contains a namespace,
System.Web.Services.Description. This namespace has a good amount of classes
inside it, including the one that will generate the webservice proxy for us, called
ServiceDescriptionImporter.
Using ServiceDescriptionImporter to generate
the proxy for a webservice involves the following steps:
-
Specify the WSDL (Webservice Description Language) file
for the webservice in question
-
Create a CodeDOM representation and produce the webservice
proxy as a CodeDOM representation
-
Convert the CodeDOM representation to the language specific
code
And thus you have your own webservice proxy. Let's go
through each of the above three steps and generate a webservice proxy. The webservice
that we shall use is very simple in nature: it shall simply add two numbers and
return their sum. This is the source for the same:
|
<%@ WebService language="C#" class="CAdd" %>
using System;
using System.Web.Services;
using System.Xml.Serialization;
public class CAdd {
[WebMethod]
public int Add(int a, int b) {
return a + b;
}
}
|
Now, to view the WSDL for a .NET hosted webservice, the webservice needs
to be invoked by suffixing
?WSDL to its URI. On my system, the WSDL is made available
via the URI http://localhost/wsproxygen/add.asmx?WSDL.
WSProxyGen is the virtual folder under which the webservice has been hosted.
To read this WSDL, we use the WebClient class's DownloadData
method as shown below:
|
WebClient wcWSDL =
new WebClient();
byte[]arrWSDL = wcWSDL.DownloadData(http://localhost/wsproxygen/add.asmx?wsdl);
// write WSDL to a file
FileStream fsWSDL = File.Create("webservice.wsdl");
fsWSDL.Write(arrWSDL,0,arrWSDL.Length);
fsWSDL.Close();
|
The WSDL is returned to us as a byte array, which we write to a file called webservice.wsdl.
Now that we have the WSDL, its time to move to the next step and create a CodeDOM
representation of this WSDL.
Generating CodeDOM based webservice proxy
To read, format and parse the WSDL that we have, and produce the webservice
proxy from it, we need to read the webservice description it contains. And this
is done using ServiceDescription class's Read method. This is
(again) contained in the System.Web.Services.Description namespace, in
the System.Web.Services.dll, and is done as shown below:
|
// create a service description
ServiceDescription sd = ServiceDescription.Read("webservice.wsdl");
|
The static Read method returns us a ServiceDescription
object reference. Next, to do the actual import and produce the proxy, we create
ServiceDescriptionImporter object and point it to the service description
object from which to perform the import:
|
// instantiate an importer and load the service description in
it..
ServiceDescriptionImporter imp = new ServiceDescriptionImporter();
imp.ProtocolName = "SOAP";
imp.ServiceDescriptions.Add(sd);
|
The ProtocolName property is used to specify the protocol in
which the service description has been presented, and in this case, its the SOAP
protocol. Finally, we add the service description we created to the ServiceDescriptions
collection of the importer. The importer works on the service descriptions
contained in its collection to produce the proxy for each of them.
The next step is to create the CodeDOM representation of the
webservice proxy by creating the appropriate CodeDOM objects:
|
// create a codeDOM namespace, its code unit and perform the import
against it...
CodeNamespace ns =
new CodeNamespace("NSWinToolZone");
CodeCompileUnit ccu = new CodeCompileUnit();
ccu.Namespaces.Add(ns);
imp.Import(ns,ccu); |
For those who are new to CodeDOM or are not aware of what it
does, here's brief understanding of the same. CodeDOM is a way to represent
your program logic in memory using generic in-memory constructs. For instance, an
application's source code file is represented as a CodeCompileUnit. Each
CodeCompileUnit has a collection to which namespaces can be added, which
is grammatically equivalent to doing a using System or Imports System
language specific ways to refer to the usage of a namespace. The advantage of using
CodeDOM is that once the entire source code is represented by a CodeDOM
structure, it can be converted to any language specific code by using the relevant
CodeProvider. Support for CodeDOM is present in the
System.CodeDOM namespace, in the System.dll assembly.
Now, the ServiceDescriptionImporter produces the code for the
webservice proxy as a CodeDOM representation. For this, we create a new
namespace under which the code shall be produced, the name of of which is set to
NSWinToolZone.
CodeNamespace is the way to create a new namespace and we create
it, and subsequently add it to the Namespaces collection of the CodeCompileUnit.
Finally, we invoke the Import method of the ServiceDescriptionImporter
object that uses the service descriptions specified in its ServiceDescriptions
collection and create the CodeDOM representation of the webservice
proxy code in the code compile unit object we pass in as the second argument to
the method.
Now that we generated the WSDL, and created the webservice proxy in its
CodeDOM format, let's proceed to the final step: producing language specific
code from the CodeDOM representation that we have.
Generating compiler specific webservice proxy
To produce language specific code, we got to use entities that are termed
as code-providers. These are set of classes that the .NET framework has built-in
support for and when pointed to a CodeDOM based representation of a code,
will parse it and spit out the language specific code for the same. These code-providers
reside in the various Microsoft.* namespaces. For instance, the C# code-provider
resides under the Microsoft.CSharp namespace, while the VB.NET code-provider
resides under the Microsoft.VisualBasic namespace. For the VJ# and Managed
C++ code-providers, you will need to reference their respective assemblies before
they can be used to produce the code.
For our exemplification, we will produce C# code for our webservice proxy. Thus,
we use the Microsoft.CSharp namespace and create an instance of the CSharpCodeProvider
class:
|
// create the C# code provider, get its code generator
and produce the webservice
// proxy code..
CSharpCodeProvider provider = new CSharpCodeProvider();
ICodeGenerator gen = provider.CreateGenerator();
IndentedTextWriter tw = new IndentedTextWriter(new
StreamWriter("webservice_proxy.cs"));
gen.GenerateCodeFromCompileUnit(ccu,tw,new CodeGeneratorOptions());
|
From the code-provider, we use the CreateGenerator method to
get reference to an ICodeGenerator implementation that will generate the
C# code for us. ICodeGenerator is present under the System.CodeDOM.Compiler
namespace, so the same must be included in our code.
Next, we can use one of the various GenerateCodeFrom methods
of the ICodeGenerator implementation. Since, in our case, the webservice
proxy code is represented in a CodeCompileUnit instantiation, we shall
use the GenerateCodeFromCompileUnit method. The first argument to this
method is the code-compile-unit from which the code will be generated. The second
is the reference to a TextWriter implementation that will be used to write
the code to the disk. For this purpose, we pass a reference to the IndentedTextWriter
object that will be used to write the webservice-proxy C# code to the file,
webservice_proxy.cs. The third argument to GenerateCodeFromCompileUnit
is the set of code-generation options and we use the default values of the
CodeGeneratorOptions object.
Finally...
Once the GenerateCodeFromCompileUnit method is executed, the web-service
proxy is generated in the referenced file on the disk. Open the file and you will
see that the code produced is no different from the code produced by the WSDL
utility that ships with the .NET Framework SDK. And you can see for yourself that
it takes just three simple steps to produce such kind of proxy programmatically,
and you can have complete control over the grammar in which the proxy should be
produced.
Infact, this is the methodology I used to write the
winWSDL application that is the GUI way to produce webservice proxy in the
language of your choice. Now, you can do the same too!
Download C# source code to the webservice
and its proxy-generating client.
25th January, 2004
|