Web Services

Increase Your App's Reach Using WSDL to Combine Multiple Web Services

Gerrard Lindsay

This article discusses:

  • The mysteries of XML namespaces and the Web Services Description Language
  • Sharing types between multiple Web services without having to write client code
  • The WSDL.exe tool
This article uses the following technologies:
The .NET Framework, C#, XML, and Web services

Code download available at:WSDL.exe(147 KB)

XML Namespaces versus CLR Namespaces
What is a Binding Anyway?
The Design of the Solution
Create WeatherServices WSDL
Implementing the CalculateChanceOfRain Service
Implementing the GetWeather Service
The WeatherServices Client App
Type Sharing in the .NET Framework 2.0
Conclusion

Enterprise solutions typically aggregate information from myriad internal applications and external sources. Web services have gained rapid adoption as a method to easily and reliably consume the varied data required by these solutions. Situations inevitably arise in which a single solution requires the consumption of multiple complementary Web services, which often share company-specific or domain-specific XML types. Unfortunately, the very tools that have helped to drive the growing adoption of Web services, and the enabling abstractions that they provide, can often prevent developers from peeking behind the curtains at the XML standards that make up the Web services stack. This article will offer a solution that enables type sharing between proxies created for complementary Web services, while at the same time providing an opportunity to examine the Web Services Description Language (WSDL) and its interaction with the Web services tools we know and love.

The first time I tried to create two complementary Web services that would share a common XML type, I ran into an unpleasant surprise. Each of the proxies that had been created in my client application had a separate object representation for the common message, in a separate C# namespace. I had to copy each of the properties manually from an instance of one class to an instance of the other, which seemed an unnecessary artifact of the current implementation of the tools.

I eventually stumbled onto a technique that allows you to expose a single Web service contract that encapsulates multiple Web service endpoints through the creation of a WSDL file with multiple bindings. This explicitly defines the relationship between the services, and also has the benefit of generating proxies that can share the managed classes that represent the common messages.

It's possible to share types defined by multiple contracts in the same namespace by manually editing tool-generated proxies after the fact. However, combining the Web service bindings into a single WSDL file allows the Web service implementer to define the shared relationship rather than placing the burden on the developers creating clients for the services. This technique also saves you from having to recreate those changes to the proxy class each time you change the service contract during development.

XML Namespaces versus CLR Namespaces

Before proceeding it's important to make sure that you understand the concept of namespaces, and the difference between namespaces in the context of XML and namespaces in the context of managed code. Both types of namespaces serve a similar purpose: to help developers avoid name collisions. They allow for a single group of items to include multiple items with identical names, but different meanings. This allows you to use the name "Order" to represent both a customer's order and also the sequence of an item in her shopping cart. In that way, namespaces act like the surnames used to distinguish "John Smith" from "John Brown." Let's take a look at this by examining the XML namespace shown in Figure 1.

1

In Figure 1, the Order element in a schema created by the company that uses the http://www.example.com/OrderService namespace can live in harmony with the Order element in a schema created by the company that uses the http://www.example.com/ShoppingCartService namespace. The URIs used as XML namespaces are often, but not always, in the form of URLs. However, there is no requirement (and it's very often not the case) that those URLs actually point to a schema file or to any other actual resource.

To keep XML files from being unbearably verbose, one of the criticisms of the XML format in general, namespace prefixes can be used as a shorthand for the actual URI reference that defines a namespace. When the namespace prefix is appended to the local name of an element in a namespace, with the help of a semicolon you get the qualified name for the element. Here's an example: + ":" + = "o" + ":" + "Order" = "o:Order"

Similarly, namespaces in the managed world separate classes with identical names. A great example of this is that the Windows® Forms library contains a class called System.Windows.Forms.Button, and the ASP.NET library contains a class called System.Web.UI.WebControls.Button without any problems. Although my example uses classes in different assemblies, it's important for you to keep in mind that it's also possible to have multiple classes with the same name in a single assembly as long as they both reside in different namespaces.

What is a Binding Anyway?

