As described in Chapter 15 and shown schematically in Figure 15-1, a web service is consumed by a client application by use of a proxy. A proxy is a substitute, or local stand-in, for the web service. Once the proxy is created and registered with the consuming application, then method calls can be made against the web service. In actuality, those method calls will be made against the local proxy. It will seem to the consuming application that the web service is local to the application.
There are two ways to generate the proxy. The first way, described in the next section, is to generate the source code for the proxy class manually and compile that into the proxy DLL. The advantages to this method are:
You do not need to use Visual Studio .NET.
The command-line approach offers more flexibility and features over Visual Studio .NET.
The alternative method is to allow Visual Studio .NET to create the proxy and register it with the consuming application in a single step. The advantage to this method is that it is much less work. Using Visual Studio .NET will be demonstrated shortly.
To create the proxy, use another command-line utility called wsdl.exe . This utility takes a WSDL file as input. The WSDL file can either be stored locally, having been previously created using the disco command-line utility, or it can be generated on the fly from the web service file itself. The following two command lines will yield the same result, assuming that the local WSDL file came from the remote .asmx file:
wsdl csStockTicker.WSDL wsdl http://localhost/ProgAspNet/csStockTicker.asmx?wsdl
Alternatively, the WSDL utility can take a .discomap file, described earlier in Section 17.1, created by the disco utility as input.
The output from the WSDL utility is a source code file containing the
proxy class, which can then be compiled into a library, or
dll, file. The default language for this output
source is C#. To change the language of the output file, use the
/language:
parameter, or /l:
for short. Valid values for the
language parameter are CS
, VB
,
or JS
, for C#, VB.NET, and JScript.NET,
respectively. So to force the output to be VB.NET, you would use a
command line similar to:
wsdl /l:VB http://localhost/ProgAspNet/vbStockTicker.asmx?wsdl
By default, the first component of the output filename is based on
the input file as follows. If the WebService
attribute in the .asmx file has a
Name property, then the output file will have that name. If not, the
output name will have the name of the web service class. Note that
every output filename also has an extension corresponding to the
language.
For example, suppose that the file vbStockTicker.asmx has the following
WebService
attribute and class definition:
<WebService (Description:="A stock ticker using VB.NET.", _ Name:="StockTicker", _ Namespace:="www.LibertyAssociates.com")> _ public class vbStockTicker inherits System.Web.Services.WebService
If the WSDL utility is run against the WSDL file generated from this .asmx file with the language set to VB, then the output filename would be StockTicker.vb. However, if the Name property is removed from the .asmx source file, then the output name will be vbStockTicker.vb. By default the output file will be in the current directory of the command prompt.
You can specify both the output
filename and location by using the
/out:
parameter, or
/o:
for short. For example, the following command
line will force the output file to have the name Test.vb and be located in the bin directory below the current directory:
wsdl /l:VB /o:bin est.vb http://localhost/ProgAspNet/vbStockTicker.asmx?WSDL
Table 17-2 shows some of the other switches available to the WSDL utility.
Table 17-2. WSDL utility switches
For a complete list of parameters for wsdl.exe
,
enter the following from the command line:
wsdl /?
Compare the beginning of the original web service source file, csStockTicker.asmx, reproduced in Example 17-1, with the beginning of the generated source code for the proxy class, StockTicker.cs, shown in Example 17-2.
Example 17-1. Beginning of csStockTicker.asmx
<%@ WebService Language="C#" Class="ProgAspNet.csStockTicker" %> using System; using System.Web.Services; using System.Collections; using System.Data; using System.Data.SqlClient; namespace ProgAspNet { [WebService (Description="A stock ticker using C#.", Name="StockTicker", Namespace="www.LibertyAssociates.com")] public class csStockTicker : System.Web.Services.WebService { // Construct and fill an array of stock symbols and prices. // Note: the stock prices are as of 7/4/01. string[,] stocks = { {"MSFT","Microsoft","70.47"}, {"DELL","Dell Computers","26.91"}, {"HWP","Hewlett Packard","28.40"}, {"YHOO","Yahoo!","19.81"}, {"GE","General Electric","49.51"}, {"IBM","International Business Machine","112.98"}, {"GM","General Motors","64.72"}, {"F","Ford Motor Company","25.05"} }; public class Stock { public string StockSymbol; public string StockName; public double Price; public StockHistory[] History = new StockHistory[2]; } public class StockHistory { public DateTime TradeDate; public double Price; } [WebMethod(Description="Returns stock history for " + "the stock symbol specified.")] public Stock GetHistory(string StockSymbol) { Stock stock = new Stock( ); // Iterate through the array, looking for the symbol. for (int i = 0; i < stocks.GetLength(0); i++) { // Do a case-insensitive string compare. if (String.Compare(StockSymbol, stocks[i,0], true) == 0) { stock.StockSymbol = StockSymbol; stock.StockName = stocks[i,1]; stock.Price = Convert.ToDouble(stocks[i,2]); // Populate the StockHistory data. stock.History[0] = new StockHistory( ); stock.History[0].TradeDate = Convert.ToDateTime("5/1/2001"); stock.History[0].Price = Convert.ToDouble(23.25); stock.History[1] = new StockHistory( ); stock.History[1].TradeDate = Convert.ToDateTime("6/1/2001"); stock.History[1].Price = Convert.ToDouble(28.75); return stock; } } stock.StockSymbol = StockSymbol; stock.StockName = "Stock not found."; return stock; }
Example 17-2. Beginning of Proxy class source code file StockTicker.cs
//------------------------------------------------------------------------------ // <autogenerated> // This code was generated by a tool. // Runtime Version: 1.0.2914.16 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // </autogenerated> //------------------------------------------------------------------------------ // // This source code was auto-generated by wsdl, Version=1.0.2914.16. // using System.Diagnostics; using System.Xml.Serialization; using System; using System.Web.Services.Protocols; using System.Web.Services; [System.Web.Services.WebServiceBindingAttribute(Name="StockTickerSoap", Namespace="www. LibertyAssociates.com")] public class StockTicker :System.Web.Services.Protocols.SoapHttpClientProtocol
{ [System.Diagnostics.DebuggerStepThroughAttribute( )]public StockTicker( ) {
this.Url = "http://localhost/ProgAspNet/csStockTicker.asmx";
}
[System.Diagnostics.DebuggerStepThroughAttribute( )] [System.Web.Services.Protocols.SoapDocumentMethodAttribute( "www.LibertyAssociates.com/GetHistorys", RequestNamespace="www.LibertyAssociates.com", ResponseNamespace="www.LibertyAssociates.com", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols. SoapParameterStyle.Wrapped)] public StockGetHistory
(string StockSymbol) { object[] results = this.Invoke("GetHistory", new object[] { StockSymbol}); return ((Stock)(results[0])); } [System.Diagnostics.DebuggerStepThroughAttribute( )] public System.IAsyncResultBeginGetHistory
(string StockSymbol, System.AsyncCallback callback, object asyncState) { return this.BeginInvoke("GetHistory", new object[] { StockSymbol}, callback, asyncState); } [System.Diagnostics.DebuggerStepThroughAttribute( )] public StockEndGetHistory
(System.IAsyncResult asyncResult) { object[] results = this.EndInvoke(asyncResult); return ((Stock)(results[0])); }
There is no need to understand fully all the nuances of the proxy class source code file. But there are several points worth noting:
The namespaces referenced with the
using
statements at the beginning of Examples
Example 17-1 and Example 17-2 are not
the same. This is because the proxy class is not actually using
System.Data. It is merely taking the call to the method that will
ultimately use System.Data, wrapping it in the proper protocol (SOAP
in this case), and passing it over the Internet to the web service.
Therefore, the only namespaces actually needed by the proxy class are
those necessary for interacting with a web service, serializing the
data into an XML data stream, and sending and receiving those XML
packages.
The StockTicker
class inherits from
SoapHttpClientProtocol rather than from
WebService. This inherited class provides the methods for the proxy
to talk to the web service using the SOAP protocol.
Immediately following the StockTicker
class
declaration in the generated proxy is a
constructor
, which is a public method
with the same name as the class. In the constructor, the URL of the
web service is specified.
A constructor is the method in a class that is invoked when the class is first instantiated. The constructor is used to initialize the class and put it into a valid state. If a class does not have a constructor, the CLR will create one by default.
While the original .asmx file has
the Stock
and StockHistory
classes, followed by the
GetHistory method, the
proxy class goes directly to GetHistory.
Again, the proxy does not need the first two classes, since the proxy
only substitutes for method calls.
While the original .asmx file has the public method GetHistory, the proxy class has that method plus two additional, related public methods, BeginGetHistory and EndGetHistory. In fact, you will notice that every web method in the original .asmx file has the same method in the proxy class, plus two others, one for Begin... and another for End.... These additional methods are used to implement asynchronous processing.
Normal method calls are synchronous. In other words, the calling application halts all further processing until the called method returns. If this takes a long time, either because of a slow or intermittent Internet connection (not that that ever happens, of course) or because the method is inherently time-consuming (e.g., a lengthy database query), then the application will appear to hang, waiting.
On the other hand, if the method call is made asynchronously, then the Begin method call is sent out, and processing can continue. When the results come back, the corresponding End method call receives the results. Asynchronous method calls will be demonstrated later in thischapter.
The output of the WSDL utility is a class source code file for the proxy. This source code then needs to be compiled with the appropriate command-line compiler.
For VB.NET, use the following single command line to compile the proxy:
vbc /out:binvbStockTickerProxy.dll /t:library /r:system.dll,system.web.dll,system.web.services.dll, system.xml.dll,system.data.dll StockTicker.vb
and for C# use the following single command line:
csc /out:bincsStockTickerProxy.dll /t:library /r:system.dll,system.web.dll,system.web.services.dll StockTicker.cs
You
will notice that although the
VB.NET and C# versions of the StockTicker proxy being compiled are
functionally identical, with the exact same set of referenced
namespaces (using the Imports
statement in VB.NET
and the using
statement in C#) as can be seen by
referring back to Example 16-23 and Example 16-24 in Chapter 16, the
command-line compile commands are different for the two languages, in
that the VB.NET version has two additional namespaces referenced.
This is one of those mysterious, undocumented differences between VB.NET and C#. It turns out that there is a configuration file located in the .NET Framework program directory, called csc.rsp, which contains the list of default references for the C# compiler. There is no comparable configuration file or default list for VB.NET. Presumably the list of default references are hard-coded somewhere.
Creating
the proxy file
requires several steps, all performed at a command prompt. Further,
several of those steps involve a fair amount of typing of parameters,
with lots of places to make mistakes. Finally, when all is done, you
probably need to move or copy the resulting dll
file to a different directory.
This entire process can be automated somewhat by creating a batch file. Batch files are text files that contain one or more command-line operations. The batch file, which has an extension of .bat, can then be executed from the command line, and all the operations within the file are executed one after the other, just as though they were manually entered at the command line.
Back in the days of DOS, batch files were used extensively. It is possible to make them fairly sophisticated, with replaceable parameters, conditional processing, and other programmatic niceties. For our purposes, a simple batch file will do.
Example 17-3 shows the contents of a batch file that
changes to the correct current directory, runs the WSDL utility,
compiles the resulting source code, then copies the resulting
dll
from one bin directory
to another.
Example 17-3. csStockTickerProxy.bat
e: cd projectsProgramming ASP.NET rem Generate the proxy class source file wsdl /l:CS http://localhost/ProgAspNet/csStockTicker.asmx?wsdl rem Compile the proxy class source file csc /out:bincsStockTickerProxy.dll /t:library /r:system.dll,system.web.dll,system.web.services.dll StockTicker.cs rem Copy the dll copy bincsStockTickerProxy.dll c:inetpubwwwrootcsWebServiceConsumer1in
The first line in the batch file makes drive E the current drive. The
next line changes the current directory. Blank lines are ignored.
Lines beginning with rem
are comments and are also
ignored, although the contents are displayed on the screen as the
file is processed. After the WSDL utility is run and the resulting
file is compiled, it is copied. This last command is equivalent to:
copy e:projectsProgramming ASP.NETincsStockTickerProxy.dll c:inetpubwwwrootcsWebServiceConsumer1in
Be careful of inadvertent line breaks. A line break in a batch file is the equivalent of hitting the Enter key on the keyboard.
52.15.135.175