Wrox Press
ASPToday
       995 Articles
  in the Solutions Library
  Log off
 
 
 
Wrox Press Programming Books!  
 
ASPToday Subscriber's Article Steven Livingstone
Where’s my Plane? End to End Web Services with UDDI 2.0 and XML Web Services
by Steven Livingstone
Categories: .NET Framework, Other Technologies
Article Rating: 5
Published on December 5, 2002
 
Content Related Links Discussion Comments Index Entries Downloads
 
Abstract
This article, by Steven Livingstone, will walk through a real world use of web services – how to check flight information on the Internet using UDDI and XML Web Services in .NET.

Specifically, this article will cover:
  • A brief overview of UDDI and why it is needed
  • Some of the problems when working with web services
  • Ideas behind dynamic discovery of web services
  • An application architecture for our web service
  • Querying UDDI for business and service information
  • Using UDDI for dynamic service invocation
  • Using a flight information web service
Steven will demonstrate a practical application that uses a web service to get flight information. The Web Service will provide all data for the application, such as city and time information, as well as handling requests and returning the results of requests. This will demonstrate using various UDDI interfaces to dynamically get business and service information from a UDDI directory. He will then show how to get further details on that web service, bind to it using the available WSDL description of the web service and then bind to a service to make a query to return some flight information.

Finally the article will show how backup services can be dynamically found at run time by querying the UDDI directory and displaying the information to the user.
 

Article Information
Author Steven Livingstone
Chief Technical Editor John R. Chapman
Project Manager Helen Cuthill
Reviewers Todd Meinershagen & Brady Gaster

 
Article

Introduction

This article will walk through a real world use of web services - how to check flight information on the Internet using UDDI and XML Web Services in .NET.

I will demonstrate the following:

  • A brief overview of UDDI and why it is needed
  • Some of the problems when working with web services
  • Ideas behind dynamic discovery of web services
  • An application architecture for our web service
  • Querying UDDI for business and service information
  • Using UDDI for dynamic service invocation
  • Using a flight information web service

I am going to demonstrate a practical application that uses a web service to get flight information. The Web Service will provide all data for the application, such as city and time information, as well as handling requests and returning the results of requests. This will demonstrate using various UDDI interfaces to dynamically get business and service information from a UDDI directory.

I will then show how to get further details on that web service, bind to it using the available WSDL description of the web service and then bind to a service to make a query to return some flight information.

Finally I will show how backup services can be dynamically found at run time by querying the UDDI directory and displaying the information to the user.

System Requirements

To run this application, the following is recommended:

  • IIS 5.0 +
  • .NET Framework
  • An Internet Connection
  • UDDI 2.0 SDK

I am also assuming that you have heard of UDDI and have an idea of how it fits into the web services architecture - the resources at the end of the article point you to some ASPToday articles explaining these concepts if you require further information. Let's get on and look at the problem I will solve in this article.

Application Setup

The only setup up you must do is to ensure that you download the UDDI 2.0 SDK from http://msdn.microsoft.com/downloads/sample.asp?url=/msdn-files/027/001/874/msdncompositedoc.xml&WROXEMPTOKEN=740130Zp7nkFw0eFCA6t7BIRslhttp://msdn.microsoft.com/downloads/sample.asp?url=/msdn-files/027/001/874/msdncompositedoc.xml.

Defining the Problem

The problem is that everyone (airlines, travel companies, portals etc.) create their own branded web pages to display this data which may also have to appear on multiple devices. Smaller companies can afford less and so there is always a gap in functionality between large web sites and smaller web sites. Furthermore, the companies that develop this software are seldom the companies that should ever be allowed near a user interface (and I include myself in that category) and so things like usability, accessibility and other important concepts are missed by all but the largest companies with huge budgets (and my recent experience leads me to argue with that point as well). The result is that fewer people use services from smaller sites and so there is less encouragement for smaller sites to develop such functionality. Sure, a smaller site may be able to offer a catered set of functionality or be visually designed for specific types of users; things larger sites find it extremely difficult to do, however, in the end the smaller sites just can't offer the functionality of those larger sites and so always lose out.