Before I delve into the solution, let's take a moment for a quick refresher on the moving parts that make up a WSDL file. I'll focus on a very simple document-literal Web service that operates over HTTP. There are lots of options, such as SoapHeaders and different transports and various encodings because WSDL is intended to be a very extensible standard. The simple WSDL file will describe a service whose body contains a custom XML document, the document part, defined in the service using XML Schema Definition Language (XSD), the literal part. If you'd like more detailed information on RPC/encoded Web services, WSDL, SOAP, or the other XML standards that make up the Web services stack, pay a visit to the MSDN Web Services Developer Center. Meanwhile, I'll give you a whirlwind tour of service contracts.

WSDL is a standard for describing the wire format of the messages that are carried by SOAP envelopes. A WSDL file is an XML document that acts as a contract for a Web service. It can be used by consumers of that Web service as a guide for creating and validating the XML payloads delivered to and received from that service. Let's take a look at the skeleton of a WSDL file, as shown in Figure 2, to get a better idea of its components. Keep in mind that this isn't a valid service contract. I've simplified it for this example by removing certain attributes and namespace declarations so that I can focus on the most interesting parts.

The definitions element is the root element of the WSDL file and contains the rest of the elements that I'll examine. As the root element, it also includes the namespace declarations for the WSDL namespace, the SOAP namespace, the XSD namespace, and any custom namespaces that are used by elements defined in the WSDL file or imported schemas. In the example file, the http://example.com/supersimplewebservice namespace is declared with the "my" namespace prefix.

The next element I'll examine is types, which is similar to an inline XSD. It defines the structure of the XML elements that make up the messages described by the WSDL file, in this case RequestMessageElement and ResponseMessageElement. A WSDL designer also has the option of defining the types in an external schema file, which can be imported into the service contract using import (not shown in the sample file). I use imported schemas with message-focused contract-first design, as you'll see when taking a closer look at the solution.

That brings me to the message element. Each message element describes a distinct message which will be used by the bindings defined in the WSDL file. The message element acts primarily as a container for part elements. Each part has an element attribute that specifies either a base XSD type such as string, float, or one of the elements defined in the types section of the WSDL file. While it's possible to include multiple part elements within a single message, the majority of the WSDL files that you'll encounter will only include a single part.

The portType element acts as a container for a group of operations and the messages that are sent or received during the course of each operation. Operations can be thought of as defined exchanges of messages and are modeled as instances of the operation element. If you think in terms of object-oriented programming, you can also see a vague resemblance between operations and abstract method signatures.

An operation element can have one or zero input, output, or fault elements. If you've ever taken a look at a WSDL file you've probably seen the elements that specify the inward and outward bound messages that an operation accepts, but I've yet to see a fault element in the wild. Although each of the operation's child elements is optional, at least one input or output message must be defined. In practice, most Web services include at least an input message. Each of these child elements contains a message attribute, which specifies the name of one of the message elements defined earlier in the file.

Now let's take a look at the binding element, or more precisely, the wsdl:binding element. All of the elements I've previously discussed have defined the messages in an abstract sense that may or may not directly correspond to the way that the messages are represented as they travel across the wire. The binding defines the concrete format of the messages by detailing the actual format of the messages, the protocol used during the interactions, and the network transport used to exchange the messages. The binding element's type attribute specifies the port type to which it is linked. Its operation child element specifies the particular operation within the port type for which the binding is intended, as well as the input, output, or fault messages to be used.

Each binding also contains extensibility elements, which are used to specify additional information specific to the protocol used by the binding. Since my sample service uses SOAP 1.1 as its protocol, it includes a child soap:binding element. Remember that the two binding elements have the same local name, but are, in fact, different elements because of the namespaces they are in. The soap:binding element's transport attribute from the example service specifies that the service will use HTTP rather than SMTP or TCP/IP as its network transport. The soap:binding element's style attribute declares the default body style used by the operations specified in the wsdl:binding as either document or rpc (in this case, document). The other binding extensibility element in the sample WSDL file is the soap:operation element, which specifies the SOAPAction header expected by services that implement the binding. It also provides the ability to override the default style specified by the soap:binding element.

