Using UDDI at Run Time, Part I

 

Karsten Januszewski
Microsoft Corporation

December 2001

Summary: This article walks through using UDDI at run time and discusses how UDDI, both the public registry and UDDI Services available in Microsoft Windows Server 2003™, can act as infrastructure for Web services to support client applications. (9 printed pages)

Contents

Introduction
UDDI Runtime Infrastructure
Sample Scenario
Creating the Web Service: A C# .NET .asmx
Consuming the Web Service: A C# Windows Form .NET Client
Other Scenarios
Conclusion

Introduction

UDDI (Universal Description Discovery and Integration) is often called the "yellow pages" for Web services. While the yellow pages analogy is useful, it doesn't convey the complete story of how UDDI can be incorporated into a Web-service-based software architecture. The yellow pages analogy only speaks to design-time usage of UDDI—the ability to discover and consume Web services by searches based on keywords, categories or interfaces. From a design-time perspective, the yellow pages analogy is accurate: just as the yellow pages categorizes and catalogs businesses and their phone numbers, UDDI categorizes and catalogs providers and their Web services. A developer can find WSDL files and access points in UDDI and then incorporate those Web services into client applications.

However, UDDI offers more than just design-time support. The yellow pages analogy does not speak to how UDDI offer run-time support. UDDI plays a critical role after the discovery process is complete. The ability to programmatically query UDDI at run time allows UDDI to act as an infrastructure to build reliable, robust Web service applications.

UDDI Runtime Infrastructure

Consider some of the concerns and issues at hand after a Web service has been integrated into a client application. A key issue is the inability to predict, detect, or recover from failures of the provider that hosts the Web service. What recourse does the client application have if that Web service fails? How can the application recover gracefully and dynamically from an unsuccessful Web service call?

Similarly, from a Web service provider perspective, how can the owner of that Web service provide dynamic updates about changes? Consider the situation where the Web service is moved to a new server. How can the clients of that Web service be informed of this change in an efficient way? How can the owner dispense that information at run time, so that all of the clients of that Web service will not break?

It is in scenarios such as these that UDDI can play an essential role in providing an infrastructure to support Web services at run time. UDDI addresses these "quality of service" issues by defining a calling convention that involves caching binding information such as the access point of that Web service as well as other parameters specific to that particular implementation. When failures occur, a client can issue a run-time UDDI query, refreshing the cached information with the latest information.

The pattern for such a convention would be as follows:

  1. Discover a Web service in UDDI. Consume the WSDL file for this Web service (represented by a UDDI tModel) and the implementation details such as the access point and any other configuration information, all of which is contained in a UDDI bindingTemplate.
  2. Prepare a client application for a particular Web service. In the client application, cache the unique bindingKey of the Web service so that, when the application is first used, it can retrieve all the information it needs from UDDI.
  3. When the application invokes the remote Web service, use the cached data that was obtained from a UDDI Web registry.
  4. If the invocation fails, use the bindingKey value and the get_bindingTemplate API call to a UDDI registry to get a fresh copy of the binding information.
  5. Compare the new information with the old: if it is different, retry the failed call. If the retry succeeds, replace the cached data with the new data, storing it for later calls. However, if the binding info returned is the same, the provider has not made any updates and the application should throw an error. Similarly, if there is new binding information and the call continues to fail, the application should throw an error.

From the perspective of a Web service provider, the provider needs to be cognizant that the UDDI entry for that Web service can be updated when appropriate. When the provider of a Web service needs to redirect traffic to a new location or backup system, the provider only need to activate the backup system and then change the access point in the UDDI registry. This approach is called retry on failure and provides for a mechanism for clients to recover from failures at run time.

Sample Scenario

Let's take a look at a sample of how such a pattern might work. This sample scenario will consider the scenario of a fictional company that needs to provide real-time sales data to a department inside its organization. As such, this Web service is one not exposed publicly, but rather used inside the firewall.

To begin, we will need a Web service. In this case, we will expose a very simple Web service that supports one method, GetSalesTotalByRange, which allows client to get a snapshot of real-time sales data based on a date range.