Improving the User Experience

The above discussion is important as it helps you understand one of the reasons why web services are to become so popular with businesses. The trend will certainly be toward allowing better integration of web sites and the applications behind those web sites all via web services.

Taking this a step further, however, is it really necessary to have to identify partners providing functional components for our application up front? Businesses typically have many service providers for different parts of their business and it isn't uncommon that over time these service providers change. For example, the business providing office supplies may well change over time. So what happens if you start with a low cost solution and decide that your site is going so well that you wish to upgrade the service to another provider who offers further services, such as monitoring and logging? Or perhaps the reverse is true, where you see a part of the site is not yet as successful as you had hoped. You will have to modify the code that uses the current web service to use the new service. This becomes more worrisome if you consider the case where the link between your site and the service provider goes down or the provider stops offering the service for some reason. If you happen to run a site that uses many services, but don't want to continually bring in a consultant each time the service changes, then there has to be a better solution.

At the moment there is still a lot of learning to do to determine how web services will be effective - Universal Description, Discovery and Integration (UDDI) is a major focus of that learning. The idea of UDDI is two-fold that (a) you can find a web service and (b) you can integrate it within your application - potentially dynamically at run time.

In this case I want to be able to find flights for a given day and/or view the current flight status. I am going to use the live UDDI directory at https://uddi.ibm.com/ubr/registry.html?WROXEMPTOKEN=740130Zp7nkFw0eFCA6t7BIRsl to return information on a web service that satisfies our requirements of finding the current flight schedule and finding status information on a specific flight.

The web service is in fact one that I have created and hosted for this article - although it does query live flight information at another site. The original intention was to be able to use some of the flight web sites registered on UDDI, but as of late these have required access codes before you can use them and they provide no information about where you can get these access codes. My second intention was to then use an existing top travel site to gather flight information. However, these sites are implemented in ways that make it technically difficult, if not impossible to simulate them.

I could write about some of the problems I will see as I migrate from web applications to web services, such as documentation, application state, session identifiers and asynchronous operations. All of these aspects would hinder my ability to create even a simple service for this article. But that would be a topic for another time.

Application Architecture

The web service I am going to use crosses four boundaries:

  • The flight information database
  • The web service implementation
  • The web service consumer
  • The web site user

The application I am about to create will use such a service from the UDDI directory and propose an initial way of creating web services for your site using information from the UDDI directory. The consumer will first go with the full intention of using a web service from a well-known service provider that can be found in the UDDI directory. The web service client will be created at design-time.

If however, there is a problem during the invocation of this service, the UDDI directory will be looked up at run time and a backup service binding will be sought. This will be common in the real world where you don't want downtime to affect the running of your services. In our case, the first backup binding will actually be an email address that will allow you to request details via email.

Finally, there may be a problem with the SMTP server that relays emails sent within an organization, or the actual mail server may have a problem. In this "worst case scenario", the service resorts to providing a telephone support number that the web service can display to users. This will allow you to call and still maintain your business despite the network problems. This telephone number can again be looked up in the UDDI directory at run time.

You may at this point be asking yourself, "Why bother putting all of this in a UDDI directory and not just put it directly in the web page, as is common today?" Well, for a start, the UDDI directory is a shared private or public directory that can be used by all consuming applications and therefore, all applications see the same updates immediately. Also, at the most basic level, you can add in updated service bindings to support your web services without having to reconfigure clients; on a public directory, you can add and remove web service bindings from other providers without requiring the client to know anything about them. So if I wanted to add a new binding to a partner also hosting the web service, it is as simple as updating the UDDI directory. So all clients who dynamically check the directory (perhaps once an hour) will find a new service binding available. This is particularly useful if you need to balance the load on your web service servers.

An architecture diagram of what I will be doing in this article is shown below:

We should now have a good understanding of what is going on architecturally, so let's now look at the code that accomplishes this.

UI Design

