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. |