Скачиваний:
64
Добавлен:
15.03.2015
Размер:
4.31 Mб
Скачать

366

Day 16

After today’s lesson, you should be well equipped to create your own Web Service applications. The remainder of this book will focus on deployment and configuration issues in Web applications and Web Services.

Today you will learn how to

Use ASP.NET session state and application state in your Web Services

Work around problems associated with ASP.NET session cookies

Use asynchronous calling techniques for Web Service clients

Create Web application client programs that use asynchronous calling

Managing State in Web Services

This section will explain how to use ASP.NET session and application state with Web Services. There’s no quicker way to start a debate among .NET developers than to ask the question, “How do I manage per-user data in my Web Service using sessions?” Opinions vary on the use of session state in Web Services. Some regard the combination of session state and Web Services to be a terrible approach because some Web Services—maybe even your own—are designed so that thousands of users can call the service’s methods at the same time. A Web Service that makes airline reservations might fit this description. If a Web Service like this uses session state, thousands of Session objects will be created at the same time. The creation of all these Session objects could quickly consume all of a Web server’s resources. Web Services like these probably need to manage per-user data differently. Day 21, “Creating Your Application Architecture,” will detail Web application and Web Service architectural issues. Until then, let’s postpone the debate on ASP.NET sessions and Web Services. For now, we will show you how to use session state without making any pronouncements regarding whether you should use it.

Using Session State

In the lessons covering Web applications, you saw examples that use the Session object to store data for Web forms. For a quick review, remember that you can store data associated with each specific user of your Web site in the Session object, with statements such as the following:

Session[“User Name”] = “Joe Smith”;

and

String userName = (String) Session[“User Name”];

The Application object can be used similarly. The lesson for Day 3, “Using Web Forms,” contained an example that used the Application object to store objects for a Web application.

Putting It All Together with Web Services

367

You can also use the Session and Application objects when developing ASP.NET Web Services. By using the Session object, you can keep track of information about clients that access your Web Service. Listing 16.1 shows a sample Web Service that keeps track of the amount of time spent on the server in a method called DoWork.

LISTING 16.1 Timing.asmx.cs: Keeping Track of the Time Spent in the DoWork Method

1: using System;

16

 

2: using System.Threading;

 

3: using System.Web;

 

4: using System.Web.Services;

 

5:

 

 

6: namespace WebBook

 

7: {

 

 

8:

public class Timing : System.Web.Services.WebService

 

9:

{

 

10:

private void AddTime(DateTime start, DateTime end)

 

11:

{

 

12:

Object oTime = Session[“TotalTime”];

 

13:

TimeSpan ts;

 

14:

if(oTime == null)

 

15:

{

 

16:

ts = new TimeSpan(0, 0, 0, 0, 0);

 

17:

}

 

18:

else

 

19:

{

 

20:

ts = (TimeSpan) oTime;

 

21:

}

 

22:

ts += end - start;

 

23:

Session[“TotalTime”] = ts;

 

24:

}

 

25:

 

 

26:[WebMethod(EnableSession = true)]

27:public void DoSomeWork()

28:{

29:DateTime startTime = System.DateTime.Now;

30:// do some work

31:Thread.Sleep(1000);

32:

33:DateTime endTime = System.DateTime.Now;

34:AddTime(startTime, endTime);

35:}

36:

37:[WebMethod(EnableSession = true)]

38:public String GetTimeSpent()

39:{

40:if(Session[“TotalTime”] != null)

41:{

42:return ((System.TimeSpan) Session[“TotalTime”]).ToString();

43:}

44: return new System.TimeSpan().ToString();
45: } 46: } 47: }
ANALYSIS The AddTime utility method (Lines 10–24) adds time to a total stored in the Session object. The method takes two DateTime objects representing the start
and end times of a method call and calculates the difference between them by using the TimeSpan object (Line 22). It adds the difference in time to the total time and stores the updated value in Session (Line 23). Lines 14–21 contain logic to make sure that the total time count exists in Session.
The DoSomeWork method (Lines 27–35) calls the Thread.Sleep method (Line 31) rather than performs any real work. It takes a snapshot of the current time on the server at the beginning and end of the method (Lines 29 and 33) and uses the AddTime method to update the current user’s time count.
The GetTimeSpent method (Lines 38–47) returns a string containing the latest total time figure for the DoSomeWork method. Of course, this Web Service doesn’t accurately reflect the amount of CPU time that the server spent executing the code. It reports only the amount of elapsed time that the DoSomeWork method took to execute.
Using the Timing Web Service works normally if it’s accessed from a Web form client. We won’t show a Web form client here. You could use the following few lines of code to exercise the Web Service within your own Web form:
Timing timingServ = new Timing(); timingServ.DoSomeWork(); timingServ.DoSomeWork(); timingServ.DoSomeWork(); Response.Write(timingServ.GetTimeSpent());
The result from this code might look like the following line:
00:00:03.0043200
This output shows that the Web server spent just over 3 seconds to execute the three DoSomeWork calls, as expected.
Let’s test the Timing service by using a console application for a client. Listing 16.2 shows just such an application.
Continued
LISTING 16.1
Day 16

