Web Service Clients

I l @ ve RuBoard

Web Service Clients

When you consume a Web service, you're presented with a lot of complex information about the available operations and how the service expects to be called. This section will look at how Visual Studio .NET and the Microsoft .NET Framework can take this information and present it in a developer-friendly way.

Note

This chapter uses the URL http://fourthcoffee.com as the base URL for all Web services so the namespaces appear realistic. However, the Web services mentioned do not actually exist at fourthcoffee.com ”this is a dummy URL provided by Microsoft for documentation purposes. We achieved the realistic effect by adding a line to our \Windows\System32\drivers\etc\ hosts file to map fourthcoffee.com to 127.0.0.1 ( localhost ), so you must perform a similar mapping if you want to try out the samples without reimporting the Web references and recompiling them. However, all of the examples will work perfectly well with localhost ” you'll simply have to do some mental mapping of the names as you go through the examples in the chapter.


The Client View of a Web Service

The publisher of a Web service will provide potential clients with information about the capabilities and location of the service. As you saw in Chapter 17, this usually takes the form of a Web Services Description Language (WSDL) document that contains a list of the operations provided by the service and the input and output parameters of these operations. A WSDL document uses XML Schema syntax to describe any complex types used as parameters. The operations are grouped into portType elements, which are combined with particular transport mechanisms to create ports. The WSDL document brings together one or more ports to create a service description that maps to a running instance of the service.

Given this information, you can construct your own SOAP calls using a System.Net.WebClient , System.Net.HttpWebRequest , System.Net.Sockets.TcpClient , or even a System.Net.Sockets.Socket object. However, this is messy work and requires that you have intimate knowledge of the SOAP specification and XML Schemas. Most of us have homes to go to and lives to lead outside of work, however, so the .NET Framework provides tools to help you quickly and easily create a client-side proxy to represent the Web service. These tools are integrated into Visual Studio .NET. Using these tools, you can create a proxy that represents the Web service as a .NET class and exposes the SOAP operations as methods of this class. This scenario is shown in Figure 18-1. The server shown is based on ASP.NET, but the same proxy pattern applies to all Web services no matter how they're implemented. (This is the beauty of Web services.)

Figure 18-1. The client-side proxy represents the Web service for .NET clients.

You can invoke the proxy from any supported .NET language. In fact, you can generate it in the main .NET languages, including Microsoft Visual J#. To invoke a SOAP operation, the client calls a method of the proxy. The Web service proxy blends in with .NET programming paradigm, so it can be used from any type of .NET application, including ASP.NET pages, console applications, and Windows Forms, or from another Web service, as shown in Figure 18-2.

Figure 18-2. The same Web service proxy class can be used from any type of .NET application.

Creating a Web Service Client Using Visual Studio .NET

As an initial example, consider creating a client for the Fourth Coffee cake enquiry service as defined in Chapter 17. This is a Web service version of the familiar FeedsHowMany method. As a customer of the Fourth Coffee company, you need to obtain information about the service from which to create your proxy. We'll discuss some more automated and scalable mechanisms later, but for now assume that you've engaged in an e-mail exchange with the company and that they've provided you with the URL where you can locate the service: http://fourthcoffee.com/CakeCatalogService/Enquiry.asmx .

Note

The Fourth Coffee company Web services are provided as sample files for this chapter. When you unpack the sample files, you'll find a Windows Installer file, CakeCatalogServiceInstaller.msi, at the top level. If you've already installed and run the sample files for Chapter 17, you do not need this installer. However, if you haven't installed the CakeCatalogService project from Chapter 17, this installer gives you a quick and easy way to do so. To install CakeCatalogService, double-click on the installer file and select all the defaults. (Click the Next buttons until you get to Finish, and then click that.) The Visual Studio .NET solution file associated with these services can be found in the sample files in the CakeCatalogService folder. Once you've installed the installer file, you can examine and change the Web service code by loading the CakeCatalogService.sln solution file from this folder.


