CityView App: Build Web Service Clients Quickly and Easily with C#
Jeff Prosise
Download the code for this article:CityView.exe (45KB)
Browse the code for this article at Code Center: CityView

I

f Microsoft is correct, Web Services will play an important role in the future of the Internet. A Web Service is an application that exposes Web methodsâ€"functions that can be called over the Internet (or, if you'd prefer, an intranet)â€"to interested clients. It runs on a Web server and listens on port 80 for Simple Object Access Protocol (SOAP) packets that represent requests to execute a particular method. When a request arrives, the Web Service extracts the XML-encoded method name and parameters, executes the corresponding method, and returns the results as XML.
      One of the driving forces behind the Microsoft .NET initiative is the belief that Web Services will transform the Internet into a universal software platform that contains a massive API formed by millions of Web methods written by companies all over the world. A chief component of that initiative is to make writing Web Services very, very easy. The WebService class in the .NET Framework class library does just this. To demonstrate how easy it is, here's the code for a complete Web Service featuring a Web method named GetGreeting that returns a greeting (Hello, world) to callers.

  <%@ WebService Language="C#" Class="GreetingService" %>
  

using System;
using System.Web.Services;

class GreetingService : WebService
{
[WebMethod]
public string GetGreeting ()
{
return "Hello, world";
}
}

 

      If you package this code in an ASMX file and place it on a Web server outfitted with ASP.NET, any client than can access the file's URL can also call the GetGreeting method. Although not shown here, writing a client for the GetGreeting method is almost as easy as writing the Web Service itself, thanks to another class in the .NET Framework class library named SoapClientProtocol and a utility named WebServiceUtil that generates SoapClientProtocol-derived proxy classes.
      It's unlikely that clients will flock to your server to call the GetGreeting method, but if you expose rich content or business logic through a Web Service, clients might be compelled to use it. One of the most prolific examples of a Web Service that's available today is Microsoft TerraService (https://terraserver.homeadvisor.msn.com/default.asp). TerraService is a Web Service written with the .NET Framework that exposes the contents of the Microsoft TerraServer database (https://terraserver.homeadvisor.msn.com/default.asp) to Internet clients. TerraServer is a massive SQL Serverâ„¢ database that contains aerial images and topographical maps of much of the earth's surface. You can go to the TerraServer Web site and pull up images or maps of virtually any place in the world. Or you can do the same thing programmatically by invoking TerraService's Web methods.
      As a testament to the ease with which Web Services can be built with .NET, TerraService was built by two programmers (one of whom is MSDN® Magazine's own Jeffrey Richter) in just a few days. Much of that time was spent planning the project and designing the TerraService interfaceâ€"that is, deciding what Web methods to expose from it.
      So what does TerraService have to do with Wicked Code? I'm glad you asked. After building and experimenting with a few Web Services of my own (an ongoing research project that is still far from being finished), I wanted to get some experience building Web Service clients. And TerraService seemed like the ideal back end. So I wrote a Windows® Forms-based application that takes a city name as input and displays an aerial image of that city in a choice of resolutions. I was astounded by how little code it took to pull it off. Most of the code in my client is user interface code that handles menus and other UI elements; only a fraction of itâ€"about 30 linesâ€"is actually devoted to fetching images from TerraService. If that's not wicked, I don't know what is.
      In this column I'll describe my TerraService client and present the source code. If you, too, are interested in taking advantage of the many Web Services that are already proliferating on the Web, I think you'll find it a worthwhile read.

The CityView Application

      Before I dive down under the hood to show you what makes my TerraService client tick, let's take a look at what a user sees. The application, which I named CityView, is shown in Figure 1. It displays a composite image of San Francisco (courtesy of the U.S. Geological Survey). The image is formed from 200x200 pixel tiles fetched from TerraServer via TerraService. The City menu features commands for fetching images of preselected cities and also a command that pops up a dialog box for entering the city and state of your choice. The Scale menu lets you select a resolution. Select a smaller number to zoom in for a more detailed view, or a larger number to zoom out and see more of the city.

Figure 1 The CityView Application
Figure 1 The CityView Application

      My first task in designing CityView was figuring out how to take arbitrary city and state names and convert them into longitudes and latitudes to feed to TerraService. So I checked the documentation for TerraService, which is available online at https://terraserver.homeadvisor.msn.com/terraservice.htm, and discovered that ConvertPlaceToLonLatPt is among the 16 Web methods that it exposes. The method is prototyped this way:

  public LonLatPt ConvertPlaceToLonLatPt (Place place)
  

 

      LonLatPt and Place are both data types defined by TerraService. LonLatPt encapsulates a longitude and latitude, while Place represents a placeâ€"city, state, and countryâ€"somewhere in the world. With this method to call upon, converting an arbitrary place name such as San Francisco, CA into a longitude and latitude is as simple as this:

  Place place = new Place ();
  
place.City = "San Francisco";
place.State = "CA";
place.Country = "USA";
LonLatPt point = ts.ConvertPlaceToLonLatPt (place);

 

This example assumes that ts is a reference to a TerraService proxy objectâ€"an object that converts method calls into SOAP requests and transmits them to TerraService over the Internet. Exactly how you go about getting that reference will be discussed shortly.
      The next challenge is to take the longitude and latitude returned by ConvertPlaceToLonLatPt and convert them into an AreaBoundingBox object describing the geographical boundaries of the desired image. TerraService has a Web method named GetAreaFromPt that does exactly that:

  public AreaBoundingBox GetAreaFromPt (
  
LonLatPt center, Theme theme, Scale scale,
int displayPixWidth,
int displayPixHeight)

 

      The following statement uses GetAreaFromPt to retrieve a bounding box suitable for framing a 640�480 pixel image of the terrain at the longitude and latitude represented by point at a resolution of 32 meters:

  AreaBoundingBox abb = ts.GetAreaFromPt (point,
  
Theme.Photo, Scale.Scale32m, 640, 480);

 

      Once the bounding box is computed, all you need to do is fetch from the TerraServer database all the 200�200 pixel images (tiles) that intersect that box and glue them together to create a composite image. Individual tiles are fetched with the GetTile method, which returns an array of bytes representing the pixels:

  public Byte[] GetTile (TileID id)
  

 

The TileID passed to GetTile can be retrieved from the AreaBoundingBox. Because there's a bit of work involved in fetching all the tiles and creating an image from them (mostly work involving the GDI+, which is a superset of the Windows GDI), I won't show the image retrieval code just yet. What's important for now is that I used only three of the Web methods that TerraService exposes to create an application that's a real conversation piece. Most people are amazed the first time they see it in action. They're even more amazed when they see how little code it took to make it work.

Creating a TerraService Proxy

      To invoke methods on a Web Service such as TerraService, you need code that encapsulates requests in SOAP "envelopes" and squirts them out through an HTTP connection. There are two basic ways to write that code. There's the hard way, which means using the SOAP Toolkit (or, if you'd prefer, no toolkit at all) to write the code from scratch. And there's the easy way, which involves deriving a class from the .NET Framework class library's SoapClientProtocol class and letting it do the work for you. The easy way is made even easier by the fact that you don't have to perform the derivation by hand. Instead, you can use the WebServiceUtil utility that comes with the .NET Framework SDK. Together, WebServiceUtil and SoapClientProtocol make it trivial to write Web Service clients. You don't have to know the first thing about SOAP to use them because they hide SOAP way down under the hood where network protocols belong. (You don't have to understand TCP/IP to use sockets, do you?)
      Using WebServiceUtil is simplicity itself. Assuming you have the .NET Framework SDK installed on your machine, open a command prompt window and type:

  WebServiceUtil /c:proxy /l:csharp /n:TS
  
/path:https://terraserver.microsoft.net/terraservice.asmx?sdl

 

The /C switch tells WebServiceUtil to generate a proxy class (derived from SoapClientProtocol) that can be used to call TerraService's Web methods. The /L switch instructs WebServiceUtil to generate C# code (as opposed to Visual Basic®), and the /N switch tells it to enclose that code in a namespace named TS. (The namespace is necessary to prevent name collisions between TerraService data types and certain data types defined in the .NET Framework class library.) The /PATH switch identifies TerraService's Service Description Language (SDL) URL. WebServiceUtil uses the URL as the target of an HTTP GET and receives in return SDL data describing TerraService's Web methods, data types, and supported protocols. It is from this SDL that WebServiceUtil determines what methods to build into the proxy class.
      The output from the WebServiceUtil command is a C# file named TerraService.cs. Look inside it and you'll find a wrapper class named TerraService whose methods map one-to-one to TerraService's Web methods. (All source code associated with this column can be downloaded from the link at the top of this article.) This download also contains definitions for data types such as Place and AreaBoundingBox, which are essential if you're going to use those data types in your own client application.

The CityView Code

      Once you've generated TerraService.cs, you have everything you need to begin writing the CityView client (or any other TerraService client, for that matter). CityView's source code is shown in Figure 2. It's written in C# and was compiled using the Beta 1 .NET Framework SDK. Note that it will need some minor tweaking to compile under Beta 2. If you'd like to compile the code yourself, go to the directory where CityView.cs and TerraService.cs are stored and type the following at the command prompt:

  csc /target:winexe
  
/out:CityView.exe
/reference:System.dll
/reference:System.WinForms.dll
/reference:System.Drawing.dll
/reference:Microsoft.Win32.Interop.dll
/reference:System.Xml.Serialization.dll
/reference:System.Web.Services.dll
TerraService.cs CityView.cs

 

This command invokes the Microsoft C# compiler and produces CityView.exe from the source code files CityView.cs and TerraService.cs. The /REFERENCE switches identify assemblies containing the external data types that CityView uses which aren't defined in MsCorLib.dll.
      As I mentioned earlier, most of the CityView code is UI stuff that processes user input and displays images fetched from TerraServer. The heart of the application is a method named GetTiledImage, which takes a city, state, scale, and width and height as input and returns a Bitmap object containing the requested image. GetTiledImage's general strategy is to create a Bitmap object in which to build a composite image and a Graphics object for drawing to the Bitmap. Then it enters a nested for loop, calling GetTile repeatedly to retrieve the individual tiles that make up the image. The bytes comprising each tile are captured in a MemoryStream object, stuffed into an Image object with Image.FromStream, and drawn into the composite image with Graphics.DrawImage. At the end, the composite image is returned to the caller. CityView stores a reference to that Bitmap in a field (_MyBitmap) so it can draw it on the screen. The actual drawing is done in the OnPaint handler of the MyForm class, which is called whenever CityView's window requires repainting.
      You can download CityView, source code and all, from the link at the top of this article. Play with it, tweak it, amaze your friends, do whatever you like with it. Do remember that because CityView is a Windows Forms-based application, you'll have to have the .NET Framework installed on your PC to run it.
      Is it possible to write a CityView-like application that runs on machines that don't have .NET installed on them? You bet. You don't have to have the .NET Framework to write Web Services or Web Service clients. But as you've seen, the .NET Framework makes writing these types of applications incredibly easy. Another option is to make CityView a Web Forms application rather than a Windows Forms-based application. Web Forms require nothing more than an HTML 3.2-compliant browser on the client side because all the .NET processing is done on the server. In fact, writing a Web Forms version of CityView would be an interesting exercise for aspiring Web Forms programmers. Such a Web Form already exists at https://terraserver.microsoft.net/map.aspx?t=2&s=16&lon=-122.4&lat=37.8&w=500&h=300. But that, dear friends, is a topic for another Wicked Code.

Drop Me a Line

      Are there tough Win32®, MFC, COM(+), or .NET programming questions you'd like answered? If so, drop me a note at jeffpro@wintellect.com. Please include "Wicked Code" in the message title. I regret that time doesn't permit me to respond to individual questions, but rest assured that each and every one will be considered for inclusion in a future column.

Send questions and comments for Jeff to wicked@microsoft.com.

Jeff Prosise is the author of Programming Windows with MFC (Microsoft Press, 1999). He is also a cofounder of Wintellect (https://www.wintellect.com), a Windows developer training and consulting firm.

From the April 2001 issue of MSDN Magazine.