The windows application I am going to develop will have two key components to its interface. The first will be the main page where you can enter details of your flight parameters based on an interface that is dynamically populated. The second will display specific information on any of the flights you choose in a datagrid control.

To allow us to simulate a real world application, a set of flags have also been added to the application that will allow us to simulate failures with the web service itself and failures with the email server (or when the user may not have access to an email client).

Let's look at the main form first.

The captions in the diagram explain what is going on in the application from a user interface point of view. When a user clicks an item in the flightList box, the FlightDetails tab is displayed and the information for that specific flight is shown. The FlightDetails tab is shown below.

So, now that we understand what the user will see, let's look at what they won't see - the code underneath that is pulling information from the local machine, the UDDI directory and a remote web service all into a single view.

The Code

A new Windows Form application written in VB.NET was created in Visual Studio .Net and a Web Reference added. To do this, right click on the References tab in Solution Explorer and select Add Web Reference , which will cause a new window to pop up. In the address bar, enter http://www.deltabis.com/ASPTodayLive/FlightFinder/flightservice.asmx?wsdl&WROXEMPTOKEN=740130Zp7nkFw0eFCA6t7BIRsl and hit Enter. A WSDL file will be displayed in the window on the left - this defines the interface to the web service. Now, click the Add Reference button and Visual Studio will download the WSDL file and generate a VB.NET proxy class that represents a typed view of the WSDL file. This will allow you to access the web service as though it were a local class installed in your machine and get early binding and IntelliSense. Now that you have added a reference to the web service, you have to Import the proxy class into your project, so at the top of the Form1.vb file, the following Imports statement has to be added:

Imports FlightsWSClient.com.deltabis.www

I am also going to use some classes in the Xml namespace that must be imported, and as this article is about UDDI, I am also going to be using some of the UDDI namespaces. You must also add a reference to UDDI 2.0 called Microsoft.Uddi.Sdk.dll - I am going to use the UDDI 2.0 interfaces for this sample, as this is what will be supported by the UDDI service with Windows .NET Server. These are shown below:

Imports System.Xml
Imports Microsoft.Uddi
Imports Microsoft.Uddi.Binding
Imports Microsoft.Uddi.Api

We also need to define some global variables that will be used throughout our code:

Dim DestinationsTable As New Hashtable()
Dim PeriodsTable As New Hashtable()
Dim businessName As String = "deltabis"
Dim serviceName As String = "FlightService"
Dim UDDIServer As String = "http://test.uddi.microsoft.com/inquire"

The DestinationsTable will store each travel destination that will be returned from our web service; the PeriodsTable is similar, but will store "time of day" information that is also returned from the web service. The businessName variable is very important as it will determine the name of the business providing this service and the serviceName variable defines the name of the service I want to use. I could store the GUID's for the business and service here to avoid the lookups for this information, but in the real world you are unlikely to want to hardcode GUID information. (You would also have the opportunity of making the business and services names configurable by the user, which again would be easier than having them enter GUID's). Finally, the UDDIServer variable defines the URL to the server I am going to use for our UDDI queries - in this case I am going to use Microsoft's test UDDI registry, called the UBR (UDDI Business Registry), but I could easily use any other UDDI server.

Now we can create the code to initialize the form. The data on the form is almost all driven by the web service we added earlier, so there is quite a bit of initialization that takes place before the user ever gets to use the form. As was mentioned earlier, there is also a check that services are running as expected. If there are problems, fallback services are instituted. The sequence of events is bulleted below:

  • Determine the UDDI server to use
  • Get the ID for the business whose services we want to use and some details on the business
  • Get the ID of the specific service we are interested in
  • Based on the current status of the application, get the type of service we want to invoke and run it. This may be one of web service, email service or telephone service depending on the simulated status of the server.
  • For the web service, invoke the remote service and populate the form

Initializing the Form

The InitializeDate() sub routine is used to do the work described above.

Private Sub InitializeData()
  Try
       'Configure the connection for the UDDI node that is to be accessed
     Inquire.Url = UDDIServer

