17.2 Creating the ProxyAs 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:
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. 17.2.1 Manually Generating the Proxy Class Source CodeTo 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\test.vb http://localhost/ProgAspNet/vbStockTicker.asmx?WSDL Table 17-2 shows some of the other switches available to the WSDL utility.
For a complete list of parameters for wsdl.exe, enter the following from the command line: wsdl /? 17.2.2 Proxy Class DetailsCompare 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.1.4322.573 // // 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 Stock GetHistory(string StockSymbol) { object[] results = this.Invoke("GetHistory", new object[] { StockSymbol}); return ((Stock)(results[0])); } [System.Diagnostics.DebuggerStepThroughAttribute( )] public System.IAsyncResult BeginGetHistory(string StockSymbol, System.AsyncCallback callback, object asyncState) { return this.BeginInvoke("GetHistory", new object[] { StockSymbol}, callback, asyncState); } [System.Diagnostics.DebuggerStepThroughAttribute( )] public Stock EndGetHistory(System.IAsyncResult asyncResult) { object[] results = this.EndInvoke(asyncResult); return ((Stock)(results[0])); } ... } Here's part of the VB .NET proxy class: '------------------------------------------------------------------------------ ' <autogenerated> ' This code was generated by a tool. ' Runtime Version: 1.1.4322.573 ' ' Changes to this file may cause incorrect behavior and will be lost if ' the code is regenerated. ' </autogenerated> '------------------------------------------------------------------------------ Option Strict Off Option Explicit On Imports System Imports System.ComponentModel Imports System.Diagnostics Imports System.Web.Services Imports System.Web.Services.Protocols Imports System.Xml.Serialization ' ' This source code was auto-generated by Microsoft.VSDesigner, ' Version 1.1.4322.573. ' Namespace localhost '<remarks/> <System.Diagnostics.DebuggerStepThroughAttribute( ), _ System.ComponentModel.DesignerCategoryAttribute("code"), _ System.Web.Services.WebServiceBindingAttribute( _ Name:="vbStockTickerSoap", _ [Namespace]:=" www.LibertyAssociates.com"), _ System.Xml.Serialization.XmlIncludeAttribute( _ GetType(System.Object( )))> _ Public Class vbStockTicker Inherits System.Web.Services.Protocols.SoapHttpClientProtocol '<remarks/> Public Sub New( ) MyBase.New Me.Url = "http://localhost/WebApplication1/vbStockTicker.asmx" End Sub '<remarks/> <System.Web.Services.Protocols.SoapDocumentMethodAttribute( _ "www.LibertyAssociates.com/GetHistory", _ RequestNamespace:=" www.LibertyAssociates.com", _ ResponseNamespace:=" www.LibertyAssociates.com", _ Use:=System.Web.Services.Description.SoapBindingUse.Literal, _ ParameterStyle:=SoapParameterStyle.Wrapped)> _ Public Function GetHistory(ByVal StockSymbol As String) As Stock Dim results( ) As Object = Me.Invoke("GetHistory", _ New Object( ) {StockSymbol}) Return CType(results(0),Stock) End Function '<remarks/> Public Function BeginGetHistory(ByVal StockSymbol As String, _ ByVal callback As System.AsyncCallback, _ ByVal asyncState As Object) As System.IAsyncResult Return Me.BeginInvoke("GetHistory", _ New Object( ) {StockSymbol}, callback, asyncState) End Function '<remarks/> Public Function EndGetHistory(ByVal asyncResult As _ System.IAsyncResult) As Stock Dim results( ) As Object = Me.EndInvoke(asyncResult) Return CType(results(0),Stock) End Function '<remarks/> ... End Class End Namespace There is no need to understand fully all the nuances of the proxy class source code file. But there are several points worth noting:
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 this chapter. 17.2.3 Compiling the Proxy ClassThe output of the WSDL utility is a class source code file for the proxy. This source code then must be compiled with the appropriate command-line compiler. For VB.NET, use the following single command line to compile the proxy: vbc /out:bin\vbStockTickerProxy.dll /t:library /r:system.dll,system.web.dll,system.web.services.dll, system.xml.dll,system.data.dll StockTicker.vb For C#, use the following single command line: csc /out:bin\csStockTickerProxy.dll /t:library /r:system.dll,system.web.dll,system.web.services.dll StockTicker.cs
17.2.4 Automating the Process with a Batch FileCreating 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, and then copies the resulting dll from one bin directory to another. Example 17-3. csStockTickerProxy.bate: cd \projects\Programming 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:bin\csStockTickerProxy.dll /t:library /r:system.dll,system.web.dll,system.web.services.dll StockTicker.cs rem Copy the dll copy bin\csStockTickerProxy.dll c:\inetpub\wwwroot\csWebServiceConsumer1\bin 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:\projects\Programming ASP.NET\bin\csStockTickerProxy.dll c:\inetpub\wwwroot\csWebServiceConsumer1\bin Be careful of inadvertent line breaks. A line break in a batch file is the equivalent of hitting the Enter key on the keyboard. |