The client is a Console application called SimpleCakeEnquiry (contained in the file SimpleCakeEnquiry.jsl in the SimpleCakeEnquiry project). To generate the Web service proxy, you simply add a Web reference to the Enquiry Web service. You can do this by choosing Add Web Reference from the Project menu. In the Add Web Reference dialog box, type the URL of the Web service description (the WSDL file). Figure 18-3 shows what happens when you enter the enquiry service URL ( http://fourthcoffee.com/CakeCatalogService/Enquiry.asmx ). Visual Studio engages in a conversation with the Web service and obtains the same information we retrieved when we tested the Web service in Chapter 17. It uses the ?wsdl suffix to obtain the WSDL for the service (referred to in the dialog box as the Contract) and displays the HTML documentation available from the Web service, as shown in Figure 18-3.

Figure 18-3. The Add Web Reference dialog box allows you to view Web service information.

To create the client-side proxy, click Add Reference. This imports the Web service description and builds the proxy in the language of the current project. The imported reference consists of the WSDL document, a DISCO document and a reference map. The latter two items are discussed later, but if you choose Show All Files from the Project menu, you can see the proxy itself (Reference.jsl) in Solution Explorer under the Reference.map folder below com.fourthcoffee in the list of Web references. You can view the code for this proxy, as shown in Figure 18-4.

Figure 18-4. Adding a Web service reference to an application

We won't walk through the code line by line, but here are the main points to note:

  • The package in which the proxy classes are generated is the same as the host name from the WSDL document ”in this case, fourthcoffee.com . The package name used for the proxy should be unique or as distinctive as possible to avoid name clashes with other classes. (This is generally a good practice.) The use of the Web service domain name ( reversed ) as the basis for the package name will avoid clashes with Web services provided by other servers:

     packagecom.fourthcoffee; 
  • The class name is the name of the service as defined in the <service> element of the WSDL document ”in this case, Enquiry . This class extends System.Web.Services.Protocols.SoapHttpClientProtocol , which provides the basic connectivity and marshaling support for an HTTP-based SOAP exchange.

  • The proxy class has a default constructor that sets the target URL of the service. The default value for this URL is obtained from the <soap:address> element contained in the <service> element in the WSDL document:

     publicEnquiry(){ this.set_Url("http://fourthcoffee.com/CakeCatalogService/Enquiry.asmx"); } 
  • Each method exposed by the Web service is represented by a method of the proxy. These methods are decorated with some of the XML serialization and Web service attributes you learned about in Chapter 17. These attributes define the element names and namespaces to be used in the request message and to be expected in the response. All of this information is automatically gathered from the appropriate part of the WSDL document:

     /**@attributeSystem.Web.Services.Protocols.SoapDocumentMethodAttribute ("http://fourthcoffee.com/CakeCatalog/FeedsHowMany", RequestNamespace="http://fourthcoffee.com/CakeCatalog/", ResponseNamespace="http://fourthcoffee.com/CakeCatalog/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)*/ publicintFeedsHowMany(intdiameter,Stringshape,Stringfilling){ 
  • The methods themselves use the protected Invoke method of the SoapHttpClientProtocol class to send the SOAP message and wait for the response. You'll see three methods for every SOAP operation: one for synchronous operation and two for asynchronous operation. We'll discuss the asynchronous methods later. At this stage, we're considering only synchronous exchanges.

     publicintFeedsHowMany(intdiameter,Stringshape,Stringfilling) { Object[]results=this.Invoke("FeedsHowMany",newObject[]{ (System.Int32)diameter, shape, filling}); return((int)(System.Int32)(results[0])); } 

To use the proxy, you should import the package and instantiate the proxy. You can then call the methods of the proxy as you would methods of any other .NET Framework class. The complete code for the SimpleCakeEnquiry class, which calls the Enquiry Web service and displays the result, is shown here:

 packageSimpleCakeEnquiry; importcom.fourthcoffee.*; publicclassSimpleCakeEnquiry { /**@attributeSystem.STAThread()*/ publicstaticvoidmain(String[]args) { Enquiryenq=newEnquiry(); intfeeds=enq.FeedsHowMany(10, "square", "fruit"); System.Console.WriteLine("Thatcakefeeds " +feeds+ " people"); } } 

The Web service will inform you that this cake feeds 25 people.

Going Beyond the Simple Client Scenario

At this point, using Web services might seem easy. To an extent, this is true. If the service you need to communicate with is simple, like the Enquiry service used so far, you'll have few problems. However, in a commercial application, you need to consider what might happen in the following scenarios:

  • An error occurs during a Web service call.

  • The Web service moves to a different URL.

  • The Web methods take complex types as parameters and return values.

  • You need to use multiple Web services in a client application.

  • A Web method takes a long time to complete.

Security is also a major concern, especially when a Web service is used across the Internet. Web service client developers will want to take advantage of other functionality (such as state management) that was covered from the server perspective in Chapter 17. Here, we'll discuss several issues related to creating Web service proxies in the "real world."

Handling Errors

When you define the interfaces between components in an application, you must define the set of exceptions or error conditions that can occur. The same is true with Web services. Web methods can throw application-defined exceptions. However, as you saw in Chapter 17, these exceptions are converted to SoapException instances as they cross the Web service boundary, so they can be converted into SOAP Faults that can be passed back to the client. Thus, from the client point of view, you must be prepared to encounter three types of exception:

  • An underlying exception related to transporting information back and forth. For example, the server might not be able to respond to your request because it has crashed. In this case, you'll receive a low-level exception such as a RuntimeException raised by one of the underlying network classes. This type of exception comes with the territory when you develop distributed applications, and you cannot ignore it ”you should catch such exceptions and try to handle them gracefully.

  • A SoapException that's generated because of a problem in the underlying SOAP protocol exchange. A SoapException can be generated by a SOAP class used by the proxy to send and receive the SOAP messages. If the Code property is anything other than SoapException.ServerFaultCode , the problem is not due to an error on the server. (We'll discuss the codes shortly.)

  • A SoapException that acts as a wrapper for an application-level error. When a Web service operation generates a SOAP Fault, the client-side .NET proxy will convert this into a SoapException and throw the exception for your client to catch. If the SOAP Fault is the result of an exception generated in a .NET-based Web service, the Message property of the SoapException will be a copy of the Message from the underlying exception.

The moral here is that you should always surround Web service calls with an exception handler.

One consideration here is how to determine what type of error has occurred on the server. Some parts of an exception that you might find useful, such as the stack trace, will relate to the client side rather than the server. Other parts , such as the inner exception, will not be set. However, extra information is available in a SoapException that you might find useful:

  • The Code property indicates why the call failed. This property can be set to one of four values defined in the SoapException class. A value of ClientFaultCode indicates that the client sent something that was not correct, either in content or in formatting. ServerFaultCode indicates that the error occurred during server-side processing but was not due to bad information supplied by the client. VersionMismatchFaultCode indicates that incompatible versions of SOAP are in use (a rare occurrence). Finally, MustUnderstandFaultCode indicates that one of the SOAP headers labeled as MustUnderstand was not understood by the server. Again, this condition is unlikely to occur unless you've set your own SOAP headers.

  • The Actor property indicates where the exception occurred. This is typically the URL of the Web service that was processing the request.

  • The Detail property can contain more information if the Web service has explicitly generated the SOAP Fault. An example of this is shown in the FeedsHowMany method that is part of the proxy generated for the EnquiryWithException Web service. (The creation of the detail on the server side was shown in Chapter 17.)

The following code fragment shows SOAP exception handling from the ExceptionCakeEnquiry sample class:

 publicstaticvoidmain(String[]args) { try { CakeCatalogEnquiryenq=newCakeCatalogEnquiry(); intfeeds=enq.FeedsHowMany(10,null, "fruit"); System.Console.WriteLine("Thatcakefeeds " +feeds+ " people"); } catch(SoapExceptionex) { System.Console.WriteLine("SOAPException: " +ex.get_Message()); System.Console.WriteLine("Code: " +ex.get_Code()); System.Console.WriteLine("Actor: " +ex.get_Actor()); System.Console.WriteLine("Detail: " + ex.get_Detail().get_OuterXml()); } catch(Exceptionex) { System.Console.WriteLine("UnexpectedException: " + ex.getMessage()); } } 

The result of running this code is shown in Figure 18-5.

Figure 18-5. The SoapException class, which contains SOAP-specific information

Retargeting the Proxy

In the initial client application, we accepted the default location for the service obtained from the WSDL document. However, the service might move ”the service provider might make a duplicate or updated service available at another URL, for example. Alternatively, if you're developing your own Web services as part of a distributed application, you'll have to move your development Web services to a testing environment and then on to the live servers. Because your Web reference will have been created based on the WSDL generated from the development Web service, it will contain the host name of the server on which that development Web service ran. As you move from development to test to live deployment, your client will need to be retargeted at a new server.

The most direct way to retarget the proxy is to change its Url property:

 CakeCatalogEnquiryenq=newCakeCatalogEnquiry(); enq.set_Url("http://otherserver/CakeCatalogService/Enquiry.asmx"); 

If the client is a Console application, you could supply the replacement URL as a command-line argument. But a better solution that works for other types of clients is to put the URL in a configuration file, such as an ASP.NET application's web.config file. Visual Studio .NET provides an easy way of doing this. If you select a Web reference in Solution Explorer and look at the Properties for it, you'll see that one of these properties is URL Behavior. By default, this is set to Static . If you change this property to Dynamic , the generated proxy will be altered so that it looks in the configuration file for the Web service URL before using the URL provided in the WSDL document:

 publicCakeCatalogEnquiry(){ StringurlSetting= System.Configuration.ConfigurationSettings.get_AppSettings().get_Item("ExceptionCakeEnquiry.com.fourthcoffee.CakeCatalogEnquiry"); if((urlSetting!=null)){ this.set_Url(String.Concat(urlSetting, "")); } else{ this.set_Url("http://fourthcoffee.com/CakeCatalogService/EnquiryWithException.asmx"); } } 

If you use this mechanism, you should define the value of the URL in the <appSettings> section of the application configuration file. This section consists of name/value pairs. Taking the sample code shown as an example, the name in the pair would be ExceptionCakeEnquiry.com.fourthcoffee.CakeCatalogEnquiry and the value would be the appropriate URL for the Web service:

 <configuration> <appSettings> <addkey="ExceptionCakeEnquiry.com.fourthcoffee.CakeCatalogEnquiry" value="http://test01/CakeCatalogService/EnquiryWithException.asmx" /> </appSettings> </configuration> 

You might also want to change the target URL of the Web service client when you need to debug your services. When problems occur, it can be useful to capture SOAP messages going back and forth. You can do this using a tool such as TcpTrace (from http://www.pocketsoap.com), which will capture traffic between your client and the Web service. To do this, it listens on a given port (usually port 8080 on the local machine), logs all the traffic it receives, and then passes it on to the real destination. To use this tool, you must retarget your Web service proxy to the port and machine on which TcpTrace is running by setting its Url property. If TcpTrace is running on your local machine, you use a URL starting with http://localhost:8080 .

Handling Complex Types

The FeedsHowMany method uses simple types for its parameters and return value. However, as discussed in Chapter 17, many applications will need to pass across more complex types. The Ordering Web service (in the Ordering.asmx file in the CakeCatalogService sample project) defines the Submit ­Order method, which takes an instance of a CakeCatalogService.Order as a parameter and returns an instance of CakeCatalogService.WsReceipt . The Order class itself contains an array of CakeCatalogService.Item instances and hence forms a nested complex type. (The WSDL generated for this service was discussed in Chapter 17.) The question from the client perspective is, "What sort of proxy methods are generated from this WSDL?"

A client for the Ordering Web service is provided in the OrderCakeClient project (in the file OrderCakeClient.jsl). This project includes a Web reference for the Web service at http://fourthcoffee.com/CakeCatalogService/Ordering.asmx , which generates the Reference.jsl file in the com.fourthcoffee namespace. The proxy file contains the proxy class itself ”which is called Ordering ” and also contains mappings for the complex data types it has found in the WSDL, as shown here:

 /**@attributeSystem.Xml.Serialization.XmlTypeAttribute (Namespace="http://fourthcoffee.com/CakeCatalog/")*/ publicclassOrder{ /**<remarks/>*/ publicSystem.StringCustomerName; /**<remarks/>*/ publicItem[]OrderContents; /**<remarks/>*/ publicintOrderId; } /**<remarks/>*/ /**@attributeSystem.Xml.Serialization.XmlTypeAttribute (Namespace="http://fourthcoffee.com/CakeCatalog/")*/ publicclassItem{ /**<remarks/>*/ publicSystem.StringItemDescription; /**<remarks/>*/ publicintQuantity; } /**<remarks/>*/ /**@attributeSystem.Xml.Serialization.XmlTypeAttribute (Namespace="http://fourthcoffee.com/CakeCatalog/")*/ publicclassWsReceipt{ /**<remarks/>*/ publicSystem.StringCustomerName; /**<remarks/>*/ publicSystem.Object[]OrderContents; /**<remarks/>*/ publicintOrderId; /**<remarks/>*/ publicSystem.DateTimeTimestamp; } 

The Order , Item , and WsReceipt classes are client-side representations of the classes with the same names that are used on the server side. On the client side, these classes become simple data structures that expose data through public data members . (See the section titled "Consequences of XML Serialization" in Chapter 17.) These classes are defined in the same package as the Ordering class ( com.fourthcoffee ); their associated XML elements appear in SOAP messages under the XML namespace http://fourthcoffee.com/CakeCatalog/ .

The following code fragment from OrderCakeClient.jsl builds up an order by directly accessing the public member variables defined in the Order and Item classes and submits it using the SubmitOrder method:

 Orderorder=newOrder(); order.CustomerName= "PeterWaxman"; order.OrderId=12345; Itemitem1=newItem(); item1.ItemDescription= "BirthdayCake"; item1.Quantity=7; Itemitem2=newItem(); item2.ItemDescription= "PartyCake"; item2.Quantity=3; Item[]items={item1,item2}; order.OrderContents=items; WsReceiptreceipt=orders.SubmitOrder(order); 

The contents of the receipt can be accessed through the public member variables defined in the WsReceipt class.

Handling Bulk Data

In the previous section, the Order class contained an array of Item instances. It is straightforward to pass an array comprising elements of a fixed type between a Web service client and server (as long as that type is serializable and its data is publicly accessible). However, this might not always be the most convenient way. As discussed in the section titled "Passing DataSet Objects" in Chapter 17, you might want to transmit data that has been retrieved from a database. Ideally, you shouldn't convert all of this into application-specific types solely to pass it across the Web service interface.

The RetrieveCatalog method of the sample file Catalog.asmx.jsl (in the CakeCatalogService project) returns a DataSet . The WSDL produced by this was discussed in the section titled "Passing DataSet Objects" in Chapter 17. This WSDL has been imported into the sample project DataSetCakeCatalog, which contains a client class called DataSetCakeCatalog . The main method of the DataSetCakeCatalog class invokes the RetrieveCatalog method of the Catalog Web service. The DataSetCakeCatalog class lists the contents of the retrieved DataSet as if it were any other DataSet obtained from an ordinary data source:

 Catalogcatalog=newCatalog(); DataSetds=catalog.RetrieveCatalog(); DataTabletable=ds.get_Tables().get_Item(0); DataRowCollectionrows=ds.get_Tables().get_Item(0).get_Rows(); DataColumnCollectioncolumns=table.get_Columns(); for(inti=0;i<columns.get_Count();i++) { if(i!=0)Console.Write(", "); Console.Write("" +columns.get_Item(i).get_ColumnName()); } Console.WriteLine(); System.Collections.IEnumeratorenumerator=rows.GetEnumerator(); while(enumerator.MoveNext()) { DataRowrow=(DataRow)enumerator.get_Current(); for(inti=0;i<columns.get_Count();i++) { if(i!=0)Console.Write(", "); if(row.get_Item(i)==null (row.get_Item(i)instanceofSystem.String&& row.get_Item(i).ToString().get_Length()==0)) { Console.Write("<empty>"); } else { Console.Write("" +row.get_Item(i)); } } Console.WriteLine(); 

The result of running this program is shown in Figure 18-6.

Figure 18-6. The DataSetCakeClient program lists the contents of the DataSet received.

Tip

If the use of DataSet objects to transport bulk data appeals to you but the clunky syntax puts you off (calls such as get_Item(0) are not very descriptive or type-safe), consider using typed DataSet objects. A typed DataSet class lets you access data by using meaningful table, row, and column names. For more information on typed DataSet objects, see Chapter 7.


Passing XML Documents

As you might expect, sending and receiving XML documents through Web service interfaces is quite simple. Again, we dealt with the server-side aspects in Chapter 17, and you saw an example of this when we created the method RetrieveXmlCatalog in the sample file Catalog.asmx.jsl (in the CakeCatalogService project). From the client side, all you need to do is make the call, as shown here:

 Catalogcatalog=newCatalog(); XmlNodenode=catalog.RetrieveXmlCatalog(); Console.WriteLine("Nodeis " +node.get_OuterXml()); Console.WriteLine("NamespaceforCakeCatalogis " + node.get_NamespaceURI()); 

The code calls the method RetrieveXmlCatalog , which returns a System.Xml.XmlNode . Once you've received the XmlNode , you can manipulate it in the same way that you would any other XML document. For more information on manipulating XML, see Chapter 5. The full client can be found in the sample project XmlDocumentCakeClient. When you run this program, you'll see the output shown in Figure 18-7.

Figure 18-7. XML documents and their namespace information pass easily through Web service interfaces.

One interesting point is that the namespace specified in the original file ( http://fourthcoffee.com/schemas/cakecatalog.xsd ) is not affected by being encapsulated in various other XML documents on its passage back from the server.

Maintaining State

In Chapter 17, you saw how a Web service based on ASP.NET can maintain state on behalf of a client using the server-side Session object. For this to work, any client must be able to send and receive cookies. By default, the proxies generated from the WSDL description of the service do not use cookies. However, you can change this behavior by creating a new System.Net.CookieContainer object and passing it to the proxy through its CookieContainer property, as shown here:

 importcom.fourthcoffee.Ordering; Orderingorders=newOrdering(); orders.set_CookieContainer(newSystem.Net.CookieContainer()); 

Caution

This state management mechanism, available only when you communicate with Web services, is based on ASP.NET. It might be available on other Web services, such as those based on JavaServer Pages (JSPs) or servlets, but you should check the documentation for those services to ensure compatibility.


In this case, the proxy is for the Fourth Coffee ordering service defined in the CakeCatalogService project. This service provides a sequence of methods that can be used to build up an order on the client. It also exposes an Increment method that increments an application-scope count. The following code fragment from the SessionCakeClient.jsl sample file in the SessionCakeClient project shows a client using these calls. It calls the Increment method multiple times to illustrate how the application value can be updated and then builds up an order:

 Console.WriteLine("Incrementing: " +orders.Increment()); Console.WriteLine("Incrementing: " +orders.Increment()); Console.WriteLine("Incrementing: " +orders.Increment()); Console.WriteLine("Incrementing: " +orders.Increment()); Console.WriteLine("Incrementing: " +orders.Increment()); orders.StartBuiltUpOrder("PeterWaxman",987098); orders.AddToOrder("PartyCake",12); orders.AddToOrder("WeddingCake",3); WsReceiptreceipt=orders.SubmitBuiltUpOrder(); Console.WriteLine("Receipthas " + receipt.OrderContents.length+ " items"); 

The result of this code is shown in Figure 18-8. Note that this was the second run of the client code, so the application-wide value started from 5.

Figure 18-8. Cookies allow the server to maintain state on behalf of the client.

Note

If you're familiar with ASP.NET, you might be wondering whether Web service proxies can work with cookieless mode. The simple answer is that they cannot. Cookieless mode uses URL rewriting (or mangling), which involves ASP.NET generating a different URL containing the state token and then telling the client to use this new URL. ASP.NET then intercepts this second request and retrieves the state token before forwarding the request to the real destination. This sequence relies on redirecting the client to the new URL by sending it an HTTP 302 object moved message. The Web service proxy does not handle redirection in this way, and so you'll get an exception.


A Brief Note on Polymorphism

The WsReceipt object returned from SubmitBuiltUpOrder contains a list of the items ordered. This list takes the form of an ArrayList containing Item objects. The declaration of this instance variable in the server-side WsReceipt class looks like this:

 privateArrayListorderContents; 

This instance variable is exposed through a property called OrderContents . Given that an ArrayList can contain any type of .NET object, in the WSDL description it looks like this:

 <s:elementminOccurs="0" maxOccurs="1" name="OrderContents" type="s0:ArrayOfAnyType" /> 

In the transmitted SOAP document, this WSDL description leads to the following XML elements:

 <orderContents> <anyTypexsi:type="Item"> <itemDescription>PartyCake</itemDescription> <quantity>12</quantity> </anyType> <anyTypexsi:type="Item"> <itemDescription>WeddingCake</itemDescription> <quantity>3</quantity> </anyType> </orderContents> 

As you can see, even though the basic element containing the serialized OrderContents is <anyType> , it has an xsi:type attribute that indicates a type of Item . The type attribute is added as the class is marshaled into XML; the XmlSerializer detects the type of the object it is serializing and adds the xsi:type attribute to each anyType element.

On the client side, the OrderContents property of the WsReceipt class is defined in the Reference.jsl file, as follows :

 publicclassWsReceipt{ /**<remarks/>*/ publicObject[]OrderContents; } 

The anyType array has become an array of Object (which seems fairly reasonable in marshaling terms). How do you then get back your Item information? The key here lies in the fact that the anyType elements are denoted in the SOAP message as actually being of type Item . The XmlSerializer on the client side has access to this information. It also knows from the WSDL that there's a class definition in the same XML namespace for an Item class. Therefore, under the covers it actually unmarshals the contents of the anyType element into an instance of the Item class. These instances are then used to populate the array of Objects . Because the objects in the OrderContents array are really Item objects, all you need to do to use them is to cast each one to an Item type as you retrieve it. The following code fragment, from the main method of the AnyTypeCakeClient class (in the project AnyTypeCakeClient), shows how to do this:

 for(inti=0;i<receipt.OrderContents.length;i++) { Itemitem=(Item)receipt.OrderContents[i]; Console.WriteLine("Orderitem " +i+ " isa " + item.ItemDescription+ ",(" + item.Quantity+ " unit(s))"); } 
Making Secure Calls

In Chapter 17, we secured the Fourth Coffee PartnerServices Web service using Windows-based authentication. Anyone who wants to access this service must provide security credentials as part of the interaction with the server, as shown in this code fragment from the SecureCakeClient.jsl example file in the SecureCakeClient project:

 importcom.fourthcoffee.PartnerServices; PartnerServicesps=newPartnerServices(); NetworkCredentialcredentials= newNetworkCredential("PeterWaxman",  "ILoveCakes"); CredentialCachecredentialCache=newCredentialCache(); ps.set_Credentials(credentialCache); credentialCache.Add(newSystem.Uri(ps.get_Url(default.htm)), "Basic",credentials); Stringrecipe=ps.GetRecipe(); Console.WriteLine("Thesecretrecipeis: " +recipe); 

This code creates an instance of the System.Net.NetworkCredential class. (You might recall the NetworkCredential class and some of the other classes and concepts discussed here from Chapter 9.) A username (Peter Waxman) and password (ILoveCakes) are supplied to the NetworkCredential constructor. This set of credentials must then be registered with the Web service proxy. You can add one or more sets of credentials to the credential cache held by the proxy. By default, the proxy has no cache set. (It uses anonymous authentication.)

To use a credential cache, you must create an instance of System.Net.CredentialCache and assign it to the Credentials property of the proxy (using the set_Credentials method in J#). You can then add the credentials you created earlier to the cache, specifying the target URL for which they are to be used (encapsulated in a System.Net.Uri object) and the type of authentication. The authentication type can be Basic, Digest, or NTLM. In this case, the credentials supplied will be used for Basic authentication. When the client makes the GetRecipe call on the Web service proxy, the proxy will provide the given credentials to authenticate the user and complete the SOAP operation.

An alternative is to supply the credentials of the current user as follows:

 ps.set_Credentials(System.Net.CredentialCache.get_DefaultCredentials()); 

Using credentials in this way relies on Integrated Windows authentication being configured.

Note

If you're importing Web service information from an ASP.NET-based Web service using the standard Visual Studio Web reference mechanism, you should be aware of one security issue: If the identity under which you're developing the code doesn't have access to the secure service (your code might be passing the credentials programmatically, for example), you won't be able to access the Web service to obtain its WSDL by using ?wsdl unless you provide the appropriate credentials. If you're also the Web service developer, you might want to configure the security after you've imported the Web reference.


If you're using Secure Sockets Layer (SSL), you must access the Web service using a URL beginning with https . If this URL was not set in the original WSDL file, you can update the target URL by using the proxy's Url property.

If you want to use certificates, you can add them through the ClientCertificates property of the Web service proxy. You create instances of System.Security.Cryptography.X509Certificates.X509Certificate and add them to the collection as follows:

 X509Certificatecertificate= X509Certificate.CreateFromCertFile("c:\certificates\PeterW.cert"); ps.ClientCertificates.Add(certificates); Stringrecipe=ps.GetRecipe(); 
Making Transactional Calls

Chapter 17 showed how to use the WebMethodAttribute to make an ASP.NET-based Web service method transactional. This requires no special processing from the client side. The transaction is scoped to the WebMethod associated with a particular Web service operation and completes or aborts at the end of the method. There is currently no way of propagating transaction context between a Web service client and the Web service itself, so if the client is executing in the context of an existing transaction, it cannot make the server-side transaction part of this existing transaction. This problem should eventually be solved by the Global XML Web Services Architecture (GXA), as discussed at the end of Chapter 17.

Making Asynchronous Calls

Every Web service proxy created by Visual Studio .NET provides three method calls for each SOAP operation. For example, consider the SubmitOrder Web method from the Ordering.asmx.jsl sample Web service provided as part of the CakeCatalogService sample project. Open the Ordering.asmx.jsl file and find the SubmitOrder method. To simulate heavy server-side processing, add a 5-second wait into the implementation, as shown here:

 /**@attributeWebMethod()*/ publicWsReceiptSubmitOrder(Orderorder) { //Shouldprobablyhavesomedatabasestuffhere... //...simulatethisbywaitingforatime:^) System.Threading.Thread.Sleep(5000); WsReceiptreceipt=newWsReceipt(order.get_CustomerName(), order.get_OrderId(),DateTime.get_Now()); returnreceipt; } 

Based on the WSDL description for this one method, three distinct methods are created in the Web service proxy. (The attributes have been removed for clarity.)

 publicWsReceiptSubmitOrder(Orderorder){ Object[]results=this.Invoke("SubmitOrder",newObject[]{ order}); return((WsReceipt)(results[0])); } publicSystem.IAsyncResultBeginSubmitOrder(Orderorder,     System.AsyncCallbackcallback,ObjectasyncState){ returnthis.BeginInvoke("SubmitOrder",newObject[]{ order},callback,asyncState); } publicWsReceiptEndSubmitOrder(System.IAsyncResultasyncResult){ Object[]results=this.EndInvoke(asyncResult); return((WsReceipt)(results[0])); } 

The BeginSubmitOrder and EndSubmitOrder methods represent an asynchronous invocation of the SubmitOrder operation defined in the WSDL. The asynchronous mechanism does not rely on the availability of an underlying asynchronous SOAP transport (such as SMTP). Instead, it uses a thread from the application domain's thread pool, which calls the synchronous form of the SOAP operation. The BeginSubmitOrder call will start the new thread and return quickly, so the caller can continue. Some of the types in the asynchronous method signatures might look familiar because Web service asynchronous calls use the same pattern and some of the same classes as asynchronous I/O (as discussed in Chapter 8).

As with any asynchronous operation, the main challenge is to retrieve the return value from the method ”in this case, the receipt. Continually polling to see whether the asynchronous operation has completed defeats the purpose of invoking the method asynchronously. Instead, you can register a delegate that will be called when the operation completes. The following code fragment is from the sample file AsyncCakeClient.jsl and shows how you can register for notification:

 booleanstillWaiting=true; publicvoidCallback(System.IAsyncResultresult) { stillWaiting=false; } publicvoidAsynchronousCall(Orderorder) { Orderingorders=newOrdering(); IAsyncResultresult=orders.BeginSubmitOrder(order, newAsyncCallback(Callback), null); while(stillWaiting) { System.Threading.Thread.Sleep(500); Console.WriteLine("Doingusefulwork(honest!)"); } WsReceiptreceipt=orders.EndSubmitOrder(result); Console.WriteLine("Receipt: "); Console.Write(receipt.CustomerName+ "'sordernumber "); Console.Write(receipt.OrderId+ " wasreceivedon "); Console.WriteLine("" +receipt.Timestamp); } 

The AsynchronousCall method takes an instance of Order and uses the BeginSubmitOrder method to start the asynchronous operation. In addition to the order, it passes in an instance of the AsyncCallback delegate. This delegate wraps the Callback method that sets the boolean instance variable stillWaiting to false . The stillWaiting variable is used as the condition of the while loop following the call to BeginSubmitOrder . The program will then loop. In each loop, it will sleep for half a second, write out a message, and then test the value of stillWaiting . (In a commercial application, the client would do some useful work rather than simply sleeping.) If the value of stillWaiting is true , the program will sleep again. Once the asynchronous operation has completed, the program will exit the loop.

Once you know that the asynchronous operation has completed, you can retrieve the return value by calling EndSubmitOrder . However, because a call to BeginSubmitOrder returns very quickly, you might have multiple asynchronous calls ongoing at any one time on the same proxy. When you call EndSubmitOrder , how will the proxy know which call you're referring to? The answer lies in the return value from BeginSubmitOrder , which is an object that implements System.IAsyncResult . The IAsyncResult implementation acts as an identifier for the asynchronous call, so you pass this object to EndSubmitOrder to retrieve the correct return value. The output from the AsyncCakeClient class is shown in Figure 18-9.

Figure 18-9. You can perform useful work while a Web service operation executes asynchronously.

The example code allows you to do some useful work while the asynchronous call is made. However, there is no need to have your main body of code call EndSubmitOrder . You can make this call in the delegate and store the return value in an instance variable. When you access such an instance variable, the usual caveats about multithreaded variable access apply, so you should synchronize access to the variable. (See Chapter 8 for details.)

Namespaces, WSDL, and Manual Proxy Generation

Visual Studio's ability to import WSDL documents and automatically generate proxies is very useful and is a great aid to developer productivity. However, it comes with one slight drawback. You might have noticed that when you import a Web reference into a project, the reference appears under the name of the host given in the WSDL service element ( com.fourthcoffee in our example). So far, the clients have been targeted at one particular service on the Fourth Coffee site. What happens if you refactor your clients so they use more than one service?

For example, you can add a Web reference to the SimpleOrdering service (or any Fourth Coffee service). This will appear under the name com.fourthcoffee in the Web references. The classes defined in References.jsl for this Web reference are all placed in the package com.fourthcoffee . If you then add another Web reference, this time to the Catalog service, you might expect it to be added to the information under com.fourthcoffee . However, what actually happens is that a new Web reference, com.fourthcoffee1 , is created, as shown in Figure 18-10. All of the classes in the Reference.jsl file for this Web reference are defined in the package com.fourthcoffee1 .

Figure 18-10. Multiple entries are created for Web services on the same server.

You might be wondering what the problem is. Consider an enhanced catalog service that returns instances of a CatalogEntry class. In the project shown in Figure 18-10, this class would be com.fourthcoffee1.CatalogEntry . You could modify the SimpleOrdering service so it will take instances of CatalogEntry when the contents of an order are specified. This would make life a lot easier for the client-side developer ”or so you might think. In fact, when the Reference.jsl is created for this new SimpleOrdering service, it will create a new definition for CatalogEntry in the package com.fourthcoffee1 . The compiler will, with some justification, deem that the types are different and that you therefore cannot use a CatalogEntry retrieved from the Catalog service as a parameter to the SimpleOrdering service. To get around this, you can do one of two things:

  • Define schemas for your shared types and import them into your WSDL documents. This is the cleanest approach, although it does mean that you cannot import the Web service information into your application using the automatic WSDL generation capability (by appending ?wsdl to your service URLs).

  • Use the wsdl.exe command-line tool to generate the proxies manually so they're in the same namespace. Each file will contain its own definition of the CatalogEntry class, but they'll both be in the same package, so you must edit these files to remove one of the duplicate CatalogEntry definitions.

Manual proxy generation can also be convenient if you want to build your Web service clients as a batch process. To examine manual proxy generation, consider the Ordering Web service and the TestOrder Web service contained in the CakeCatalogService project. The TestOrder Web service contains a copy of the SubmitOrder method from the Ordering service, which uses the same complex types as parameters and return values. Adding separate Web references to each of these services would cause the issues described above. You can use wsdl.exe from the command line to generate your proxies manually as follows:

 wsdl.exe/n:com.fourthcoffee.ordering /l:"Microsoft.VJSharp.VJSharpCodeProvider,VJSharpCodeProvider, Version=7.0.3300.0,Culture=neutral,PublicKeyToken=b03f5f7f11d50a3a"  http://fourthcoffee.com/CakeCatalogService/Ordering.asmx?wsdl 

This command will generate the file Ordering.jsl, which contains the proxy for the Ordering service. The /n flag indicates the namespace to be created for the classes ”in this case, the com.fourthcoffee.ordering package. You can execute a similar command to generate a proxy for the TestOrder service. To use these manually generated proxies, follow this sequence of steps:

  1. Generate the required proxies using wsdl.exe, as described above. The project ManualProxyCakeClient contains the file GenerateProxies.bat, which manually generates proxies for the Ordering Web service and for the TestOrder Web service. (You must run this batch file manually before building the project.)

  2. Import the proxies into your client project by choosing Add Existing Item from the Project menu.

  3. Add a reference to .NET assembly System.Web.Services.dll. This is normally done for you when you use Visual Studio .NET to generate a Web reference, but in this case you must add it manually.

  4. If you compile at this point, you'll get a message about multiply-defined classes. The simplest way to solve this is to edit the TestOrder.jsl file and remove the definitions for Order , Item , and WsReceipt . The project will then compile and run correctly.

If the Web service definitions change, you must repeat these steps to regenerate the proxies.

Versioning and Updates

You should be sure to use the correct WSDL descriptions when you write Web service clients. This is particularly important in a development environment, in which service definitions can evolve rapidly . Visual Studio .NET will not automatically reimport a Web service description and regenerate the proxy because it does not know when the Web service description has changed. To ensure that a Web reference is up-to-date, you can choose Update Web Reference from the Web reference's shortcut menu, as shown in Figure 18-11.

Figure 18-11. You should be sure that your Web references are up-to-date.

It is important to ensure that references are kept up-to-date because no version checking is performed on Web method calls. For example, if you add a parameter to a Web method and invoke this new version of the method from a client built using the old method, the SOAP infrastructure will not generate an error. All parameter matching is performed using names, so when the call is unmarshaled, ASP.NET will attempt to match the parameter names in the call to the parameters in the method. Any unknown names will be given default values, such as 0 for an integer. This means that the call will not break but might well misbehave. The same issue arises when you change parameter names.

Other Client Types

So far, we've examined the use of clients for SOAP-based, document-centric Web services, which are the default type of Web service created when you use ASP.NET. However, there are other types of services and other types of clients. We'll briefly examine some of them.

Web Service Clients for .NET Remoting Services

One question commonly seen in newsgroups and e-mail lists is about how to decide when to use Web services rather than .NET Remoting to build a distributed application. Each mechanism has its benefits, but it is possible to tread a middle path . If a .NET Remoting server uses the HTTP protocol and XML serialization, you can use the Web reference mechanism in Visual Studio .NET (or wsdl.exe) to create a Web service proxy for it. With a little care in the specification of namespaces, you can easily substitute a service based on .NET Remoting for one based on ASP.NET without having to change the client code.

To create a client-side Web service proxy for a .NET Remoting server, you simply create a Web reference for it. The sample project RemotingProxyCakeClient uses a proxy for the ServerActivatedHTTPCakeSizeServer from Chapter 11. The .NET Remoting server listens on port 7000 and advertises its URL as CakeInfo.soap . If you start the server, you can add a Web reference for this service by providing the URL http://localhost:7000/CakeInfo.soap?wsdl , as shown in Figure 18-12.

Figure 18-12. You can create Web service proxies for SOAP-based .NET Remoting servers in the same way you would for an ASP.NET Web service.

The Web reference provides a proxy class called CakeInfoService in the localhost package. You can import and use this class just as you would any other Web service proxy:

 importlocalhost.CakeInfoService; CakeInfoServiceinfo=newCakeInfoService(); //Askabouta15",square,fruitcake... inteaters=info.FeedsHowMany(15,0,1); Console.WriteLine("Thiscakewillfeed " +eaters+ " people"); 
.NET Remoting Clients for Web Services

Looking at things from the other direction, you might want to employ .NET Remoting to access a Web service. The soapsuds.exe tool that's provided as part of the .NET Framework allows you to do this. Basically, you feed soapsuds the WSDL describing the service and it will output source code or an assembly that contains a .NET Remoting “based proxy class for the service. Note that the SOAP messages used with .NET Remoting pass RPC-style messages with SOAP encoding (see the sidebar titled "Document Literal vs. SOAP Encoding and RPC" in Chapter 17), so the service must expose SOAP operations that can be accessed using RPC-style method calls. By default, all Web services based on ASP.NET use document-style and literal encoding.

To make ASP.NET Web methods RPC-friendly, you can apply the attributes System.Web.Services.Protocols.SoapRpcServiceAttribute and System.Web.Services.Protocols.SoapRpcMethodAttribute . For example, consider a form of the Enquiry Web service that's intended for RPC-style interaction:

 /**@attributeWebServiceAttribute (Namespace="http://fourthcoffee.com/CakeCatalog/RpcEnquiry", Description="RPC-styleenquiryserviceforFourthCoffee'sCakeCatalog") */ /**@attributeSoapRpcServiceAttribute()*/ publicclassRpcEnquiryextendsSystem.Web.Services.WebService { /**@attributeWebMethodAttribute()*/ publicintFeedsHowMany(intdiameter,Stringshape, Stringfilling) { } } 

This RpcEnquiry class is provided as part of the CakeCatalogService sample project. You can use soapsuds.exe to generate an assembly containing a .NET Remoting “based proxy for this service, as shown here:

 Soapsuds-pn:com.fourthcoffee.cakecatalogurl:http://fourthcoffee.com/ CakeCatalogService/RpcEnquiry.asmx?wsdl-oa:SoapSudsCakeProxies.dll 

By default, any source code is generated in Visual C#. The command shown above uses the “ oa flag to instruct soapsuds to build an assembly called soapsudscakeproxies.dll rather than emit source code. The “ pn flag changes the namespace (package) in which the proxy class is defined, in this case com.fourthcoffee.cakecatalog . After you've built the assembly, you can reference it from your project using the Add Reference command on the Project menu. To see what has been generated, you use the Object Browser. (Choose Other Windows from the View menu and then choose Object Browser.) You'll see an entry for soapsudscakeproxies , below which you will find the package com.fourthcoffee.cakecatalog , which is the namespace (package) name passed to soapsuds using the “ pn flag. This package contains the class RpcEnquirySoap , which implements the FeedsHowMany method.

The RpcEnquirySoap class extends the System.Runtime.Remoting.Services.RemotingClientProxy class. This base class supplies useful helper functionality (cookies, authentication, and so on) that's similar to that provided by SoapHttpClientProtocol for document-oriented Web service proxies. Figure 18-13 shows the contents of the soapsudscakeproxies assembly. To use the proxy, import the com.fourthcoffee.cakecatalog package, instantiate the proxy, and call the FeedsHowMany method.

Figure 18-13. Soapsuds will create an assembly containing a .NET Remoting proxy for an RPC-style Web service.

Although this example uses a separate Web service to provide the RPC-style functionality, you can expose multiple sets of methods on the same service ”some for RPC clients and some for document clients. This technique is beyond the scope of this chapter but is explained in the .NET Framework documentation.

Non-.NET Clients

It is quite possible to create a non-.NET client for a Web service based on ASP.NET or .NET Remoting. This, after all, is a primary objective of Web services. Several vendors and open source projects are currently working on various Java-based Web service toolkits, such as Apache Axis, so you can build Web service clients and servers that target the Java 2 platform. Interoperability is quite good now between different Web service toolkits, and there are even sites on the Web dedicated to publishing the results of interoperability tests.

I l @ ve RuBoard


Microsoft Visual J# .NET (Core Reference)
Microsoft Visual J# .NET (Core Reference) (Pro-Developer)
ISBN: 0735615500
EAN: 2147483647
Year: 2002
Pages: 128

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