The first thing to do is initialize the Url property of the Inquire class, which sets the connection for the UDDI server.

       '**************************************
       '****** Get Business Information ******
       'Create an object to find a business
       Dim findBusiness As FindBusiness = New FindBusiness()
       findBusiness.Names.Add(businessName)

       'the xml that is being sent
       DisplayXml(findBusiness, "Xml being sent in FindBusiness")

       'Send the prepared find business request
       Dim workingBusinessList As BusinessList = findBusiness.Send()

       'the xml that is returned
       DisplayXml(workingBusinessList, "Xml returned from" _
        "FindBusiness request")
       '****** End Get Business Information ******
       '**************************************

The first thing I have to do is find information about the business I intend to use for this service. I currently know only the business name, so I want to look up the UDDI directory to find out more about the business and get a list of the services offered by that business. The FindBusiness class allows you to add the name of a business to the Names collection and this is what is done below. Notice that the DisplayXml() method simply allows you to display the XML that is being sent to the UDDI server - I'll discuss this later on. The Send() method of the FindBusiness class is then used to query the UDDI registry and returns a BusinessList instance which contains a list of all businesses that matched this business name. In our case this will return a single business.

       '**************************************
       '****** Get Service Information ******
       Dim busInfo As Business.BusinessInfo = _
       workingBusinessList.BusinessInfos(0)
       Dim businessKey As String = busInfo.BusinessKey()

       msg.Text = "Today this service is brought to you by " _
              & busInfo.Name
       ad.Text = busInfo.Descriptions(0).Text

       'we now want to get the services offered by that business
       Dim findService As FindService = New FindService()
       findService.BusinessKey = businessKey

       'the service xml that is being sent
       DisplayXml(findService, "Xml being sent in FindService")

       Dim workingServiceList As ServiceList = findService.Send()

       'the xml that is returned
       DisplayXml(workingServiceList, "Xml returned from" _
              " FindService request")
       '****** End Get Service Information ******
       '**************************************

Now that I have the business details, I can find out a more about the business. I do this using the BusinessInfo class, which is set to the first (and only) business in the business list returned above. I can get the unique key for the business and I also display a message indicating the provider of the service (which could easily change) using the Name property of the BusinessInfo instance. I also display the first business description on the form (a business may have multiple descriptions of itself, which of course is also true in the real world).

Next I want to get information about the services offered by that business. This is accomplished with a combination of the FindService class and the unique business key. The BusinessKey property of the FindService instance is set and the Send() method is called, which will query the directory and return a ServiceList instance containing all the services offered by that business.

       Dim serviceKey As String

       '******************************************
       '****** Get Specific Service Details ******
       'We want to find the service being used in this application
       Dim serviceItem As Service.ServiceInfo
       For Each serviceItem In workingServiceList.ServiceInfos
              If serviceItem.Name = serviceName Then
                     serviceKey = serviceItem.ServiceKey
                    Exit For
                End If
       Next

Now that I know the services offered, I want to get the details on the specific service I am using in this application. To do this, I iterate over the ServiceInfos instance, which will give us a ServiceInfo instance that allows us to get specific details on the service. I can use the Name property and check this against the name of the service I am working with to determine where the current service I have is the service I want to work with. If it is not, I go to the next service in the collection. If it is I get the unique key for this service, store it in the serviceKey variable and exit the loop.

       'we now want to get the details of the service offered 
       'by that business
       Dim getServiceDetail As GetServiceDetail = New GetServiceDetail()

       getServiceDetail.ServiceKeys.Add(serviceKey)

       'the service xml that is being sent
       DisplayXml(getServiceDetail, "Xml being sent in GetServiceDetail")

       Dim serviceDetail As ServiceDetail = getServiceDetail.Send()

       'the xml that is returned
       DisplayXml(serviceDetail, "Xml returned from GetServiceDetail" _
              " request")
       '****** End Get Specific Service Information ******
       '**************************************************