368

Putting It All Together with Web Services

369

LISTING 16.2 CallTiming.cs: Calling the Timing Web Service, with Unexpected Results

using System; using System.Net;

using WebBook.localhost1;

namespace WebBook

 

{

 

public class CallService

16

{

public static void Main()

{

Timing timingServ = new Timing(); timingServ.DoSomeWork(); timingServ.DoSomeWork(); timingServ.DoSomeWork();

Console.WriteLine(“Time spent using web service: “ + timingServ.GetTimeSpent());

}

Console.ReadLine();

}

}

The output from Listing 16.2 looks like Figure 16.1.

FIGURE 16.1

Calling a Web Service using a console application.

Clearly, there is a problem here; the application should report a time of about 3 seconds.

To understand the problem, let’s revisit how sessions work. ASP.NET normally uses browser cookies to keep track of sessions. When a new Web browser accesses a Web site or Web Service, ASP.NET generates a new empty Session object and new cookie for this browser. The cookie contains an ID number for the newly generated session. On each subsequent page hit from a browser, ASP.NET uses the ID number in the cookie to look up the user’s session data, which is normally stored in memory in the Web server. ASP.NET takes the ID, looks up the session in memory, and then places that session into the Session object so that the Web Service (or Web form) can use it.

ANALYSIS
NEW TERM

370

Day 16

The problem with the Timing Web Service is that not all client programs support cookies. In fact, most clients, such as a Windows Forms application or console application, don’t support cookies.

The solution to the problem involves a little trickery in two steps. First, stop ASP.NET from using cookies by changing one line of code in the web.config file.

Note

Day 3, “Using Web Forms,” gave some introductory information on the web.config file, and Day 18, “Configuring Internet Applications,” will explain all the options you can set in the file. As a quick review, you use the web.config file to change compilation settings, session state settings, debugging settings, and many more options. The web.config file should reside in the same directory as your Web Service.

You can use a web.config file such as the one in Listing 16.3 to stop ASP.NET from using cookies.

LISTING 16.3 A web.config File That Turns Off Cookies for Session Management

1:<configuration>

2:<system.web>

3:<compilation defaultLanguage=”C#” debug=”true”/>

4:<session cookieless=”true” />

5:</system.web>

6:</configuration>

Line 3 makes sure that debugging is enabled, and Line 4 turns off cookies.

Now that cookies are disabled, ASP.NET will use a technique called URL munging to keep track of sessions. URL munging works by taking the ID for a session

that’s normally stored in a cookie and appending it to the URL for the Web Service or Web form. This means that on the first method call to the Web Service, the client will be redirected to a new “munged” URL, such as the following:

http://localhost/WebBook/(ylotjcyo4hhe00nkqwmr1r55)/Timing.asmx

Unfortunately, this new URL will cause an exception in the console application client when it invokes the Web proxy. The standard Web proxy doesn’t work when the Web Service redirects it to a new URL.

At this point, the second part of the solution is necessary. You have to make a small modification to the client program so that it will go to the new URL after the first call to the Web Service. Listing 16.4 shows one way to implement the solution.

Putting It All Together with Web Services

371

LISTING 16.4 CallTiming2.cs: Handling Redirection to a Munged URL

1:using System;

2:using System.Net;

3:using WebBook.localhost1;

5:namespace WebBook