Then, we will create a client that consumes this Web service. We will configure the client to cache the access point and bindingKey information, and set up a mechanism for the client to refresh its cache from a UDDI registry in the event of failure.

Creating the Web Service: A C# .NET .asmx

The Microsoft .NET Framework makes writing Web services very easy. For this sample, we will create a simple Web service with a single method, GetSalesTotalByRange, which takes two dates as input parameters, and returns a double. Below is an .asmx page, SalesReport.asmx, that achieves this goal:

<%@ WebService Language="c#" Class="SalesReportUSA.SalesReport" %>
using System;
using System.Web.Services;
namespace SalesReportUSA
{
   [WebService(Namespace="urn:myCompany-com:SalesReport-Interface")]
   public class SalesReport : System.Web.Services.WebService
   {
      [WebMethod]
      public double GetSalesTotalByRange ( System.DateTime startDate, System.DateTime endDate )
      {
         return 5000.00;
      }   
   }
} 

This page should be added to a virtual directory. For the client sample to work, create a virtual directory entitled SalesReportUSA (https://localhost/SalesReportUSA/SalesReport.asmx). Note that this Web service is always returning 5000.00 as its return value. (If only sales reports could be so predictable!) A real-world application would of course make a database call to retrieve this information. For the purposes of this sample, a hard-coded value is sufficient.

Upon deploying this Web service, the next step would be to register it in a UDDI registry. This UDDI registry would be an internal UDDI Server, as it would not make sense to expose this Web service to the public. Microsoft provides UDDI Services natively with Microsoft® Windows® Server 2003. (See the Windows Server 2003 Web site for more about this feature. If you do not have Microsoft Windows Server 2003, you can also use the Microsoft UDDI Software Development Kit (SDK) to install UDDI on a local machine.

To register a Web service in UDDI, there are two options: You can register using a Web user interface or you can register the Web service programmatically using the UDDI SDK. Using the SDK is convenient—you can see the code sample published in the Web Service Description and Discovery Using UDDI column. Using either method, you first register the WSDL file for your Web service as a tModel. UDDI tModels are XML entities used to represent interfaces and abstract meta-data; thus, WSDL files are signified as tModels. Then, you would register the access point for that Web service as a bindingTemplate. UDDI bindingTemplates are XML structures used to represent implementation details about a given Web service. (For more about the UDDI Schema and its relationship to WSDL, see both http://www.uddi.org and a UDDI "best practices" document, Using WSDL in a UDDI Registry, Version 1.07.)

Below is a sample of the resulting UDDI bindingTemplate structure when we completed these steps using UDDI Services. Note that the serviceKey, bindingKey and tModelKey were all generated by UDDI and are unique to the entities we saved. The keys generated by other UDDI registries will be different.

<bindingTemplate serviceKey="ef25102d-2171-454c-ade9-3dd7a4a914ee" 
      bindingKey="f46fced9-2b8a-4817-b957-f8d8aca0a2f9">
   <accessPoint URLType="http">
      https://localhost/SalesReportUSA/SalesReport.asmx
   </accessPoint>
   <tModelInstanceDetails>
      <tModelInstanceInfo tModelKey=
            "uuid:b28fe40a-ea62-4657-88d5-752d8a6cdf77" />
   </tModelInstanceDetails>
</bindingTemplate>

In this structure, we have highlighted the accessPoint and bindingKey for this Web service. It will be important for our client to have knowledge of these two pieces of information. Also, if a client needed to obtain the WSDL for this Web service, the client could use the tModelKey to query UDDI for that tModel.

Consuming the Web Service: A C# Windows Form .NET Client

At this point, we can switch roles, and take a look at the client half of the application. At design time, we would presumably discover this Web service in UDDI. We would download the corresponding WSDL file and generate a proxy class either using Add Web Reference from Microsoft Visual Studio® .NET or WSDL.exe. (WSDL.exe is a command line tool that is part of the Microsoft .NET Framework SDK.)

We can proceed with writing the logic in the client application. In this case, it will be a C# Windows Form application called SalesReportClient.exe that allows users to query for Sales Report information.

First, we will need to add the UDDI .NET SDK classes to our project, which are available for download. (Microsoft UDDI SDK version 1.5.2 is compatible with Visual Studio .NET Beta 2. Microsoft UDDI .NET SDK Beta version 1.75 is compatible with the Visual Studio .NET Release Candidate.) The using declarations should be as follows:

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Configuration;
using System.Windows.Forms;
using System.Data;
using Microsoft.Uddi;
using Microsoft.Uddi.Binding;

Then, we need to store the access point for the UDDI server where we found this Web service—after all, UDDI is a Web service itself. To do this, we create an application configuration file for this .exe, where we store the location of the UDDI Server. We will also store the bindingKey of the Web service in this configuration file. The use of XML configuration files in .NET allows us to add any number of appSettings, which is then available through a collection for our application.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
   <appSettings>
      <add key="UDDI_URL" value="https://localhost/uddi/api/inquire.asmx" />
      <add key="bindingKey" value="f46fced9-2b8a-4817-b957-f8d8aca0a2f9" />
</appSettings>
</configuration>

In this case we are pointing to a Microsoft UDDI Developer Edition server hosted on our own machine. The UDDI_URL could also be one of the public UDDI nodes, or a UDDI registry hosted somewhere internally in an organization. We save this file using the naming convention for configuration files: app.config. When our application is compiled, the configuration file will be placed in the /bin directory and be named after the name of the .exe itself.

This step we just completed—adding configuration information about our Web services—is not unlike how Visual Studio .NET exposes the URL Behavior property on each Web Reference that is added to a project. By changing that property to dynamic, Visual Studio .NET will create a config file with the access point of the Web service. What we have done above is to extend that concept further by providing the ability to re-query UDDI at run time. Consequently, our config file contains the access point of the UDDI node and the bindingKey of the Web service.

Now we can begin coding the application itself. First, we create a text box, a label, a button, and two datetime pickers. Then we establish some global variables:

   //some variables for the application
   private string InquiryURL = null;
   private string bindingKey = null;
   private string accessPoint = null;
   private BindingTemplate bt;
   private double salesFigure = 0;

Then, when the form is instantiated, we initialize these variables:

      
   public Form1()
   {
      //
      // Required for Windows Form Designer support
      //
      InitializeComponent();

      //populate variables from config file
      InquiryURL = ConfigurationSettings.AppSettings["UDDI_URL"];
      bindingKey = ConfigurationSettings.AppSettings["bindingKey"];
      bool InitCache = RefreshCacheFromUDDI();
      if ( InitCache == true ) accessPoint = bt.AccessPoint.Text;

   }

The RefreshCacheFromUDDI() function is used to query the UDDI Server to find the access point. We are using the UDDI SDK to perform a UDDI API call, GetBindingDetail, passing the bindingKey as a parameter.

   private bool RefreshCacheFromUDDI()
   {
      //using the UDDI SDK, set the UDDI access point
      Inquire.Url = InquiryURL;
      //create a get_bindingDetail UDDI API message
      GetBindingDetail gbd = new GetBindingDetail();
      //add the bindingKey
      gbd.BindingKeys.Add( bindingKey );
      try
   {
         BindingDetail bd = gbd.Send();
         //if we are successful, update our bindingTemplate object
         //with the first template in the returned collection
         bt = bd.BindingTemplates[0];
         return true;
      }
      catch (Exception err)
      {
         textBox1.Text += err.Message;
         return false;
      }
   }

For the duration of our application, we will hold the location of the accessPoint in a variable. If the user were to restart the application, it would re-query UDDI for the accessPoint, so that the application always has the most recent change to the Web service. It would be possible to cache this data to the file system or to a database, if one wanted to.

We then create a function for invoking the Web service itself:

      private bool InvokeWebService()
      {
         localhost.SalesReport sr = new localhost.SalesReport();
         //set the access point for the proxy class
         sr.Url = accessPoint;
         try
         {
            salesFigure = sr.GetSalesTotalByRange( dateTimePicker1.Value, 
               dateTimePicker2.Value );            
            label1.Text = "Sales Figure For Dates Selected: $" + 
               salesFigure.ToString();
            textBox1.Text += "Web Service invocation successful!";
            return true;
         }
         catch (Exception err)
         {
            textBox1.Text += err.Message;
            return false;
         }
      }

Finally, when the user clicks the button, the application will try to invoke the Web service.

   private void button1_Click(object sender, System.EventArgs e)
   {
      Cursor.Current = Cursors.WaitCursor;
      //try to invoke Web Service
      bool WebServiceSuccess = InvokeWebService();
      
      //if it fails for some reason, query UDDI
      if ( WebServiceSuccess == false )
      {
         textBox1.Text += "Web Service failed. Requerying UDDI 
            for new accesspoint.\n\n";
         bool UDDISuccess = RefreshCacheFromUDDI();
         
         //we were successful requerying UDDI,
         if ( UDDISuccess == true )
         {
            
            //compare the accessPoint with the new accessPoint
            //to determine if it changed
            if ( accessPoint.Equals( bt.AccessPoint.Text ) == false)
            {
               //because the accessPoint is different, it must be new
               //we reset our variable
               accessPoint = bt.AccessPoint.Text;

               //and attempt to invoke the web service again
               WebServiceSuccess = InvokeWebService();
               
               //we aren't able to invoke the Web Service with the 
                  new info 
               if ( WebServiceSuccess == false )
               {
                  textBox1.Text += "Web Service failed again. Updated 
                     accesspoint from UDDI didn't help!.\n\n";
               }

            }
            else
            {
               textBox1.Text += "No new information was provided from 
                  UDDI.\n\n";
            }
         }
         else
         {
            textBox1.Text += "UDDI refresh failed.\n\n";
         }
      }
   }

Notice how we are setting the accessPoint at run time for our Web service proxy class. Because all proxy classes derive from System.Web.Services.Protocols.SoapHttpClientProtocol, a series of properties are exposed by our proxy class, one of which is the .Url property. Setting this property allows us to specify the access point at run time. We then send our SOAP request over the wire. If we don't catch an exception, all is well and the data returned from the Web service is displayed in the form. However, if we do encounter an exception, this function will return false, and our calling code will try to refresh the access point from UDDI, reusing the RefreshCacheFromUDDI() function.

Once we have re-queried UDDI, we will compare the accessPoint returned from UDDI with our old access point. If the access point is identical, then the provider has not updated UDDI with any new information and there is nothing we can do, other than try to contact the provider of the Web service to inform them that the Web service is not responding. However if the access point retrieved from UDDI is different, we can attempt to invoke the Web service again.

To simulate failure, change the name of the Web service to something different. Try running the application. Then, update the UDDI entry with the new name for the Web service. Run the application again. The application will discover the new access point in UDDI, successfully query the new service, and save this information. If you close the application entirely and reopen it, you should be able to invoke the Web service again on the first attempt.

Other Scenarios

This retry on failure sample is just area where UDDI can serve as supporting infrastructure at run time for a Web service client. In future columns, we will take a look at other scenarios, including:

  • Optimized Access Point Discovery—Perhaps there are multiple Web services which support a common interface hosted on different servers in different physical locations. It might make sense for a client to use the Web service closest to them. With a run-time UDDI look-up, a client could determine the best access point based on geographical categorizations of the different services or other meta data that has been associated with that implementation.
  • Aggregation of Data from Based on Common Interface—Perhaps a standard Web service interface for searching catalogs is defined in WSDL. Multiple vendors in that industry could implement that interface as a Web service and publish the access point in UDDI. At run time, a client application could dynamically discover those access points and issue queries to aggregate that catalog data. In this way, a polling application could take advantage of run-time UDDI data.

Additionally, we will look at optimizing WSDL files so as to make them act truly as interface description files.

Conclusion

UDDI provides important run-time functionality that can be integrated into applications so as to create more robust, dynamic clients. By using UDDI as infrastructure in a Web services architecture, applications can be written to be more reliable.