With this service key I can use the GetServiceDetail class to get details on the specific service. You may ask, "Why not get all this information when working with GetService() ?" It's OK doing this if you have one or two services, but typically you will have many. Returning a large XML document containing all the information of every service (even if you are interested in only one service) would be a bigger performance hit than two simple calls returning relatively short messages. So, I add the serviceKey to the ServiceKeys collection of the GetServiceDetail instance and then use the Send() method to make the call. This will return a ServiceDetail instance containing all the information on the service specified which we work with next. I now have enough information on the business and service we are working with to start doing something interesting.

       'go through binding templates and find based on flags
       Dim template As BindingTemplate
       Dim svcDescription As String
       Dim svcAccessPoint As String

       '******************************************
       '****** Invoke the appropriate access based on error information 


     For Each template In serviceDetail.BusinessServices(0).BindingTemplates
       'stores the access point information
       Dim accessPoint As AccessPoint = template.AccessPoint  

Next I get a binding template, which gives me more information on how to interact with each service. I get information on each binding of a service from a BindingTemplate. Remember that although I may have a single service, that service can be realized by more than one binding. For example, there may be two URL's, each providing back up for one another or, as in this case, the other bindings may not be URL accessible services at all. This is very useful as most businesses that have adopted UDDI on the Internet to date do not yet provide web service implementations, but rather HTML web pages. In this sense a UDDI service is not necessarily a programmatic service! So, back in the code I get the BindingTemplates of the first (and only) service and iterate through each. I get the access point of each binding, which may be an HTTP URL, an email address or even a telephone number. Following this, there are three checks performed, which evaluate whether there is a simulated fault with the service and then what the access type for the current binding is. These access types could be HTTP, SMTP, or a telephone number.

                If (rdNone.Checked And (accessPoint.URLType() = _
                  URLType.Http)) Then    'we can use the web service version    
                    svcDescription = template.Descriptions(0).Text()
                    svcAccessPoint = accessPoint.Text
                    InvokeService(svcDescription, svcAccessPoint)

                ElseIf (rdWS.Checked And (accessPoint.URLType() = _
                    URLType.Mailto)) Then     'we use the email version
                    svcDescription = template.Descriptions(0).Text()
                    svcAccessPoint = accessPoint.Text
                    InvokeEmail(svcDescription, svcAccessPoint)

                ElseIf (rdWeb.Checked And (accessPoint.URLType() = _
                    URLType.Phone)) Then     'we use the phone version
                    svcDescription = template.Descriptions(0).Text()
                    svcAccessPoint = accessPoint.Text
                    InvokePhone(svcDescription, svcAccessPoint)
                End If
            Next
       Catch ex As Exception
       'This happens if there really WAS an error, which of course never
       'happens in my code
       MessageBox.Show("Sorry there was an error - not in my code" _
         " of course, but probably with the UDDI Server")
    End Try
   End Sub

If the "no flag" button is checked then there is no error. So if the current binding access type is HTTP, then I get the URL to the web service and call the InvokeService () method, which I'll discuss below. If there is a web service fault, indicated by FlagWSProblem being checked, then I check if the access type of the current binding is email. If so I get the access point, which is the actual email address and description and call the InvokeEmail() method. Finally, if FlagWebProblem is checked, I cannot send a message over the Internet. Therefore, the system will check to see if the binding is a telephone type. The information stored will then reflect a phone number that the user can call to access that business information.

At this point I am either running the web service version, the email version or phone version. Let's look first at the two latter methods as these are simplest. I will then go on to look at the web service.

Invoking the Services

The InvokeEmail() method takes the description of the email binding and the access point of this binding, which is the email address in the form of "mailto:emailaddress ".

'This shows the email details if there is as web service problem
Private Sub InvokeEmail(ByVal description As String, _
      ByVal accessPoint As String)

      Dim response As String = InputBox(description & Environment.NewLine _
      & Environment.NewLine & accessPoint, "Email Flight Information", _
      "<< Enter your email address and flight request >>")

      MessageBox.Show("We have sent your email shown below and will be in" _
      "touch shortly. Thankyou for using Deltabis FlightService" & _
       Environment.NewLine & Environment.NewLine & response, _
       "Your email has been sent")