The final element to look at is service. Each service element corresponds to a specific Web service endpoint and defines the name and set of ports exposed by that endpoint. The service element also contains an extensibility element, which in this case is soap:address. The soap:address element's location attribute specifies the URI of the endpoint itself. When you start building real WSDL files, you may notice that they do not include a service element as they are intended to be reused by several endpoints.

The Design of the Solution

At this point, you have enough of a foundation to look at the technique I described earlier. Yasser Shohoud wrote about message-focused, contract-first design of Web services in his great article "Place XML Message Design Ahead of Schema Planning to Improve Web Service Interoperability" in the December 2002 issue of MSDN®Magazine. In the article, Yasser walks you through the steps of creating a Web service that returns information about the current weather using a message-first design methodology. To summarize, he starts by defining the XML messages that will be exchanged with his service. He then uses that XML structure to define an XML schema and then an abstract service description. That abstract service description is used to create an implementation of the service.

Using message-focused, contract-first design allows the Web service designer to define the exact structure of the messages that will travel over the wire, rather than designing and implementing the object representations and letting the tools decide on the wire level representation. There are probably a large number of Web services in production whose architects have never given a thought to what their services's messages look like on the wire, generally because they have been implemented and tested on a single platform and only consumed through proxies. If those developers were forced to dive below the abstraction provided by their development tools to solve an esoteric issue, or if they needed to make use of the powerful tools available to XML developers, they might suddenly be surprised to confront less than ideal XML representations of their messages.

Imagine you are part of a different development team asked to build a new Web service called CalculateChanceOfRain, which will be complementary to the GetWeather service implemented by Yasser's team. The new Web service will use one of the elements defined in the original service's schema, but it will return a float value representing the likelihood of rain based on the current weather. Your service will be hosted on its own server in a different datacenter, and it will use different back-end systems than the GetWeather service. In fact, you could easily assume that the new service is being developed by a completely standalone partner organization. You'll have no access to the source code or other implementation details for the original service, just the schema and WSDL files. Figure 3 displays the sequence of operations in a single user interaction with the two services.

Figure 3** Overview of a Single User Transaction **

As you can see, the two service implementations form a "virtual" aggregate service from the client's point of view. If you were tasked with designing the new solution, your first thought might be to create a brand new service contract (WSDL file) for the new service that would import the schema defined for the GetWeather service. Unfortunately, using that technique will mean that the current .NET tools will treat the two Web services as completely distinct entities. When Visual Studio® .NET or WSDL.exe is used to create proxies for the two Web services, each proxy will have its own object representation of the "CurrentWeather" message.

The classes will be identical and will be serialized and deserialized into the identical XML structure. In fact, you could use an XmlSerializer to serialize an instance of one class into a document, and then deserialize that same document successfully into an instance of the other class. However, from the runtime's perspective the two classes will be separate managed classes in separate namespaces that just happen to have the same name. That being the case, the object instance returned from a call to the GetWeather proxy object's method can't be passed to the method of the CalculateChanceOfRain proxy. They aren't the same class in the managed world, any more than System.Windows.Forms.Button and System.Web.UI.WebControls.Button are the same class.

This means that you're left with either copying values between instances of the different classes or manually editing the proxy code generated by a tool. Copying the data between fields probably wouldn't make much of a performance difference if your messages aren't huge, but it's definitely an extra maintenance task that I'd prefer to avoid. If you decided to change the proxies manually, you'd have to remove one of the two classes that represent the common XML message, and to recode the proxy whose representation was removed to use the other proxy's class. Remember, those changes would have to be reimplemented each time you created a new Web reference to the services. In my mind, neither of these solutions is particularly ideal because they place the burden of enforcing the conceptual sharing of messages between the services on the client developer.

By creating a single interface WSDL with a binding for each of the implementation services you can gain a level of encapsulation that explicitly defines the heretofore implicit relationship between the two service implementations. Instead of just having a GetWeather service and a CalculateChanceOfRain service, you can expose a virtual WeatherServices service that encompasses both sets of behavior. You also allow the Web service tools you use to create a single class representation of the data types common to both services. Let's take a look at creating an aggregate service description containing bindings for both of the services.