6:{

7:

public class CallService

16

8:

{

9:public static void Main()

10:{

11:Timing timingServ = new Timing();

12://Make an attempt to call a method. This will result in

13://an exception, because the web server redirects us to a

14://”munged” url

15:try

16:{

17:timingServ.DoSomeWork();

18:}

19:catch (WebException e)

20:{

21://Handle the exception. The message of the exception will

22://contain a reference to the new URL

23:String err = e.Message;

24:

25://Create a search string to find the new URL

26:String search = “Object moved to <a href’”;

27:int newUrlPos = err.IndexOf(search);

28:

29:if(newUrlPos > 0)

30:{

31://We found the magic string, so redirect ourselves

32:String rightString = err.Substring(newUrlPos + search.Length);

34://Split the string using the ‘ character. The second element

35://will contain the new URL - the first and third elements

36://contain the rest of the error message

37:String[] segmentedString = x.Split(‘\’’);

38:

39: String newUrl = segmentedString[1];

40:

41://Redirect ourselves by changing the URL in the

42://web server proxy

43:timingServ.Url = “http://localhost” + newUrl;

44:}

45:else

46:{

47://this must be a different error

48:throw e;

49:}

50:}

51:

ANALYSIS

372

Day 16

LISTING 16.4 Continued

52:timingServ.DoSomeWork();

53:timingServ.DoSomeWork();

54:timingServ.DoSomeWork();

55:String timeSpent = timingServ.GetTimeSpent();

56:Console.WriteLine(“Time spent using web service: “ + timeSpent);

57:}

58:}

59:}

The new code to redirect the Web proxy to the munged URL is contained in the try...catch block in Lines 15–50. The catch block in Lines 19–50 searches for

a “magic” string in the exception’s error message (Line 26–27). If the search string is found, the code parses the new URL (Lines 32–39) and then replaces the URL for the Web Service proxy (Line 43).

Using Application State

ASP.NET application state doesn’t have the complications that surround session state for Web Services because application state is independent of specific users. Application state doesn’t need to be tracked using cookies. Not needing to use cookies simplifies your Web Service and Web Service client code significantly.

For instance, you could modify Listing 16.4 slightly to record statistics about a Web Service. Rather than record information user by user about the call time for the DoSomeWork method, the Web Service could record times for every user. You then could modify the GetTimeSpent method to return the average time spent in the DoSomeWork method for all the service’s clients.

Listing 16.5 shows a sample Web Service that records average call times for a method.

LISTING 16.5 AverageTiming.asmx.cs: The Code Behind for a Web Service That Records Average Call Time for a Method

1:using System;

2:using System.Threading;

3:using System.Web;

4:using System.Web.Services;

6:namespace WebBook

7:{

8:public class AverageTiming : WebService

9:{

10:private void AddTime(DateTime start, DateTime end)

11:{

ANALYSIS

Putting It All Together with Web Services

373

LISTING 16.5

Continued

 

12:

int calls = 0;

 

13:

Object oTime = Application[“TotalTime”];

 

14:

TimeSpan ts;

 

15:

if(oTime == null)

 

16:

{

 

17:

ts = new TimeSpan(0, 0, 0, 0, 0);

 

18:

}

16

19:

else

20:{

21:ts = (TimeSpan) oTime;

22:calls = (int) Application[“Calls”];

23:}

24:ts += end - start;

25:Application[“TotalTime”] = ts;

26:Application[“Calls”] = calls + 1;

27:}

28:

29:[WebMethod]

30:public void DoSomeWork()

31:{

32:DateTime startTime = System.DateTime.Now;

33:// do some work

34:Thread.Sleep(1000);

35:

36:DateTime endTime = System.DateTime.Now;

37:AddTime(startTime, endTime);

38:}

39:

40:[WebMethod]

41:public String GetAverageTimeSpent()

42:{

43:TimeSpan ts = (TimeSpan) Application[“TotalTime”];

44:int numCalls = (int) Application[“Calls”];

45:double avgTime = (ts.Seconds * 1000 + ts.Milliseconds)/numCalls;

46:return avgTime.ToString();

47:}

48:}

49:}

The AddTime method (Lines 10–27) has been changed to use the Application object. Also, the AddTime method records the total number of calls and stores this

figure in the Application object (Lines 22 and 26). The AddTime method also records the total amount of time spent inside the calling method (Lines 24–25).

The DoSomeWork method (Lines 30–38) is unchanged from Listing 16.4. The GetAverageTimeSpent method (Lines 41–47) finds the average call time so far in milliseconds and returns the value.