Advanced Topics

I l @ ve RuBoard

Yes, creating Web Services really is this simple. However, writing a "good" Web Service usually isn't. So far we've glossed over several important issues you're going to face when you try to implement your own Web Services.

First, there's the issue of complex types. How do we send custom classes of our own devising to clients that may reside on a different platform and be written in a wholly different language? Second, we all know the Internet is an unruly place, so what happens when delays interrupt our service in mid-transaction? Do the service and client application just stall until the connection is restored, or is there a better way to handle the situation? The answers to these questions are not overly complex, but they will add additional complexity to your application.

Also, if you're an old hand at Web application programming in the Windows environment, you're probably asking yourself, "How do I access all of the application support services that were provided under the Windows DNA model?" Specifically, you want to know how to use Microsoft Message Queue Server (MSMQ) and Microsoft Transaction Server (MTS) from within your Web Services. The fact is that both of these technologies are tightly integrated into the .NET architecture and programming model and are therefore available for you to use within your Web Services. The caveat, however, is that not all of these features are available as part of the SOAP protocol, so if you intend for your Web Services to interoperate with those hosted on non-Microsoft platforms, you will run into some limitations.

In this section we'll learn how to handle complex types and make our Web Service applications more resilient to the depredations that the Internet will inflict upon them. We'll also take a quick look at how and when MTS and MSMQ are used within Microsoft's implementation of the Web Services architecture. Near the end of the section, we'll take some time to gaze into our crystal ball and try to imagine how messaging and transaction handling functionality could evolve away from Microsoft's current proprietary MSMQ and MTS implementations to eventually become a part of the SOAP protocol itself.

Returning Complex Types

As mentioned previously, complex data types such as datasets and user -defined types (structs and classes) can be transmitted using SOAP. This feat is accomplished through a process known as XML serialization. XML serialization in turn depends upon another process known as reflection. Reflection represents the .NET runtime's ability to examine and expose the internal structure of any .NET class. XML serialization takes this structural information, combines it with the actual data associated with an object of the reflected class, and transforms the result into an XML message suitable for transmission via the SOAP protocol. The inverse of this process allows the original object to be recreated by the SOAP message recipient.

To demonstrate , let's modify our testService application to return a complex data type. First, modify the testService.asmx file to resemble Listing 11.3.

Listing 11.3 A Web Service that returns a complex type ( 11ASP03.cs.asmx ).
 <%@ WebService Language="C#" Class="testService" %> using System; using System.Web.Services; public class Greeting {     public String name;     public String salutation;     public String honorific; } public class testService {     [WebMethod]     public Greeting HelloWorld(string name) {         Greeting myGreeting = new Greeting();         myGreeting.name = name;         myGreeting.salutation = "Why, hello";         myGreeting.honorific = "Dr.";         return myGreeting;     } } 

What we've done is added a simple class called Greeting with three public member variables to our Web Service. Then, we modified the original testService class to create a new instance of the Greeting class, populate its member variables, and return the Greeting object as a result whenever the HelloWorld method is invoked.

Next , point your browser to http://localhost/testService/testService.asmx and invoke the HelloWorld method using your name as the parameter. The result that you see on your screen should look similar to the following and is the XML serialization of the Greeting class's structure and data.

  <?xml version="1.0" encoding="utf-8" ?> <Greeting xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.  org/2001/XMLSchema" xmlns="http://tempuri.org/">     <name>William</name>     <salutation>Why, hello</salutation>     <honorific>Dr.</honorific> </Greeting> 

Again, it's important to note that SOAP is an open standard officially submitted to the W3C. Just as a .NET client application can easily deserialize this XML representation back into a native .NET object, a SOAP-enabled Java or CORBA runtime could just as easily deserialize it into a Java or CORBA object.

NOTE

It's technically incorrect to say that SOAP can serialize and deserialize an object instance. When we use the word object or class, we automatically imply behavior ( methods ) as well as data (attributes). As a result, we should be careful to observe that what SOAP is really doing is transmitting the data associated with an object instance and not the object instance itself. By way of comparison, serialization in the Java world is capable of transmitting both an object's data and behavior across a network. This functionality can be simulated, however, by casting a SOAP-encoded complex type to a local class type that implements the relevant behavior and data representation capabilities.


Let's try one more example of returning a complex data type. This time, we'll return a dataset object to the client using the SOAP protocol. You might not have SQL Server installed, so we'll just reconstitute a serialized dataset to get things going. When our dataset is reconstituted, it will act no differently than if a production SQL Server system had provided the information. The dataset, serialized as XML and presented in Listing 11.4, can either be typed in exactly as given or, better yet, downloaded from our Web site. In either case, you should name the file authors.xml and place it in the c:\inetpub\ wwwroot \testService directory along with our other Web Service files.