Create WeatherServices WSDL

The first step towards creating a new aggregate service description is to decide on the messages that will be passed to the new CalculateChanceOfRain service. As I've previously discussed, the message passed as input to the new Web service will be the same CurrentWeather element returned by Yasser's GetWeather service. Let's take a look at the sample XML in the original article: Sunny http://www.LearnXmlws.com/images/sunny.gif 0.41 30.18 75 23.89

As you can see, the CurrentWeather element contains child elements that include a textual description of the current conditions, the temperature in both Celsius and Fahrenheit, and other pieces of information about the weather in a certain area. The new CalculateChanceOfRain service will ostensibly use this information about the weather to forecast the likelihood of rain in that area. The percent chance of rain will be returned to the caller as a simple element containing a float value. You'll need to create a new schema to define that output message, which you'll call CalculateChanceOfRainResponse. The contents of the new RainServiceMessages schema are shown in the following:

Now that you've defined your message, you can create the new aggregate service interface. Rather than creating a new WSDL file from scratch, you're going to modify the WSDL file created by the team responsible for the GetWeather service. Figure 4 shows the contents of the file in its current form.

To create a new aggregate service interface, you need to copy the contents of the WeatherInterface.wsdl to a new file I'll call Aggregate_WeatherInterface.wsdl. Then you need to add the namespace of your new RainServiceMessages schema to the definitions element in the new WSDL file. Then add a request message that uses the CurrentWeather element as its data part, and a response message that uses the new CalculateChanceOfRainResponse element. You also need to add a port and binding for the new service to the WSDL file. Figure 5 shows the completed Aggregate_WeatherInterface service description.

Once it's complete, you'll need to move the WSDL file to a location where it will be accessible to your clients. When you tell client developers about the Web service, or when you publish it using services like UDDI, you'll point users to the location of this static WSDL file instead of using the dynamically generated file created by accessing the ASMX URL with "?WSDL" on your query string. Of course, like all good Web service citizens you've been saving that dynamic file as a static file once you go into production, right? Of course you have. It's not a contract if you're dynamically generating it using reflection. You could also have the dynamically generated WSDL point at your static WSDL using the mechanism that you will find described at Building XML Web Services Using Industry Standardized WSDLs.

Implementing the CalculateChanceOfRain Service

Now that the aggregate service description is complete, you're ready to implement the CalculateChanceOfRain Web service. The first step in that process will be to create an abstract stub for the service implementation using the wsdl.exe tool, just as Yasser did while implementing the GetWeather service. Here's the command line I used to create my CalculateChanceOfRain stub: wsdl /server /o:RainServiceStub.cs http://localhost/WeatherService/aggregate_weatherinterface.wsdl

If you examine the Web service stub file that is generated, you'll notice that the file includes two abstract Web service classes. Each corresponds to one of the bindings included in the aggregate Web service description file. You should manually remove the GetWeather binding implementation from the stub file since you're only using this class to implement your CalculateChanceOfRain Web service, but that's more for aesthetics than any real technical motivation. You'll need to keep the definition of the CalculateWeather class as you don't have access to the source code defining the class in the GetWeather service project in your scenario. Figure 6 shows the modified Web service stub file.

[System.Web.Services.WebServiceBindingAttribute( Name="CalculateChanceOfRainInterface", Namespace="http://learnxmlws.com/Weather")] public abstract class CalculateChanceOfRainInterface : System.Web.Services.WebService { [System.Web.Services.WebMethodAttribute()] [System.Web.Services.Protocols.SoapDocumentMethodAttribute("", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle= System.Web.Services.Protocols.SoapParameterStyle.Bare)] [return: System.Xml.Serialization.XmlElementAttribute( "CalculateChanceOfRainResponse", Namespace="http://somecompanydotcom/RainServices")] public abstract System.Single CalculateChanceOfRain( [System.Xml.Serialization.XmlElementAttribute( Namespace="http://learnxmlws.com/Weather")] CurrentWeather CurrentWeather); } [System.Xml.Serialization.XmlTypeAttribute( Namespace="http://learnxmlws.com/Weather")] public class CurrentWeather { public string Conditions; public string IconUrl; public System.Single Humidity; public System.Single Barometer; public System.Single FahrenheitTemperature; public System.Single CelsiusTemperature; }

