Building XML Web Services Using Industry Standardized WSDLs
February 19, 2002
You are a pencil-reselling tycoon. As a pencil-reselling tycoon, you require constant information on the state of your suppliers in order to get the best price and to meet the demands of the pencil retailers who purchase your product. Your suppliers are your lifeline, so you must always know exactly what price they are asking, whether or not they have stock available for immediate shipping, the general quality of their product, and their timeliness in fulfilling orders.
You used to do your business by phoning your sales contacts at each supplier, but you found you were losing excruciatingly large opportunities, because Bob at Acme Pencil used to drone on for 20 minutes about last weekend's football game before he would take your order. As the pencil commodity market moved to the Internet, you were able to quickly visit Web sites for your different suppliers and immediately get their price and on-hand stock quantities. The problem was that each supplier had their own unique Web site with different approaches for getting at the data you needed—and even more differences in how they actually took your orders.
You longed for consistency. You dreamed of a world where a single application would query all the different pencil manufacturers for their information, and you could place your orders (using the same application) from the manufacturer of your choosing, based on product price, shipping costs, quantity on hand, and your personal knowledge of the quality of the manufacturer's service in handling orders. With an application like that, you would make millions of dollars by being the quickest, most efficient pencil-reselling tycoon in the industry.
When XML Web Services came along, you knew your dream application would soon be a reality and your millions were as good as in the bank.
The margins for pencil reselling may not exactly make millionaires overnight, but one of the more compelling opportunities for XML Web Services is certainly the ability to create common, well-defined interfaces that can be shared across an industry and make business flow competitively and easily.
In the last At Your Service column, Scott wrote about creating industry standardized WSDLs that define these interfaces. In this column, I will be taking the WSDL that Scott created for the pencil sellers industry, and will create an XML Web Service that implements the interface that the WSDL defines. Then I will add my XML Web Service to the UDDI registry, so that I can start taking fictitious pencil orders from anyone on the Internet.
The Application Model
The model for the Pencil Purchasing Web application all hinges on the definition of the WSDL files that Scott defined in the last column. The client application must be aware of the WSDLs, the UDDI registry must be aware of the WSDLs, and the XML Web Services must be aware of the WSDLs. A simplistic layout of the whole relationship is shown in Figure 1.
Figure 1. The pencil-purchasing application model
The client application shown on the left side of Figure 1 could be created with any of the SOAP tools available that allow you to communicate with an XML Web Service by referencing the WSDL for that service. The application knows at compile time how to communicate with an XML Web Service defined by Pencil.WSDL, but it does not know the endpoint for any of these services. Therefore, the first thing the application must do is to send a request to the UDDI registry asking for all the endpoints of services that implement the interface defined in Pencil.WSDL.
The UDDI Registry is basically just a database that stores information about various XML Web Services that people have registered. It handles the request from the application by returning a list of endpoints for all registered XML Web Services that implement the Pencil.WSDL interface. The diagram shows two such businesses: ColdRooster.COM and PencilSellers.Org. The Pencil Buying application can now communicate to the interfaces on these sites to see what sort of pencils each site has on stock, and can actually place and track orders with either of these sites.
In reality our application model is a little more complex, because we have broken the pencil-purchasing model into two parts: discovery and ordering. A separate WSDL was created for each. This seemed like the proper model because we wanted the discovery portion of the application to be freely available to anyone in the world. The ordering portion of the application, however, would require some form of authentication and privacy, since product orders and financial transactions would ultimately be involved. Thus we created the two separate services. We did, however, omit from our sample interface the complexity of any financial dealings and authentication mechanisms.
The Server View of Things
In this article, we will focus first on building the XML Web Service that the client application will communicate with. We will also go through the steps required to register our XML Web Service with UDDI. We wrote our XML Web Service with Microsoft® Visual Studio® .NET in Microsoft Visual Basic® using ASP.NET and some of the tools provided by the .NET Framework.
You could manually create an XML Web Service in Visual Studio .NET, and after enough playing with the various attributes and complex type definitions, you could have something that properly implements the WSDLs defined for our standard interface. Nevertheless, it would be a lot easier if there was a tool that would do all that tedious work for you.
It just so happens that there is just such a tool, called WSDL.EXE; it is one of the tools distributed with the .NET Framework SDK. WSDL.EXE is a command-line utility that will read a WSDL and implement either a proxy class for a client application, or it will create a server class that can be used to create an XML Web Service that implements the defined interface. In our case, we want to do the latter, so we will use the "/Server" command-line option to indicate to WSDL.EXE the kind of code we want it to generate. The full command line to generate the code for the discovery portion of our XML Web Service looks like this:
WSDL /Server /language:vb /namespace:PencilBaseDiscovery /out:PencilDiscovery.vb http://pencilsellers.org/wsdl/pencildiscovery.wsdl
Running WSDL.EXE with these parameters results in the creation of the PencilDiscovery.vb file that defines a number of classes. The most important class is the DiscoveryBinding class. The code for it looks like this:
'<remarks/> <System.Web.Services.WebServiceBindingAttribute( _ Name:="DiscoveryBinding", _ [Namespace]:="http://pencilsellers.org/pencil/discovery"), _ System.Xml.Serialization.SoapIncludeAttribute( _ GetType(Pencil)), _ System.Xml.Serialization.SoapIncludeAttribute( _ GetType(SearchTerm))> _ Public MustInherit Class DiscoveryBinding Inherits System.Web.Services.WebService '<remarks/> <System.Web.Services.WebMethodAttribute(), _ System.Web.Services.Protocols.SoapRpcMethodAttribute( _ "http://pencilsellers.org/pencil/GetSearchValues", _ RequestNamespace:= _ "http://pencilsellers.org/pencil/discovery", _ ResponseNamespace:= _ "http://pencilsellers.org/pencil/discovery")> _ Public MustOverride Function GetSearchValues() As _ PencilSearchValues '<remarks/> <System.Web.Services.WebMethodAttribute(), _ System.Web.Services.Protocols.SoapRpcMethodAttribute( _ "http://pencilsellers.org/pencil/Search", _ RequestNamespace:= _ "http://pencilsellers.org/pencil/discovery", _ ResponseNamespace:= _ "http://pencilsellers.org/pencil/discovery")> _ Public MustOverride Function Search( _ ByVal searchTerms() As SearchTerm) As Pencil() End Class
The DiscoveryBinding class is defined as a MustInherit class, which means that you cannot create a new instance of this class directly. You must create your own class that inherits this class. There are two functions defined in this class: GetSearchValues and Search. Both of the definitions for these functions have the MustOveride keyword, which indicates that the class you create that inherits from the DiscoveryBinding class must have functions that override these two functions. Our main job as creators of an implementation of this XML Web Service is to write the code for these two functions that will be executed when we receive requests through the Pencil Discovery XML Web Service interface.
Equally important are the function names and parameters defined for us in this base class, and the various attributes that decorate the class and function definitions. These attributes not only define our class as a Web Service and our functions as Web Methods, they also configure the various namespaces that correspond to the XML namespaces used to define our SOAP message schema. These namespaces came directly from the defined namespaces in PencilDiscovery.WSDL. Our XML Web Service must use the same namespaces that are defined in the WSDL, or we will be unable to accept the incoming requests.
Despite that this is a fairly simple interface, it is safe to say that compared with trying to come up with all the subtleties of the attributes for our class and methods, the use of WSDL.EXE to create this code is extremely efficient. It allows us to avoid what would otherwise be a tedious process. Also included in the PencilDiscovery.vb code are class definitions for all the complex types defined in the WSDL. It really does make our job significantly easier.
Creating the Web Service Class
Normally if you were creating an XML Web Service in Visual Studio .NET, you would create a class that inherits from the System.Web.Services.WebService class. In our case, WSDL.EXE created a base class for us that inherits from the System.Web.Services.WebService class, so we will simply create a class that inherits from the WSDL.EXE generated DiscoveryBinding class instead. Our initial class declaration might look something like this:
Public Class PencilDiscovery Inherits DiscoveryBinding
Though the base class has the attributes defined that make a class a Web Service, we still need to set the attributes for our class. A quick way of doing this might look like this:
<WebService( _ Namespace:="http://MyPencilServiceHost/pencilservice"), _ WebServiceBindingAttribute( _ Name:="DiscoveryBinding", _ Namespace:="http://pencilsellers.org/pencil/discovery"), _ System.Web.Services.Protocols.SoapRpcService()> _ Public Class PencilDiscovery Inherits DiscoveryBinding
The WebService attribute indicates that our class is an XML Web Service and our target namespace is set to the URL for where our particular endpoint will lie. The WebServiceBindingAttribute attribute sets the name of the binding that is used in the WSDL and defines the namespace for that binding. The SoapRpcService attribute indicates that this is an RPC-type XML Web Service, and not a document type of XML Web Service.
There is a problem, however, with leaving our attribute settings the way they are above. If we access the endpoint where we are creating our XML Web Service, and specify the "?WSDL" query string in order to see the WSDL that this XML Web Service supports, then we will see a very complex interface definition with all the various methods and complex structures defined. This is not a huge problem, but it leaves the possibility for inconsistencies between our XML Web Service and the interface defined by our industry standardized WSDL. What we would like to see instead is the WSDL indicated by our endpoint simply deferring to the industry standard WSDL for the definition of the supported interface. In order to tell our XML Web Service to do this, we have to modify the WebServiceBindingAttribute attribute to point to the remote WSDL. Our final class definition will look like this:
<WebService( _ Namespace:="http://MyPencilServiceHost/pencilservice"), _ WebServiceBindingAttribute( _ Name:="DiscoveryBinding", _ Namespace:= _ "http://pencilsellers.org/pencil/discovery", _ Location:= _ "http://www.pencilsellers.org/wsdl/pencildiscovery.wsdl"), _ System.Web.Services.Protocols.SoapRpcService()> _ Public Class PencilDiscovery Inherits DiscoveryBinding
The only difference between this class definition and the previous one is the addition of the Location parameter in the WebServiceBindingAttribute attribute. The location parameter tells our XML Web Service that the definition for the DiscoveryBinding binding is defined at http://www.pencilsellers.org/wsdl/pencildiscovery.wsdl. Remember that pencilsellers.org is the fictitious name we came up with for the industry standards body in the pencil selling business. Thus it makes sense that these WSDLs are located on the pencilsellers.org server. Once we have defined the remote binding, we have to actually use it within our class before our endpoint will indicate the proper WSDL. Therefore we have to associate some of the Web methods in our class with the DiscoveryBinding binding.
There were two Web methods defined in the DiscoveryBinding base class that WSDL.EXE generated for us: GetSearchValues and Search. When we first created our PencilDiscovery class, which inherits from the DiscoveryBinding base class, you might have noticed some compilation errors if you tried to build it. That was because the two Web methods in the base class were defined with the MustOverride keyword. Our class must override these two methods. The easiest way to do this is to use the class browsing tool at the top of your code window. In the Class Name box on the left, click Overrides. The available overrides are now in the box on the right. Figure 2 shows the overrides list, which includes the GetSearchValues and Search functions.
Figure 2. The list of overrides available from the WSDL.EXE-generated base class
Notice that both the GetSearchValues and Search functions are in italics in order to indicate that they must be overridden. By simply selecting them, the appropriate override functions will be created, and your cursor will be moved into position to start adding your own code to the functions. Not only will the functions be defined, but the attributes in the base class generated by WSDL.EXE will be added to your function declarations. The result for the GetSearchValues function looks like this:
<System.Web.Services.WebMethodAttribute(), _ System.Web.Services.Protocols.SoapRpcMethodAttribute( _ "http://pencilsellers.org/pencil/GetSearchValues", _ RequestNamespace:= _ "http://pencilsellers.org/pencil/discovery", _ ResponseNamespace:= _ "http://pencilsellers.org/pencil/discovery")> _ Public Overrides Function GetSearchValues() As PencilSearchValues
The wizard does most of the work for us, but we still need to make the association of our Web method with the DiscoveryBinding binding defined in the copy of PencilDiscovery.WSDL on pencilsellers.org. In order to do this, we must add a binding parameter to the SoapRpcMethodAttribute attribute. Luckily, modifying the attribute parameters for the function does not cause the override syntax to break, as would occur were we to modify one of the actual function parameters. With the additional binding parameter, our function definition looks like this:
<WebMethodAttribute(), _ System.Web.Services.Protocols.SoapRpcMethodAttribute( _ "http://pencilsellers.org/pencil/GetSearchValues", _ RequestNamespace:= _ "http://pencilsellers.org/pencil/discovery", _ ResponseNamespace:= _ "http://pencilsellers.org/pencil/discovery", _ Binding:="DiscoveryBinding")> _ Public Overrides Function GetSearchValues() As PencilSearchValues
Once we associate at least one Web method with the binding, an interesting thing happens to the WSDL indicated by our endpoint. Instead of defining the entire interface in a local, long-winded WSDL, the .asmx file uses the import tag to indicate that our Web Service is defined in the WSDL on pencilsellers.org. Our endpoint's WSDL simply looks like this:
<?xml version="1.0" encoding="utf-8"?> <definitions xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:i0="http://pencilsellers.org/pencil/discovery" xmlns:tns="http://MyPencilServiceHost/pencilservice" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" targetNamespace="http://MyPencilServiceHost/pencilservice" xmlns="http://schemas.xmlsoap.org/wsdl/"> <import namespace="http://pencilsellers.org/pencil/discovery" location= "http://www.pencilsellers.org/wsdl/pencildiscovery.wsdl" /> <types /> <service name="PencilDiscovery"> <port name="DiscoveryBinding" binding="i0:DiscoveryBinding"> <soap:address location= "http://localhost/PencilService/PencilDiscovery.asmx" /> </port> </service> </definitions>
If you have followed the procedure I have outlined, and you still see a lengthy WSDL when you access your endpoint with the "?WSDL" query string, it is probably because your endpoint is implementing HTTP POST and HTTP GET bindings. To eliminate these extra bindings, add the following section in your Web.config file inside the configuration/system.Web tag:
<webServices> <protocols> <remove name="HttpPost" /> <remove name="HttpGet" /> </protocols> </webServices>
Adding this section will remove support for the extra protocols and cause our interface to only use the SOAP protocol.
To complete the remaining function and Web Service definitions, we must add the DiscoveryBinding binding to the Search Web method, just like we did for the GetSearchValues Web method. We also need to perform the same procedures to define our XML Web Service for the ordering part of our interface. We created a second .asmx file for the PencilOrder.WSDL defined in our industry interface. Again we used WSDL.EXE to generate a MustInherit base class for our XML Web Service. Finally, once all the definitions were created for our classes and Web methods, some rather unremarkable SQL code was written to provide at least the illusion of a functioning Pencil Sellers XML Web Service. All the source code for this project is included in the download at the top of this article.
Registering the Pencil-Selling XML Web Service with UDDI
Now that we have created the proper XML Web Service that implements our standard interfaces for selling pencils, and we have installed a version of the functioning service on our Web server, it is time to let the world know what we have done. In order to do this, we must register ourselves in the UDDI registry. There are three main pieces of information that go into the UDDI registry: your business, the tModel for your interface (that's the WSDL), and the endpoint location for your service. Because we are using the industry standard WSDL, the tModel we are using should already be registered. We will just need to indicate the endpoint for our XML Web Service, and what business we are associated with.
Finding the tModel
Before we do anything else, we should verify that the industry standard WSDL is already registered. To do this we can go to http://uddi.microsoft.com (Microsoft's copy of the globally synchronized UDDI Registry). There is a Search-by-business-name option available on the homepage, but we want to find a tModel and not a business, so select the link to the Advanced Search page right below the Search box. The Advanced Search page allows us to search by a number of different fields in the UDDI database. One of the options is to search by tModel. Select this option and enter "pencil" in the "Search for" text box. The results are shown in Figure 3.
Figure 3. Using the Advanced Search to find the industry standard tModels
As you can see, we found two entries, which represent the discovery WSDL and the order WSDL we defined. Notice that their name indicates that our fictitious standards body, PencilSellers.Org, registered them. We must make a note of the names of these tModels for use when we register our XML Web Service.
Registering Our XML Web Service
If you have not already set up your business in UDDI, see Scott's earlier At Your Service column, Web Service Description and Discovery Using UDDI, Part II. Once your business has been set up, it is just matter of adding your XML Web Service to the service offerings. To start this process, click Add a Service under the Services section of the UDDI Administration page for your business. Figure 4 shows the first piece of information you will need to enter, a name and description for your XML Web Service.
Figure 4. Entering the name for the Discovery XML Web Service in UDDI
In our example, we are creating this service for the fictitious Cold Rooster Consulting Company's site that MSDN created for hosting sample XML Web Services. We note in the description that this XML Web Service is based on the definition from our standards organization, PencilSellers.org. After we specify a name and description, we have the option of adding a classification or a binding for our XML Web Service. We are not going to bother with a classification, but we do need to create a binding that will associate our UDDI service entry with our particular endpoint. So we click Define new binding under the Bindings section. The result is shown in Figure 5.
Figure 5. Entering the endpoint information in UDDI
The access point for our binding is the URL where our .asmx file is located for the Discovery Service. It is an HTTP URL, and we provide a description for this particular endpoint. If we click Next, we will see that our binding will be listed in the Bindings section of the service description page. You could potentially have multiple endpoints bound to your service. Now click Edit next to your binding and you will be taken back to a page where you can modify the information shown in Figure 5. Just below the Binding Detail section, there is a section called Specification Signatures. Click Add specification signature. You will be prompted to specify the tModel for this endpoint. This is where we enter the name we found using the tModel search that we did earlier. For the discovery interface, the tModel name was http://pencilsellers.org/pencil/discovery. Figure 6 shows the results for our binding after we do this.
Figure 6. Adding the industry standard tModel signature for our XML Web Service
We are then prompted to add any details we may have as far as links to further documentation, as well as a description of this signature. At this point, we can simply click Continue through the displayed screens until we are back to our business listing. We should see the Pencil Discovery Service added to the list of services provided by our business, and it is now available for anyone to use from their client applications. Similar steps will need to be performed to register our Pencil Order Service with UDDI.
We have now taken the second step in developing powerful industry applications that take advantage of XML Web Services and UDDI. We have taken an industry standard WSDL and used it to develop an XML Web Service using ASP.NET that implements the exact definition defined by the WSDL. We have then used UDDI to register our specific XML Web Service as implementing the standardized interface, so that anyone can find our service and order pencils from us.
In our next column, Scott will take the third and final step: writing a client application that queries UDDI for services that implement the standard interface, and provides the end user with the options of querying the various businesses for their pencil availability and placing orders with any of the businesses listed. Pencil suppliers will be able to get their product into the mix by simply creating their own standardized pencil-ordering Web Service, just as we have done here. The inevitable emergence of millionaire pencil-reselling tycoons will soon follow.
At Your Service