Visual C#. NET 2003 Unleashed
|
Not every web service returns results instantly, and those results often comprise extremely large amounts of data. Due to the latency of construction of SOAP envelopes and message overhead, web services lend themselves more toward a messaging infrastructure rather than an RPC (Remote Procedure Call) infrastructure. Because of that, the most efficient way to use web services is to make fewer calls with more data per call, rather than making frequent, small-data calls. As a result, some method calls to web services can take a significant amount of time to execute. For example, you might be using a web service to perform some kind of search through a vast amount of records. The search itself could take a significant amount of time, as could the delivery of the results to the client depending on the number of rows in the result set. Now think about how upset the users of your application would be if your application were completely unresponsive for the entire time it took to locate and return all of those rows. Fortunately there is an answer. If you make a request of the web service in a background thread while the main UI thread is still active and listening for input messages from the user, it will be able to respond and the user experience will not be harmed by long-running web service requests. Multithreaded Service Consumption Sample
The following code will show you how to use a background thread to invoke a web service and obtain results. After the results have been obtained, the Invoke method is used to forward the request to display the results to the main thread. There is a detail that you have to be aware of when doing multithreaded WinForms applications. When you are doing something in a background thread, any attempt to affect the UI at all will fail. In other words, you cannot do things such as open new forms or change properties of controls. You won't throw an exception, but it will appear as though nothing happened. For your background thread to affect the user interface, you have to use Invoke to cause a method to be executed on the main (GUI) thread. Listing 18.3 shows the code that invokes a method on the web service that waits for 30 seconds to simulate a CPU-intensive process. This code uses asynchronous multithreading to accomplish this without halting the foreground UI and it displays a dialog that tells the user that an action is taking place. Listing 18.3. The Asynchronous Execution of the SlowHello Web Service Method
private void btnSlowHello_Click(object sender, System.EventArgs e) { HelloService.Hello helloProxy = new HelloService.Hello(); progress.Show(); helloProxy.BeginSlowHelloWorld( new AsyncCallback(FinishSlowHelloWorld), null ); } private void FinishSlowHelloWorld( IAsyncResult ar ) { HelloService.Hello helloProxy = new HelloService.Hello(); try { string helloWorld = helloProxy.EndHelloWorld( ar ); this.Invoke( new DialogMethod( DisplayHelloWorldResult ), new object[] { helloWorld } ); } catch (Exception ex) { this.Invoke( new DialogMethod( DisplayError ), new object[] { ex.ToString() } ); } } private void DisplayHelloWorldResult( string message ) { progress.Close(); MessageBox.Show(this, message, "SlowHelloWorld"); } private void DisplayError( string message ) { if (progress.Visible) progress.Close(); MessageBox.Show(this, message, "SlowHelloWorld Failure"); }
The asynchronous execution of the web service method centers around the fact that when the proxy was created for us by Visual Studio .NET, it created two methods: BeginSlowHelloWorld and EndSlowHelloWorld. The begin method takes as an argument a delegate to a callback function that will be called when the execution of the web service method has completed. When this delegate is called, it can then call EndSlowHelloWorld to obtain the actual results of the method. From there, the Invoke method is used on the Windows Form to push the execution of the DisplayHelloWorldResult method onto the main UI thread to allow the MessageBox to show up. Although the supposedly arduous background task is taking place, the application can leave open a dialog indicating that there is work taking place. If you want to be more subtle, you can simply display a message in a status bar or even change an icon to animate, indicating that web service communication is taking place. Web Service Client Reliability
Making an application that consumes a web service perform in such a way that provides a rich user experience involves many things. One of those things, as you've seen, is the ability to deal with making requests of the web service in the background so as not to inconvenience a user or interrupt other work she might be performing at the same time. Another thing that must be done for rich web service consumption is to provide some sense of reliability to your application. If your application hangs every time there are network problems or the web service throws an exception, your users won't find your application very compelling. They might also think your application is too limited to use if the application doesn't queue up work when it is offline. A lot of applications these days are being required to work on laptops, tablets, PDAs, and so forth. These environments are often referred to as occasionally connected or loosely tethered environments. Such environments sometimes have network access and sometimes they don't. Your application will need to be able to work offline in order to provide a rich experience in an occasionally connected environment. Testing for Network Connection
If your application is to be able to work offline or handle unexpected circumstances, it will need to be able to determine whether it is indeed offline. Your application should be able to easily determine whether the web service is available. There are a couple of approaches you can take here. One approach would be to use all kinds of highly complex code to detect whether a current network connection is available and whether that connection is available to the Internet, and so on. When you start to think about it, the amount of information that your code would have to sift through and keep track of to detect network connectivity at a hardware and driver level would be ridiculous. A simpler method, typically called the Ping, requires very little code and allows for maximum flexibility because it has no dependency on how the user is connecting to your web service; it could be through a firewall, VPN, dial-up, or home DSL. To ping a web service, set a very short timeout period on the proxy client class and then invoke a method on the web service that you know takes a very short amount of time to complete. If the method does not return in a timely fashion or an error is encountered, you can safely assume that your application has a bad network connection and you should be operating in offline mode. Here's a sample ping method: private bool Ping() { HelloService.Hello helloProxy = new HelloService.Hello(); try { helloProxy.Timeout = 10000; // 10k milliseconds or 10 seconds. return helloProxy.Ping(); } catch (Exception ex) { // an error occurred, do something with information } return false; } Using this type of method to detect network connection is probably the safest bet. Any attempt to use hardware or driver-level code to detect network connection will give you only the information that the user is on a network, not necessarily the right network. In addition, that method doesn't specifically test the connectivity between your application and the web service. Handling Web Service Errors
Errors that occur during web service calls are handled in much the same way as standard errors. If an exception occurs during server-side execution, that exception will be serialized and then stored in the SOAP header as a SOAP fault. That SOAP fault will then be deserialized by the .NET Framework web services infrastructure and turned again into managed .NET exceptions. This mechanism allows it to appear as though when an exception is thrown on a web service server, it is caught by the client application. The issue is that the serialization is just that: pure serialization. If you are throwing custom exceptions that have custom behavior, you cannot expect that behavior to carry over to the client that ends up catching the exception. For this reason, if your web service does throw exceptions of any kind, it should do so in a well-published, consistent, well-documented manner so that the client can tell what went wrong and why and be able to give the end user some important information about the source of the error. Supporting Offline Actions
Offline access is something that you have to decide on; there isn't one piece of code that will enable your application to work in offline mode. What your application requires to work while untethered is dictated by the design and architecture of your application. However, there are some things that you can do that will certainly help. The first is that you could possibly use the network detection ping code to determine whether your application is offline. If the application is not able to connect to the web service, you will need some method of storing the user actions that would ordinarily have been web service calls. You could use isolated storage (as shown earlier) to store an XML document or a DataSet that contains a history of user actions committed while offline. When the application detects (or is told) that it is connected again, you can then take the queued list of actions (essentially deserializing it) and push those actions out to the web service. This functionality enables personnel such as remote salespeople to do business offline. As soon as they are connected, the business they did offline can synch up, much like your PDA synchronizes with your desktop applications. |
|