End Sub

In this case I simply prompt the user to enter their email address and provide the user with the email address they can contact for further information. In this sample I don't go any further than that, but it would be preferable to simply send the form details in an email and inform the user that you will be back in contact soon. This means the user doesn't even know email has been used, and the only part different from the web service is that it will be asynchronous.

'This shows the telephone details if there is a web service & email problem
Private Sub InvokePhone(ByVal description As String, _
      ByVal accessPoint As String)

      MessageBox.Show(description & Environment.NewLine & _
             Environment.NewLine & accessPoint, "Telephone Information")
End Sub

In the case where the telephone service is to be invoked, the access point is passed, which is the telephone number. I simply display a message informing the user of the number they can dial to get flight information. This is excellent backup where there is no other option available, but you are still able to give the user valid information.

'This actually allows the service to continue if everything is ok
Private Sub InvokeService(ByVal description As String, _
      ByVal accessPoint As String)
        
      Dim flightService As FlightService = New FlightService()
      flightService.Url = accessPoint

      'returns the available destinations
      Dim xmlDestdoc As XmlDocument = New XmlDocument()
      Dim destString = flightService.GetDestinations()
      xmlDestdoc.LoadXml(destString)
      PopulateDestinations(xmlDestdoc)

      'returns the available flight periods
      Dim xmlPerioddoc As XmlDocument = New XmlDocument()
      Dim periodString = flightService.GetFlightPeriods()
      xmlPerioddoc.LoadXml(periodString)
      PopulatePeriods(xmlPerioddoc)

      'Returns the maximum number of records that will be returned
      Dim intMaxFlights As Integer = flightService.GetMaxPageSize()
      HScrollBar1.Maximum = intMaxFlights
End Sub

When a web service is to be invoked, the InvokeService() method is called as shown below. The accessPoint that is passed will be the URL to the web service. A new instance of the FlightService proxy class is created that gives us access to the remote web service. I set the Url property of the web service to the access point passed into the method. In this case I don't need to do this because when the WSDL file was downloaded by Visual Studio .NET earlier, it including location details specifying where the web service can be accessed. However, if the service location has changed, I can get this at run time from the UDDI registry and dynamically change where the service is invoked. The GetDestinations() method of the web service is called and returned an XML string which is loaded into an XmlDocument instance and the PopulateDestinations() method is called passing the XML document as a parameter - I'll look at this method shortly. A similar thing is done with the GetFlightPeriods() method and the PopulatePeriods() method is called. The final action performed in the method is GetMaxPageSize() is used to set the maximum value of the scroll bar.

'This populates the combo boxes with the destination information
Private Sub PopulateDestinations(ByVal xmldoc As XmlDocument)
      Dim destinations As XmlNodeList = _
            xmldoc.SelectNodes("/destinations/dest")

      Dim dest As XmlNode

      departureCombo.Items.Clear()
      For Each dest In destinations
            DestinationsTable.Add(dest.Attributes("name").Value, _
                  dest.Attributes("value").Value)
            departureCombo.Items.Add(dest.Attributes("value").Value)
      Next
      departureCombo.SelectedIndex = 0

      arrivalCombo.Items.Clear()

      For Each dest In destinations
            arrivalCombo.Items.Add(dest.Attributes("value").Value)
      Next

      arrivalCombo.SelectedIndex = arrivalCombo.Items.Count - 1
    End Sub

The PopulateDestinations() method gets a node list containing the dest element nodes of the destination XML document and iterates through each item in the node list and first populates a global hashtable containing the destination item name and value. This allows us to find the underlying value for a display name that is shown to the user. Finally both the departure and arrival combo boxes are populated with the display values, which are help in the value attribute of each dest element.

The period information is populated using the PopulatePeriods() method and this works in the exact same way as the PopulateDestinations() method above.

