Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Visual CSharp .NET Programming (2002) [eng]

.pdf
Скачиваний:
58
Добавлен:
16.08.2013
Размер:
7.98 Mб
Скачать

Figure 2.12: The application invokes the web service in the code-behind module to reverse the string.

You can also try entering a palindrome to see whether it gets past the Palindrome function (Figure 2.13).

Figure 2.13: The palindrome filter seems to work.

With the application running in your browser, it's worth viewing the source that the browser is rendering to verify that it is straight HTML (and some client-side JavaScript). To view the source in Internet Explorer, select View ?Source.

Listing 2.3 shows the code-behind module for the application, complete except for the portions of code that are auto-generated-which is how I normally intend to present code in this book, after first showing the auto-generated portions.

Listing 2.3: Non-Auto-Generated Portions of the Code-Behind Module for ReverseString

...

namespace SybexC3

{

...

public class WebForm1 : System.Web.UI.Page

{

...

private void btnRev_Click(object sender, System.EventArgs e) { theService.SybexC2Service theService = new

theService.SybexC2Service();

string result = theService.ReverseString(txtInString.Text); if (Palindrome(result,txtInString.Text)) {

lblResult.Text = "Don't even think of giving me a palindrome!";

}

else {

lblResult.Text = result;

}

}

public bool Palindrome(string String1, string String2) { if (!(String1.ToUpper() == String2.ToUpper())) {

return false;

}

if (String1.Length == 1) { return false;

}

return true;

}

}

}

Consuming the IsPrime Web Method

Next on our agenda is the IsPrime web method, which, as you may recall, is part of the same web service as the previous example (SybexC2Service) that I showed you how to create in Chapter 1. Here, however, we're going to play a game that's a wee bit different.