Listing 11.4 Sample XML dataset( 11ASP04.xml ).
 <?xml version="1.0" standalone="yes"?> <NewDataSet>     <authors>         <au_lname>Arnaud</au_lname>         <au_fname>Clay</au_fname>         <au_id>22187</au_id>     </authors>     <authors>         <au_lname>Bulla</au_lname>         <au_fname>Brent</au_fname>         <au_id>22314</au_id>     </authors>     <authors>         <au_lname>Caudron</au_lname>         <au_fname>Tom</au_fname>         <au_id>77612</au_id>     </authors> </NewDataSet> 

Next, we will create a new Web Service called testDSService.asmx that will return to the caller a dataset representing the contents of the authors.xml file. Again, you'll need to type the code in Listing 11.5 into Notepad and save the file as testDSService.asmx and place it in the c:\inetpub\wwwroot\testService directory along with our other Web Service files.

Listing 11.5 A Web Service that returns a dataset ( 11ASP05.cs.asmx ).
 <%@ WebService Language="C#" Class="testDSService" %> using System; using System.Web.Services; using System.Data; using System.Xml; public class testDSService {     [WebMethod]     public DataSet getDataSet() {         DataSet ds = new DataSet();         ds.ReadXml(@"c:\ inetpub\ wwwroot\ testService\ authors.xml");         return ds;     } } 

The testDSService class is fairly straightforward. If you'd like to test it before moving on, just point your browser to http://localhost/testService/testDSService.asmx and click the Invoke button. What you'll see is the serialized XML representation of the dataset returned to your browsera neat trick but not all that useful in this format.

The real power of .NET's ability to serialize complex data types using XML and SOAP becomes apparent when our Web Service is consumed by a client application. Our next step, therefore, is to build such a client. To accomplish this, we will follow the pattern we outlined previously:

  • Create the Web Service proxy using the Wsdl.exe utility.

  • Create the Web Service client application using Notepad.

  • Compile the client code and Web Service proxy classes using the C# compiler.

To create the proxy class that our client will use to talk to our new Web Service, go into the C:\testServiceClient directory and issue the following command:

  Wsdl.exe /language:CS /out:testDSServiceProxy.cs http://localhost/ testService/  testDSService.asmx?WSDL 

Next, launch Notepad and type in the code in Listing 11.6. Once you're done save the file as testDSServiceClient.cs in the C:\testServiceClient directory.

Listing 11.6 Sample Web Service client that retrieves a dataset ( 11ASP06.cs ).
 using System; using System.Data; class testDSServiceClass {     static void Main() {         testDSService myTest = new testDSService();         DataSet response = myTest.getDataSet();         Console.WriteLine("DataSet retrieved...here comes the data!");         foreach(DataRow row in response.Tables["authors"].Rows)             Console.WriteLine(row["au_lname"].ToString());     } } 

Lastly, issue the following command to compile the service proxy and client classes and then run the resulting executable.

  csc /r:System.Web.dll,System.Web.Services.dll,System.XML.dll,System.dll, System.Data.dll  testDSServiceClient.cs testDSServiceProxy.cs 

The results should look similar to Figure 11.7.

Figure 11.7. Sample output from testDSServiceClient.

When execution reaches line 10, we retrieve the DataSet object from our Web Service and then proceed to iterate through each row, printing the last name of each author in the table as we go along. Pretty simple, isn't it? In fact, this example is so simple that you might be in danger of failing to appreciate what life would be like without the .NET framework and Web Services architecture to lend a helping hand. Even in a relatively simple homogeneous environment composed of similarly versioned Microsoft products, it can still be an onerous and time-consuming process to marshal and unmarshal data across a remote connection and then reconstitute this data in a type-safe way on the client side. Now imagine just how difficult this would be to do in a completely generic cross-platform manner across a variety of HTTP protocols. Now imagine doing so in a manner almost completely seamless to the client application developer. The mind reels! And all of this effort would have nothing to do with the actual business logic you were attempting to implement on top of all of this plumbing code. Furthermore, chances are good that you would not have the time to make an equivalent system totally generic across all object types and would be forced to reinvent this code (perhaps using design patterns) for every project that required similar services!

Asynchronous Processing

Although the examples we have looked at so far have helped to demonstrate the key concepts associated with programming Web Services, these same applications would never stand up to the punishment that can and will be inflicted on them by the Internet. Any real-world scenario involving deployment across the Internet has to account for long delays in message delivery as well as outright delivery failure. Moreover, because you'll probably be using third-party services that you have no direct control over, your own applications have to be resilient even when services they depend upon are temporarily unavailable.