'This populates the combo boxes with the destination information
Private Sub PopulatePeriods(ByVal xmldoc As XmlDocument)
      Dim periods As XmlNodeList = xmldoc.SelectNodes("/periods/period")

      Dim period As XmlNode

      periodCombo.Items.Clear()
      For Each period In periods
            PeriodsTable.Add(period.Attributes("name").Value, _
                  period.Attributes("name").Value)

            Dim index As Integer = _
                  periodCombo.Items.Add(period.Attributes("value").Value)
      Next
      
      periodCombo.SelectedIndex = 0
    End Sub

So now I have populated the form, how do I find a flight?

Finding a Flight

When the user clicks the "Find today's flights" button, the event handler gets the selected period, arrival and destination values and looks up their equivalent programmatic values from the hashtables.

Private Sub Button1_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles Button1.Click

      Dim period As String =
 PeriodsTable.Item(periodCombo.Items(periodCombo.SelectedIndex))
      Dim arrival As String
      Dim departure As String

      Dim myEnumerator As IDictionaryEnumerator = _
            DestinationsTable.GetEnumerator()

      While myEnumerator.MoveNext()
            If (myEnumerator.Value = _
            arrivalCombo.Items(arrivalCombo.SelectedIndex)) Then
                arrival = myEnumerator.Key
            Exit While
      End If
      End While

        myEnumerator.Reset()
        While myEnumerator.MoveNext()
            If (myEnumerator.Value = _
            departureCombo.Items(departureCombo.SelectedIndex)) Then
            departure = myEnumerator.Key
            Exit While
            End If
        End While

        Dim numFlights As Integer = TextBox1.Text

Now that I have the necessary values I create a new instance of the web service and call the GetTodaysSchedule() method passing in the necessary parameters. The result is an XML string containing the available flights for that day and this information is loaded into a new XmlDocument instance and passed to the PopulateFlightList() method.

        Dim flightService As FlightService = New FlightService()

        Dim flightResults As String = _
            flightService.GetTodaysSchedule(departure, arrival, period, _
            numFlights)

        Dim xmlFlights As XmlDocument = New XmlDocument()
        xmlFlights.LoadXml(flightResults)
        PopulateFlightList(xmlFlights)
    End Sub

The PopulateFlightList() method will select each of the flight element nodes in the XML document and returns an XmlNodeList containing each flight node.

'This populates the flight info list box
Private Sub PopulateFlightList(ByVal xmldoc As XmlDocument)
      Dim flights As XmlNodeList = xmldoc.SelectNodes("/schedules/flight")

      Dim flight As XmlNode
      flightList.Items.Clear()
      For Each flight In flights
            Dim flightNumber As String = flight.Attributes("num").Value
            Dim normalizedFlightNumber As String

            Dim chPlaces As Char()
            chPlaces = flightNumber.ToCharArray()

            Dim i As Integer
            While i < chPlaces.Length
                  If IsNumeric(chPlaces.GetValue(i)) Then
                  normalizedFlightNumber = normalizedFlightNumber & _
                        chPlaces.GetValue(i)
                  End If
                  i = i + 1
      End While

            i = 0
            flightList.Items.Add(normalizedFlightNumber)
            normalizedFlightNumber = ""
      Next
End Sub

Each flight node contains a num attribute, which gives us the flight number. The flight numbers that are returned are sometimes annotated with text such as "*" and "schedule" and other non-numeric values. These non-numeric values are removed by converting the string to a character array, checking each character to see if it is numeric and only keeping numeric values. This gives us a version of the flight number, which contains only numeric values.

With the flight list populated I am ready to get more information on a specific flight.

Viewing Flight Details

When the user clicks on an individual flight in the flight list, the event is caught and a new instance of the FlightService is created. The flight number is retained and the GetFlightNo() method is called passing the flight number. This method will return an XML string containing the flight information, which is loaded into an XmlDocument instance. The XML document is then read into an XmlTextReader instance by passing the XML to the constructor, setting the XmlNodeType to Document. I do this so I can get a text reader instance that can then be loaded into the DataSet I create using the ReadXml() method, which takes an XmlReader derived instance as an argument. Finally, the datagrid is populated by setting its DataSource property to the DataSet , but specifically the table used maps to the item element and so each item element is mapped to a datagrid row.