Once you have the proxy built and added to the project, calling a web method is just like calling any other method, as you saw in the ReverseString example. In the same fashion, there should be no particular problem in calling the IsPrime web method synchronously, passing it a long integer to evaluate for primality (that is, its primeness-whether it's a prime number), and displaying an appropriate message depending on the result of the method call. I'll show you how to do this (it doesn't particularly differ from working with the ReverseString method). More interestingly, I'll show you how to call the IsPrime web method asynchronously, so that your program can do other things while waiting to learn whether the number passed to IsPrime is or isn't a prime.

Before we can get to the asynchronous call to the web method, there's a tangent we have to take (actually, one of several tangents). Well, it's what you learn along the way that counts, not the destination, right?

Given the efficient nature of the algorithm for determining primality used in the IsPrime web method (at most, it needs to iterate up to the square root of the number), it just doesn't take very long to determine whether a number is a prime, at least if the number is within the bounds of the long data type (-9,223,372,036,854,775,808 to 9,223,372,036,854,775,808). Of course, if the number input is out of bounds, it will throw an error. In order to actually witness any asynchronous behavior, we're going to add code to the IsPrime web method that will slow it down on demand.

Extending the Service Interface

Going back to the web service developed in Chapter 1, we're going to add a parameter to the IsPrime web service that delays the method's return by the designated number of seconds. Here's a function that can be added to the class module behind the SybexC2 web service that sends the thread being executed to sleep for the designated number of seconds:

static public void GoToSleep(int DelayInSec) { if (DelayInSec > 0) {

System.Threading.Thread.Sleep(DelayInSec * 1000);

}

}

Note The Thread.Sleep method takes an argument in milliseconds, so the multiplication by 1000 is needed to convert the delay into seconds. It's important to make sure that the method is not passed an argument of zero (0), which would suspend the thread indefinitely; avoiding this is achieved with the conditional check that DelayInSec > 0.

The web method needs to be changed to invoke the new function:

[WebMethod (BufferResponse = false, CacheDuration = 0)] public bool IsPrime(long NumToCheck, int DelayInSec) {

ServiceSupport.GoToSleep(DelayInSec); return ServiceSupport.IsPrime(NumToCheck);

}

Note that I've added attributes to the WebMethod directive to make sure that the IsPrime web method is neither buffering nor caching its response to a call.

Listing 2.4 shows the entire revised web method. If you go back to the SybexC2 project developed in Chapter 1 and run it with the revisions, you'll see that the test page for the web method generated by Visual Studio now includes input fields for the DelayInSec argument as well as NumToCheck (Figure 2.14).

Figure 2.14: The generated test page for the revised web service now shows two parameters. Note If you've already generated a proxy that references this method in a project, you should update it by selecting the web reference in Solution Explorer, right-clicking, and

selecting Update Web Reference from the context menu. If you used wsdl.exe to create the proxy module, you should delete the current module, run wsdl.exe again, and add the newly generated module.

Listing 2.4: The Extended IsPrime Web Method

// SybexC2.SybexC2Service using System.Web.Services;

namespace SybexC2 {

[WebService (Description="Chapter 1 demo program", Name="SybexC2Service", Namespace="http://www.bearhome.com/webservices")]

public class SybexC2Service : System.Web.Services.WebService

{

...

[WebMethod (BufferResponse = false, CacheDuration = 0)] public bool IsPrime(long NumToCheck, int DelayInSec) {

ServiceSupport.GoToSleep(DelayInSec); return ServiceSupport.IsPrime(NumToCheck);

}

...

}

}

// Class1.cs

...

namespace SybexC2 {

public class ServiceSupport {

...

static public bool IsPrime(long NumToCheck ) {

for (long i =2; i <= Math.Sqrt(NumToCheck); i++) { if (NumToCheck%i == 0 ) {

return false;

}

}

return true;

}

static public void GoToSleep(int DelayInSec) { if (DelayInSec > 0) {

System.Threading.Thread.Sleep(DelayInSec * 1000);

}

}

}

}

Synchronous Consumption

As we've already said, synchronous consumption is easy. Assuming you've added a web reference to the SybexC2Service, you can create a user interface consisting primarily of a TextBox (for the number to be checked), a Button (to do the checking when the users clicks), and a Label to display the results. Figure 2.15 shows a web forms interface in its designer along these lines, with the addition of a Button to perform asynchronous consumption and a second TextBox that will be used later on for the results of the asynchronous call.

Figure 2.15: It' s easy to create an interface that can be used to check synchronous and asynchronous consumption of the IsPrime web method.

Here's the click event procedure that calls the web method synchronously to check whether the number input is a prime, and displays the appropriate message based on the method return value:

private void btnCheck_Click(object sender, System.EventArgs e) { theService.SybexC2Service theService = new

theService.SybexC2Service();

if (theService.IsPrime(Convert.ToInt64(txtInNum.Text),0)) { txtStatus.Text = "Is a prime!";

}

else {

txtStatus.Text = "Not prime!";

}

}

Of course, since we want this call to return as soon as possible, the second parameter of the call to the IsPrime method is 0-meaning, no extra delay.

If you run the program, you can see that it puts a nice front end on the web service (Figure 2.16).

Figure 2.16: A synchronous call to the web method easily determines whether a number is a prime.

Asynchronous Consumption

If you looked at the code for the auto-generated proxy module back in Listing 2.2, you'll see that it created three methods for each web method that might be invoked. The general pattern is that for each NamedWebServiceMethod included in the web service, the proxy class includes three methods:

NamedWebServiceMethod

BeginNamedWebServiceMethod

EndNamedWebServiceMethod

The Named... method is used synchronously, whereas the BeginNamed... and EndNamed...

methods are intended for use with asynchronous calls.

In the proxy class for the SybexC2 service, in addition to IsPrime, you'll find a BeginIsPrime and an EndIsPrime.

Here's the click event procedure code that invokes BeginIsPrime asynchronously. The point of the example is to demonstrate something else happening while the web method call is waiting to return. This procedure writes periods (...) into the TextBox that displays the status message while it waits (what do you do to idle away the time while waiting for something to happen?).

private void btnASynchCheck_Click(object sender, System.EventArgs e) { theService.SybexC2Service theService = new theService.SybexC2Service(); IAsyncResult ar =

theService.BeginIsPrime(Convert.ToInt64(txtInNum.Text), 1, null, null);

// do something until call returns

txtStatus.Text = "Not blocked, don't have to wait...\r\n"; while (ar.IsCompleted == false) {

txtStatus.Text = txtStatus.Text + ".";

}

txtStatus.Text = txtStatus.Text + " I'm back! "; if (theService.EndIsPrime(ar)) {

txtStatus.Text = txtStatus.Text + " Is a prime!";

}

else {

txtStatus.Text = txtStatus.Text + " Not prime!";

}

}

If you run the project, enter a number to check, and click the ASynch Check button, you'll get a display like that shown in Figure 2.17 (although there may be a great many more periods when you run this in real life, particularly if you put in a larger delay).

Figure 2.17: Periods are added to the TextBox while the asynchronous web method call does its thing.

Now that you've seen the asynchronous call in action, let's go through the code a little more carefully.

The first line instantiates an instance of the web service class, named theService, based on the class defined in the proxy module in the normal fashion:

theService.SybexC2Service theService = new theService.SybexC2Service();

The next line,

IAsyncResult ar =

theService.BeginIsPrime(Convert.ToInt64(txtInNum.Text), 1, null, null);

stores state information for the asynchronous call in a variable named ar. The first two arguments of the call to BeginIsPrime are, of course, the parameters required by the IsPrime method-the number to be checked and the delay in seconds. The third and fourth arguments are each represented here by null, a keyword that means a null reference, one that does not refer to any object.

You'll see an example in Chapter 3 of an asynchronous call that uses these parameters. As a preview, the third parameter is for a delegate of type System.AsyncCallback. A delegate is one method standing in for another. The fourth parameter is for a callback method-the method that is called when the asynchronous call is complete.

Next, a line of text is displayed in the TextBox:

txtStatus.Text = "Not blocked, don't have to wait...\r\n";

Note The backslash indicates that \r and \n are escape characters-two-character tokens with special meanings. \r means carriage return, and \n means line feed (new line).

Finally, the IsComplete property of the IAsyncResult variable is polled using a while loop. As long as its value is false-meaning the asynchronous call has not completed-a period is added to the TextBox:

while (ar.IsCompleted == false) { txtStatus.Text = txtStatus.Text + ".";

}

In theory, of course, you could do all kinds of things in this loop, not just display periods on the screen.

When the loop completes (because the IsCompleted property of the IAsyncResult object has become true), the procedure goes on in a normal fashion:

txtStatus.Text = txtStatus.Text + " I'm back! "; if (theService.EndIsPrime(ar)) {

txtStatus.Text = txtStatus.Text + " Is a prime!";

}

else {

txtStatus.Text = txtStatus.Text + " Not prime!";

}

I think you'll find if you set this program up and run it that there is a fly in the ointment, or a serpent in paradise. (See, I told you there would be tangents!) The problem is the observable behavior of the program. While it is true that a great many periods get written to the screen before the results of the asynchronous call are delivered, what happens is that it seems like there is a fairly long pause, and then everything is written all at once-first the periods, then the asynchronous call completion. It would be much more satisfying to actually see something being done while the call is being processed. The problem here is the way ASP.NET processes web forms-it waits for the entire response to the HTTP request before displaying anything. We can get around this by 'rolling our own' HTML.

Doing It Without Web Forms

As you may remember from Chapter 1, a web service can be created by hand in a text editor if the file is saved with an .asmx file extension, the file includes appropriate directives, and the file is played in an Internet Information Services virtual path accessible via URL. (For details, see Listing 1.1 and related text.) The file is compiled into an ASP.NET web service the first time it is opened.

In a similar fashion, we can create an ASP.NET web application by hand, provided it contains appropriate directives and is saved with an .aspx file extension in an accessible virtual directory. The advantage of this is that we can use the Page.Response object to hand-code HTML that is sent directly back to the browser-bypassing the Visual Studio .NET web forms page-generation process.

Note that the page still has to reference a proxy to the web service. For convenience sake, I simply imported the proxy class created for the SybexC4 example in this chapter, hard-coded in a small value (23) to check for primality, named the file async.aspx, and placed it in the virtual directory created for this chapter's example.

Here's the way the file initially looked:

<%@ Page language="C#" %>

<%@ Import namespace="SybexC4.theService" %> <%Response.BufferOutput=false; Response.Write("<html><body>\r\n"); SybexC4.theService.SybexC2Service theService = new

SybexC4.theService.SybexC2Service();

IAsyncResult ar = theService.BeginIsPrime(23, 0, null, null); // do something until call returns

Response.Write("Not blocked, don't have to wait...<h3>"); while (ar.IsCompleted == false) {

Response.Write("<b>W</b><br>");

Response.Flush();

}

Response.Write("I can do stuff while I wait!"); Response.Write("<br></h3> I'm back! ");

if (theService.EndIsPrime(ar)) { Response.Write("<br> Is a prime!");

}

else {

Response.Write("<br> Not prime!");

}

Response.Flush();

Response.Write("</body></html>");

Response.Flush();

%>

The intention was to open it with the URL http://localhost/SybexC4/async.aspx and to have the page display the letter W down a page until the asynchronous call completed. To my dismay, this did not happen, and in fact the browser would not render a page at all-not even the initial "Not blocked, don't have to wait," let alone displaying Ws or returning from the web method. I eventually realized that the browser was hanging in infinite limbo because it was being overwhelmed by the sheer number of Ws generated, so I added a for loop that uses iteration and modulo arithmetic to greatly cut down the Ws and break out of the asynchronous while statement when the loop completes:

while (ar.IsCompleted == false) {

for (long i = 1; i <= 100000000; i++) { if (i%10000000 == 0 ) {

Response.Write("<b>W</b><br>");

Response.Flush();

}

}

break;

}

When I ran it with this addition, which you can see incorporated in Listing 2.5, I was able to watch ten Ws gently plop onto my screen and then observe the completion of the asynchronous call (Figure 2.18).

Listing 2.5: Displaying Asynchronous Behavior in a Hand-Coded ASP.NET Page

<%@ Page language="C#" %>

<%@ Import namespace="SybexC4.theService" %> <%Response.BufferOutput=false; Response.Write("<html><body>\r\n"); SybexC4.theService.SybexC2Service theService = new

SybexC4.theService.SybexC2Service();

IAsyncResult ar = theService.BeginIsPrime(23, 0, null, null); // do something until call returns

Response.Write("Not blocked, don't have to wait...<h3>"); while (ar.IsCompleted == false) {

for (long i = 1; i <= 100000000; i++) { if (i%10000000 == 0 ) {

Response.Write("<b>W</b><br>");

Response.Flush();

}

}

break;

}

Response.Write("I can do stuff while I wait!"); Response.Write("<br></h3> I'm back! ");

if (theService.EndIsPrime(ar)) { Response.Write("<br> Is a prime!");

}

else {

Response.Write("<br> Not prime!");

}

Response.Flush();

Response.Write("</body></html>");

Response.Flush();

%>