One basic problem inherent to all of our examples thus far is that they have been programmed to process SOAP responses synchronously. What this means is that when your application invokes a method exposed by some Web Service, it will stop in its tracks and won't resume execution until it receives a response. This is called blocking and is a bad thing when it comes to making your Web Services scalable, responsive , and resistant to failure. This might not seem like much of a problem when running Web Services on a development workstation or local area network, but it will quickly become a huge problem when you try to deploy your application on the Internet.

Fortunately, most of the work required to support the asynchronous processing of SOAP responses has already been done for us and is just sitting there in our service proxy code waiting to be used by our client! Take another look at the TestDSServiceProxy.cs file and take particular note of the following two methods:

  public System.IAsyncResult BegingetDataSet(System.AsyncCallback callback, object  asyncState) {     return this.BeginInvoke("getDataSet", new object[0], callback, asyncState); } public System.Data.DataSet EndgetDataSet(System.IAsyncResult asyncResult) {     object[] results = this.EndInvoke(asyncResult);     return ((System.Data.DataSet)(results[0])); } 

All we have to do to enable our client to process SOAP responses asynchronously is to modify our code to use the methods provided by the proxy for this purpose. Consider the code in Listing 11.7.

Listing 11.7 Sample asynchronous Web Service client ( s ).
 using System; using System.Data; class testDSServiceClass {     static IAsyncResult asyncResult;     static void Main() {     testDSService myTest = new testDSService();         // Begin asynchronous call to retrieve authors list dataset...         asyncResult = myTest.BegingetDataSet(null, null);         // Poll for completion.  This effectively blocks but proper threading         // would allow this operation to be carried out asynchronously...         while(!asyncResult.IsCompleted);         // Get the results!         DataSet response = myTest.EndgetDataSet(asyncResult);         Console.WriteLine("DataSet retrieved...here comes the data!");         foreach(DataRow row in response.Tables["authors"].Rows)         Console.WriteLine(row["au_fname"].ToString());    } } 

When we execute the BegingetDataSet(null, null) method exposed by our Web Service proxy, we are passed back an instance of IAsyncResult . This object communicates with the Web Service on our behalf in a non-blocking fashion. Next, we enter into a conditional loop that polls our IAsyncResult object repeatedly until it indicates that it has received a response from the Web Service. Although this is tantamount to blocking within the context of our current application, we could also have continued with some other processing and polled for completion periodically, perhaps within a separate thread of execution.

When the IAsyncResult object indicates that a response to our request has been received, we execute the EndgetDataSet(asyncResult) method and then proceed to process the resulting DataSet object.

Transaction Support

Failure happens. When someone kicks the plug out of the wall, a system board component fails, or sunspot activity twiddles a crucial bit in your L2 cache, even the most crashproof of systems will fail. To make matters worse , if a crash happens in the middle of processing an important batch of instructions, your application could be left in an inconsistent state. Using transaction services such as those provided by MTS, however, can minimize the risk of catastrophic (or even just annoying) data loss in mission-critical systems.

Without going into great detail, transactional systems recover gracefully from failure by monitoring all operations that occur within the boundaries of a specified transaction. If all operations are successful, all resources involved in the transaction are told to commit modified data to persistent storage. If even one operation is unsuccessful , however, all resources involved are instructed to roll back any modifications to data that were made and act as if the entire transaction had never happened . Although such a policy might seem like overkill, the reality is that it's almost always better to do nothing than to do something wrong. The ubiquitous check cashing example is often called upon to reinforce this notion. Imagine that your bank account deposit routine crashes in the middle of processing that $1,000.00 check you just sent to your mortgage company. After the smoke clears, it turns out that the debit to your account has occurred, but the corresponding credit was never made to your lender. Ouch! No wonder this example is always used to emphasize why an all-or-nothing approach to transaction handling is a good idea!

Although a full description of MTS and its programming model is beyond the scope of this chapter, it is relevant to note that at a basic level all you need to do to transaction-enable a particular Web Service method is to modify the WebMethod attribute to look like the following:

 [WebMethod(Transaction=Transaction.Required)] 

There are a number of additional options other than just the Transaction.Required parameter that you can use to control your methods transactional behavior. Although enabling transactions within your own Web Services is easy enough, using transactions in an effective and efficient way is a complex subject and designing good transaction support into large, complex systems is still a tough job. What Microsoft has done for us, however, is bundle the basic transactional services we need with the OS and expose this functionality in a very intuitive way. The rest, as always, is up to you.

I l @ ve RuBoard


Asp. Net. By Example
ASP.NET by Example
ISBN: 0789725622
EAN: 2147483647
Year: 2001
Pages: 154

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