Private Sub flightList_SelectedIndexChanged(ByVal sender As System.Object, _
 ByVal e As System.EventArgs) Handles flightList.SelectedIndexChanged

      Dim flightDetails As FlightService = New FlightService()

      txtFlightNo.Text = flightList.Items(flightList.SelectedIndex)
      TabControl1.SelectedTab = TabPage2

      Dim xdoc As XmlDocument = New XmlDocument()
      xdoc.LoadXml(flightDetails.GetFlightInfo(flightList.Items( _
            flightList.SelectedIndex)))

      Dim xt As XmlTextReader = New _
            XmlTextReader(xdoc.DocumentElement.OuterXml, _
            XmlNodeType.Document, Nothing)

      Dim ds As DataSet = New DataSet()
      ds.ReadXml(xt)

      DataGrid1.DataSource = ds.Tables("item")
End Sub

That concludes our look at the code. Now let's see the application in use and some additional features that have been added to help you understand what is happening between your client and the UBR.

The Sample Application

Let's walk through the application and see some of the feature it offers. If you hit F5 or press the play button, the form will load. It will take a few seconds as it populates the data on the form from the web service. When complete you will see something like the following:

The form is populated as shown with the values from the web service. The cool thing is that any updates to the web service will be immediately reflected in every application that uses this data. Leave the time of day at "All Flights ", change the departure airport to "Boston" and the arrival airport to "San Jose ", change the maximum number of flights to 20 using the scroll bar and click the "Find today's flights" button.

When the service returns, the flight list box will contain a list of flights based on the flight period you selected - it will of course differ from the above list because it is real time data. Click on the top item in the list and again the application will request the data from the web service, passing the flight number and displaying the FlightDetails tab.

Now you can see the results, which display the schedule information that has been returned for that flight. Let's simulate a problem with the web service - in the real world this may be that the network is down, the server is down or there is some problem with the web service somewhere. Select the FlagWSProblem radio button, go back to the FlightFinder tab and click the Refresh button on the bottom left of the form.

Finally click the FlagWebProblem and again click the Refresh button, which will simulate the situation where the email server is down, or the user does not have an email client.

Finally, you can see some of the interaction between the client and the UDDI server. With every query and response between the client and the UDDI registry, an XML SOAP message is sent and received. I will leave the discussion of this for a future article, but if you check the "Show XML" checkbox and click the Refresh button, you will be shown the message. As an example the following is the XML returned about the "FlightService" service.

Any Limitations or Further Work

As an exercise I leave you to develop some caching methods. Obviously you only want to get the data to populate the web form every so often when it has changed, so you may want to put some checking and caching in for this. Also, you may only want to get the information from the UDDI registry at start up and every so often thereafter. So again you will want to cache information such as the business and service information.

Furthermore, there is no reason why this service should be limited to a single business - you may want to completely change the provider of the service. This requires looking at Common Business Interfaces, which are abstract interfaces that sit above the implementations and may be an industry standard. This allows many businesses to write implementations of the same interface and so you can easily change from one business to another, dynamically, with no need for implementation re-writes.

Conclusion

In this article I discussed what UDDI is and the need for dynamic discovery of web services, as well as some of the problems preventing that. I also looked at the following:

  • An application architecture for our web service
  • Querying UDDI for business and service information
  • Using UDDI for dynamic service invocation
  • Using a flight information web service

A future article may discuss the XML that is sent between the client and the UDDI registry as well as the XML used with the Web Service.

I'd be interested in ideas you may have on features that could be added to this sample application and any updates you make yourself, such as caching. Please mail me at mailto:s.livingstone@btinternet.com.

 

Please rate this article using the form below. By telling us what you like and dislike about it we can tailor our content to meet your needs.

 
 
Rate this Article
How useful was this article?
Not useful Very useful
Brief Reader Comments: Read Comments
Your name (optional):
 
 
Content Related Links Discussion Comments Index Entries Downloads
 
Back to top