Developing an XML Web Service The StockWebService Example


Developing an XML Web Service ” The StockWebService Example

ASP.NET greatly simplifies the development of XML Web services. As you will soon see, you can implement a Web service by defining a class that exposes one or more public methods marked with the System.Web.Services.WebMethod ­Attribute metadata attribute. You save the class in a file with an .asmx extension and include it in an ASP.NET Web application. ASP.NET handles all the plumbing necessary for exposing these public methods as Web methods that form the API of the Web service that can be invoked from a client application. ASP.NET implements the logic to process incoming requests and invoke the appropriate Web method. The processing logic also handles marshaling ” the packaging and unpackaging of incoming parameters and the outgoing return value. ASP.NET can automatically generate a WSDL document or contract that describes the API of the Web service. In addition, ASP.NET can generate a set of HTML pages that provide a quick way to test Web methods, as shown in Figure 18-1.

Figure 18-1. The page that ASP.NET automatically generates for testing the GetCurrentStockData Web method

graphics/f18hn01.jpg

Implementing the Web Service

The StockWebService that we will implement provides a simple simulation of a stock chart containing a fixed set of stock ticker symbols. This example Web service exposes a method named GetCurrentStockData , which provides a snapshot of the stock data that includes the current values, the most recent change in the values, and the time of the last change in the values of the requested stock symbols.

StockWebService is implemented as part of the BookWeb application in StockWebService.asmx, as shown in Listing 18-1.

Listing 18-1 StockWebService.asmx contains the implementation of the XML Web service and associated classes.
 <%@ WebService Language="c#" Class="StockWebService" %> <%@ Assembly src="StockServer.cs" %> using System; using System.Collections; using System.Web.Services; using System.Xml.Serialization; using BookWeb; [WebService(Name="StockWebService",     Namespace="http://localhost/BookWeb/Chapter18/StockWebService",     Description="A very simple stock tracking simulation")] public sealed class StockWebService {     [WebMethod(Description="Returns a snapshot of the current stock data")] 
 public StockDataSnapShot GetCurrentStockData(string stockSymbols) {         if ((stockSymbols == null)  (stockSymbols.Length == 0)) {             throw new ArgumentNullException("stockSymbols");         }         if (StockServer.Instance.IsStarted == false) {             StockServer.Instance.Start();         }         string[] symbols = stockSymbols.Split(';');         DateTime snapShotTime = StockServer.Instance.LastUpdateTime;         ArrayList stocks = new ArrayList();         for (int i = 0; i < symbols.Length; i++) {             StockServer.StockInfo si =                  StockServer.Instance[symbols[i]];             if (si != null) {                 stocks.Add(new StockData(si));             }         }                          StockDataSnapShot snapShot = new StockDataSnapShot();         snapShot._snapShotTime = snapShotTime;         snapShot._stocks =              (StockData[])stocks.ToArray(typeof(StockData));                  return snapShot;     } } public sealed class StockDataSnapShot {     [XmlElement("SnapShotTime")] public DateTime _snapShotTime;     [XmlArray("Stocks")] public StockData[] _stocks; } public sealed class StockData {     [XmlAttribute("Symbol")] public string _symbol;     [XmlAttribute("Value")] public double _value;     [XmlAttribute("Change")] public double _change;     public StockData() { }     public StockData(StockServer.StockInfo info) {         _symbol = info.Symbol;         _value = info.CurrentValue;         _change = info.Change;     } } 

The file is treated as a Web service by the WebServiceHandlerFactory , the HTTP handler associated with .asmx files. The code in the .asmx file is dynamically compiled when the Web service is first requested, by using the same dynamic compilation model that is used for .aspx pages. The Web service implementation just shown illustrates the following concepts:

  • The WebService directive within an .asmx file is used to indicate the language of the contained code as well as the specific class within the file to be used as the Web service. StockWebService is an ordinary class. As an option, when you implement a Web service, you can derive your class from the System.Web.Services.WebService base class. This base class provides access to ASP.NET intrinsic objects such as Session . StockWebService does not require access to these objects.

  • The StockWebService class contains a public method named GetCurrentStockData , which is marked as a Web method by using the WebMethodAttribute metadata. The attribute can also provide a more friendly description of the Web service by using the attribute's Description property. This description is used by ASP.NET while generating a test page (as shown in Figure 18-1) as well as in generating the WSDL contract.

  • The StockWebService class uses the optional WebServiceAttribute metadata to provide a friendly name and description for the Web service.

  • The GetCurrentStockData accepts a single parameter containing the stock symbols whose values are being requested. The method returns an instance of StockDataSnapShot , which contains an array of StockData instances (one for each symbol requested), along with the timestamp of the snapshot.

  • ASP.NET handles the incoming request, which is usually a SOAP message, to extract information about the method being called and its parameters. ASP.NET then invokes that method and generates an outgoing response by packaging the return value into a SOAP message. In this example, the StockDataSnapShot and StockData classes used for the method's return value need to be serialized into an XML format as part of the SOAP message. You can control the structure of the XML content via the XmlElementAttribute , XmlAttributeAttribute , and other related optional metadata attributes from the System.Xml.Serialization namespace that have been applied to the members of these classes.