Now you need to create a service implementation that will derive from the Web service stub class. The first step is to create a new Web service project. The project in the sample code included with this article is called "RainService." Add a Web service named MyRainService to the project, and open its codebehind file. Figure 7shows the code in the MyRainService Web service codebehind file after it has been modified.

[WebService(Namespace = "http://somecompanydotcom/RainServices")] [WebServiceBinding(Name="CalculateChanceOfRainInterface", Namespace="http://learnxmlws.com/Weather", Location="http://localhost/WeatherService/ aggregate_weatherinterface.wsdl")] public class MyRainService : CalculateChanceOfRainInterface { public MyRainService() { // This call is required by the ASP.NET Web Services Designer InitializeComponent(); } [WebMethod] [SoapDocumentMethod("", ParameterStyle=SoapParameterStyle.Bare)] [return: XmlElement ("CalculateChanceOfRainResponse", Namespace="http://somecompanydotcom/RainServices")] public override float CalculateChanceOfRain( [System.Xml.Serialization.XmlElementAttribute( Namespace="http://learnxmlws.com/Weather")] CurrentWeather CurrentWeather) { float result = 0; if (CurrentWeather.Conditions == "Rainy") result = 100; else { Random rnd = new Random(DateTime.Now.Millisecond); result = (float)rnd.Next(101); } return result; } }

Notice that I've applied a WebServiceAttribute to the class in order to set the namespace that will be used by the service. This isn't strictly necessary, as the ASP.NET Web service infrastructure will default to using the tempuri.org namespace, but it's a good idea for a production Web service. I've also applied a WebServiceBindingAttribute to the class to indicate that the binding for the Web service is defined in the aggregate service description that was created earlier. This is the crucial attribute; it specifies that the service is going to use the CalculateChanceOfRainInterface binding defined in the aggregate WSDL file.

Given my complete lack of knowledge when it comes to meteorology as well as my lack of friends named Storm or Chase, the implementation of the CalculateChanceOfRain method simply returns a random float value between zero and one hundred. More accurate weather forecasting logic is left as an exercise for the reader. That said, the success rate of this algorithm would probably compare favorably with that of many of the local weather forecasters I've watched on TV.

Implementing the GetWeather Service

Since your scenario is fictional, you'll need to create a sample implementation of the GetWeather service in order to test your solution. The steps are almost identical to those you followed to implement the previous Web service, so I won't cover them in detail again. Suffice it to say that you'll again use the WSDL.EXE tool to generate a Web service stub and then create a new class file to hold the service implementation. Figure 8 shows the completed implementation of the Web service.

[WebService(Namespace = "http://learnxmlws.com/WeatherService")] [WebServiceBinding(Name="WeatherInterface", Namespace="http://learnxmlws.com/Weather", Location= "http://localhost/WeatherService/WeatherInterface.wsdl")] public class WeatherService : WeatherInterface { [WebMethod] [SoapDocumentMethod("",Binding="WeatherInterface", ParameterStyle=SoapParameterStyle.Bare)] [return: XmlElement("CurrentWeather", Namespace="http://learnxmlws.com/Weather")] public override CurrentWeather GetWeather( [System.Xml.Serialization.XmlElementAttribute( Namespace="http://learnxmlws.com/Weather")] string WeatherRequest) { //ignore the zipcode specified Random rnd = new Random(); CurrentWeather result = new CurrentWeather(); result.Barometer = (float)rnd.Next(101); result.CelsiusTemperature = (float)rnd.Next(78); result.Conditions = (rnd.Next(2) == 1) ? "Rainy" : "Sunny"; result.Humidity = (float)rnd.Next(101); return result; } }

As I've said, I have very little knowledge of the mysteries of meteorology, and I don't have access to weather-detecting equipment. In lieu of a contract with a weather information provider, myGetWeather method will ignore the zip code specified by the caller. Instead, the service returns a CurrentWeather instance filled with random values. It isn't important since the CalculateChanceOfRain service ignores the message sent to it anyway.

The WeatherServices Client App

At this point, you're ready to test your new aggregate Web service. To do so, create a simple Windows Forms application called WeatherClient which will act as a client for the Web services. It will look like the form in Figure 3 (without the background image).

Now, let's take a look at what's necessary for the implementation. First, you need to add some controls to your main form to allow the user to interact with the application. Add a textbox to the main form and name it "Zipcode," then add a button to the form, and name it "SubmitButton." Finally, add a label to display the results of your calculation and name it "DisplayLabel."

Next, use Visual Studio .NET to add a reference to the aggregate service description. Remember to add the reference to the static WSDL file that was created, not to the ASMX file's dynamically created WSDL. The aggregate service description doesn't include service elements that specify the endpoints for the service implementations, so you'll have to manually set the URL properties of your proxy instances before invoking the Web services.

Like many .NET XML Web service developers, the first thing I do after adding a Web reference is set the Web reference's URL Behavior property to Dynamic. When this property is set, the proxy class determines the URL of the Web service endpoint by examining a setting in the application's configuration file so you don't have to have the URL hardcoded into the class itself. Unfortunately, this doesn't work with the multibinding file you've created, so you'll have to create the configuration entries manually and set the URL properties for your proxy instances.

Now you're ready to add the client code to the application. Figure 9 shows the important section of the code in the form.

private void SubmitButton_Click(object sender, System.EventArgs e) { //let the user know we're working this.Cursor = Cursors.WaitCursor; updateStatus("Getting weather!", false); //call the GetWeather method asynchronously weatherservices.WeatherInterface weatherService = new WeatherClient.weatherservices.WeatherInterface(); weatherService.Url = ConfigurationSettings.AppSettings["weatherServiceUrl"]; weatherService.BeginGetWeather(ZipCode.Text, new AsyncCallback(this.getWeatherResult), weatherService); } private void getWeatherResult(IAsyncResult ar) { //we're returned from our call to GetWeather! updateStatus("Getting chance of rain!", false); //let's get the result of our call to GetWeather weatherservices.WeatherInterface weatherService = (weatherservices.WeatherInterface)ar.AsyncState; weatherservices.CurrentWeather currentWeather = weatherService.EndGetWeather(ar); //now we need to call the GetWeather method asynchronously weatherservices.CalculateChanceOfRainInterface rainCalculator = new WeatherClient.weatherservices.CalculateChanceOfRainInterface(); rainCalculator.Url = ConfigurationSettings.AppSettings["rainServiceUrl"]; rainCalculator.BeginCalculateChanceOfRain(currentWeather, new AsyncCallback(this.getCalculateChanceOfRainResult), rainCalculator); } private void getCalculateChanceOfRainResult(IAsyncResult ar) { weatherservices.CalculateChanceOfRainInterface rainCalculator = (weatherservices.CalculateChanceOfRainInterface)ar.AsyncState; float chanceOfRain = rainCalculator.EndCalculateChanceOfRain(ar); string resultMessage = string.Format("There is a {0}% chance of rain.", chanceOfRain); updateStatus(resultMessage, true); } // a delegate that is used by updateStatus to invoke itself recursively private delegate void UpdateStatusDelegate( string textToDisplay, bool changeCursorToDefault); private void updateStatus(string text, bool changeCursorToDefault) { if (DisplayLabel.InvokeRequired) { DisplayLabel.Invoke(new UpdateStatusDelegate(this.updateStatus), new object[] { text, changeCursorToDefault}); return; } DisplayLabel.Visible = true; DisplayLabel.Text = text; if (changeCursorToDefault) this.Cursor = Cursors.Default; }

In the event handler for the button's Click event, initiate a call to the GetWeather service using the Web service proxy class. In order to keep the user interface responsive, use the Web service proxy's implementation of the Asynchronous Method Invocation Design Pattern. This causes the Web service request to execute on a thread from the ThreadPool, rather than on the user interface thread which needs to handle posted windows messages in order to keep the UI responsive. The getWeatherResult method is specified as the AsyncCallback delegate for your asynchronous call to the GetWeather method. The proxy is the third parameter.

When the GetWeather Web service call is completed, the getWeatherResult method executes. The getWeatherResult method converts the AsyncState property of the IAsyncResult object provided as its argument into an instance of CurrentWeather. It then uses the CurrentWeather instance as the argument for a call to the CalculateChanceOfRain Web service. The getCalculateChanceOfRainResult method is specified as the AsyncCallback delegate for your asynchronous call to the GetWeather method.

When the getCalculateChanceOfRainResult method executes, the updateStatus method is called to display the chance of rain to the user by updating the Text property of the DisplayLabel label. As you are probably aware, Windows Forms controls are not thread safe and their methods should only be called from the user interface thread. When the asynchronous responses from the Web services are received on a ThreadPool worker thread, the DisplayLabel's InvokeRequired property will be true. The method then invokes itself recursively through DisplayLabel.Invoke so that the assignment of the label's text property occurs on the user interface thread.

Type Sharing in the .NET Framework 2.0

You'll be happy to know that the type sharing story is greatly improved in the forthcoming release of the Microsoft® .NET Framework 2.0. The next version of the WSDL.EXE tool makes type sharing between services simpler with the addition of the sharetypes command-line argument. By providing multiple WSDL URLs to the tool, and by adding the sharetypes flag to your arguments, you signal to the tool that you would like any common types to be shared between the generated proxies. Common types are elements with the same local name and namespace, defined in the same schema file. Here's an example command line that could be used to generate a Web service client stub that can be used to call two Web services that share types: wsdl /sharetypes http://localhost/service1.wsdl http://localhost/service2.wsdl

The created file will contain classes for each Web service's binding, and a single class for each of the types that are shared between the services. In fact, the created file is almost identical to the file created when a developer using the .NET Framework 1.x adds a reference to the aggregate WSDL file. Obviously, the Web services team paid attention to the Web service development community which has been clamoring for this new feature.

If you're testing the .NET Framework Beta 1 release, you may notice that the new functionality isn't currently exposed in the development environment. Rest assured, the same capability will be available from Visual Studio 2005 in later betas.

There is a caveat if you are considering making use of WSDL files with multiple bindings, and your services also make use of SoapHeaders. There is a known issue with client proxy generation in that specific scenario where only the first binding's SoapHeaderAttribute parameters are correct. A simple work around is explained in the Knowledge Base article "BUG: Incorrect Proxy Is Generated When WSDL File Contains Multiple Bindings". I haven't touched on SoapHeaders in this article, but you should be aware of the issue in case you stumble into it on one of your future projects.

Conclusion

Obviously, the encapsulation accomplished here is leaky. The client application is well aware that there are two separate bindings as there are two different proxy classes. They are also well aware that the two services live in separate locations, since the client configures the URLs of the endpoints used to access the services. At the same time, there is nothing to indicate to the client application that the two services were implemented separately, at different times, by different teams. You also have the benefit of being able to share types between the two Web services in the client code, which eliminates a lot of copying of data from one structure to another identical structure in a different namespace.

Due to the incredible ease of creating and consuming XML Web services using tools available in the .NET Framework, many developers and architects never take the time to familiarize themselves with the protocols that underlie the Web services stack. It couldn't be easier to write a normal method, apply a WebMethodAttribute to it, and work exclusively with autogenerated proxy objects. However, having an understanding of the underlying protocols, and how the tools work with those protocols, can expand the options available to you as you design your solutions. Hopefully this article will inspire you to dedicate some time to looking behind the curtains, even if you're already focused on using the sharetypes functionality in the .NET Framework 2.0.

Gerrard Lindsay is an application development consultant with Microsoft Services in the East Region. This is his first article, so please make sure all flames are written in simple language. Gerrard can be contacted at glindsay@microsoft.com.