The Stock Server

As mentioned earlier, StockWebService is a simulation of a stock chart. This Web service provides access to stock data that is maintained and updated by the mock stock server running within the same Web application. This stock server is simply an implementation detail that allows the sample to be self-contained.

Note

The Web service implementation shown in Listing 18-1 refers to the StockServer implementation in source form using the Assembly directive. ASP.NET dynamically compiles the StockServer implementation into an assembly when the Web service is first invoked. Alternatively, the StockServer implementation could also have been precompiled into an assembly like any other class.


Listing 18-2 shows the implementation of the StockServer class.

Listing 18-2 StockServer.cs implements the mock stock chart simulation.
 using System; using System.Collections; using System.Collections.Specialized; using System.Configuration; using System.Globalization; using System.Timers; namespace BookWeb {     public sealed class StockServer {         public static readonly StockServer Instance =              new StockServer();         private Hashtable _stockTable;         private Timer _timer;         private DateTime _lastUpdate;         // All callers must use the single instance of StockServer          // just shown and not be allowed to create new instances.         private StockServer() {             // Initialize the stock table.             _stockTable = new Hashtable();             NameValueCollection appSettings =                 ConfigurationSettings.AppSettings;             int stockCount =                  Int32.Parse(appSettings["StockServer.Count"],                     CultureInfo.InvariantCulture); 
 for (int i = 0; i < stockCount; i++) {                 string symbol =                      appSettings["StockServer.StockSymbol" + i];                 double initialValue = Double.Parse(appSettings["StockServer.StockValue" + i],                     CultureInfo.InvariantCulture);                 _stockTable[symbol] =                      new StockInfo(symbol, initialValue);             }             _lastUpdate = DateTime.Now;         }         public bool IsStarted {             get { return (_timer != null); }         }         public DateTime LastUpdateTime {             get { return _lastUpdate; }         }         public ICollection Stocks {             get { return _stockTable.Values; }         }         public ICollection Symbols {             get { return _stockTable.Keys; }         }         public StockInfo this[string symbol] {             get { return (StockInfo)_stockTable[symbol.ToUpper()]; }         }         public void Start() {             lock(typeof(StockServer)) {                 if (_timer == null) {                     // Two-minute interval                     _timer = new Timer(2 * 60 * 1000);                     _timer.Elapsed +=                         new ElapsedEventHandler(this.Timer_Elapsed);                     _timer.Enabled = true;                 }             }         } 
 public void Stop() {             if (_timer != null) {                 lock(typeof(StockServer)) {                     if (_timer != null) {                         _timer.Enabled = false;                         _timer.Elapsed -= new                              ElapsedEventHandler(this.Timer_Elapsed);                         _timer = null;                     }                 }             }         }         private void Timer_Elapsed(object sender, ElapsedEventArgs e) {             lock(typeof(StockServer)) {                 _lastUpdate = DateTime.Now;                 Random r =                      new Random(unchecked((int)_lastUpdate.Ticks));                 foreach (StockInfo s in _stockTable.Values) {                     double randomValue = r.NextDouble();                     // Percentage should be in the range of                      // -0.2 to 0.2.                     double percentChange = (randomValue - 0.5) / 5.0;                     s.UpdateValue(percentChange);                 }             }         }         // Holds information about a stock.         public sealed class StockInfo {             private string _symbol;             private double _value;             private double _change;             public StockInfo(string symbol, double initialValue) {                 _symbol = symbol;                 _value = initialValue;             }             public double Change {                 get { return _change; }             }             public double CurrentValue {                 get { return _value; }             } 
 public string Symbol {                 get { return _symbol; }             }             internal void UpdateValue(double percentChange) {                 double change = _value * percentChange;                 if (_change != 0.0) {                     _change = (change + _change) / 2.0;                 }   else {                     _change = change;                 }                 _change = (double)Decimal.Round((decimal)_change, 3);                 _value = _value + _change;             }         }     } } 

The StockServer class loads the list of stock symbols, along with their initial values from the <appSettings> section of the Web application's configuration file, and tracks those symbols through its collection of StockInfo objects. The implementation uses a Timer to periodically update the values of each stock in its collection. The changes are based on percentage fluctuations resulting from random numbers .

Note that there is a single instance of the StockServer . This class implements the singleton pattern ”it implements a private constructor, which restricts the creation of instances. This pattern ensures that all class usage is performed through the single instance it instantiates and makes available through its static Instance member. We have used this pattern so that only one copy of stock data in the application is made available to and is shared across all incoming requests. The StockServer acquires a lock by synchronizing on the class type before performing any updates to the class's underlying data so that StockServer can ensure consistent updates while multiple threads servicing multiple requests safely access the data.

Deploying the Web Service

Once a Web service has been implemented, it needs to be exposed publicly so that it can be discovered and used by client applications. One approach to discovering a Web service implementation is to navigate to the Web service site by using a Web browser. You can configure the site to contain a Web page that describes the set of Web services available on the site, as shown next by using .disco (discovery) files. Another approach to publishing Web services is to register the Web service and its WSDL contract along with your information (used to identify the publisher) with a Universal Description, Discovery, and Integration (UDDI) registry. UDDI is a Web service that allows registration of commercial Web services, acting as a virtual White Pages and Yellow Pages directory.

Microsoft Visual Studio .NET enables Web service discovery by using .disco files and UDDI registries via the Add Web Reference dialog box. This feature can also generate a client proxy (described later in the section). Figure 18-2 shows how the Add Web Reference dialog box looks when a Web service has been discovered.

Figure 18-2. The Add Web Reference dialog box in Visual Studio .NET enables Web service discovery.

graphics/f18hn02.jpg

A .disco file is used to provide information about the existence of publicly exposed Web services, along with the location of the associated WSDL specification that documents the Web service. Listing 18-3 lists the contents of the Default.disco file, which enables the discovery of StockWebService within the BookWeb application.

Listing 18-3 Default.disco enables discovery of the StockWebService .
 <?xml version="1.0" encoding="utf-8" ?>  <discovery xmlns="http://schemas.xmlsoap.org/disco/">   <contractRef ref="Chapter18/StockWebService.asmx?wsdl"       docRef="Chapter18/StockWebService.asmx"       xmlns="http://schemas.xmlsoap.org/disco/scl/" /> 
 <soap address="Chapter18/StockWebService.asmx"       xmlns:q1="Chapter18/StockWebService"       binding="q1:StockWebServiceSoap"       xmlns="http://schemas.xmlsoap.org/disco/soap/" /> </discovery> 

Note

A Web application created by Visual Studio .NET contains a .vsdisco file that enables automatic discovery of Web services without requiring the explicit entries needed in the .disco file. However, the .vsdisco file enables the discovery of all Web services in your application, not just the ones you decide to expose. Therefore, we recommend that you delete the automatically created . vsdisco file and instead use a .disco file, as we have done in the BookWeb application.


ASP.NET can automatically provide a .disco file from an .asmx file. The .disco file shown in Listing 18-3 was created by modifying the automatically generated .disco file by navigating to the URL http://localhost/BookWeb/Chapter18/StockWebService.asmx?DISCO . The resulting file is referenced in the Default.aspx page present in the root of the BookWeb application, as shown in Listing 18-4. This allows Visual Studio .NET to list the Web services offered by a Web application by browsing to a page within the application.

Listing 18-4 Fragment of Default.aspx that shows the reference to Default.disco.
 <html> <head>  <link type="text/xml" rel="alternate" href="Default.disco" /> </head> <body>  

Developing Web Service Client Proxies

Client applications call Web services by packaging and transmitting data in the form of SOAP messages. However, implementing this messaging plumbing can be complex and time-consuming . A useful alternative is to employ a client proxy . A client proxy is a class that runs within the client application and provides the same public interface as the Web service it represents. A client proxy also contains the logic to package parameters into a SOAP message and send them to the Web service in the form of a Web request, as well as to unpackage the response from the Web service to generate a return value for the proxy's method. By using the proxy, the developer can call the Web service methods just as he or she would call any other class, without having to manually implement the plumbing. Thus, a proxy greatly simplifies Web service usage within client applications.

Visual Studio .NET simplifies the generation of the proxy class. The Add Web Reference dialog box illustrated in Figure 18-2 can automatically create a client proxy when a Web service is referenced. You can also generate the proxy class manually by using the wsdl.exe tool that ships with the .NET Framework SDK. In our example, the proxy class for StockWebService has been generated by using wsdl.exe. The specific command used is discussed in the next section.



Developing Microsoft ASP. NET Server Controls and Components
Developing Microsoft ASP.NET Server Controls and Components (Pro-Developer)
ISBN: 0735615829
EAN: 2147483647
Year: 2005
Pages: 183

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net