Managing State in Web Services Using session objects is always a good option for maintaining per user data. But the case of web services is different. In real life, Web services may be called thousands of times a day (for example, a web service maintained by Railway administration for seat reservation). In such cases, keeping per user data in the webserver would be a bad approach. If you have heard the term PARSSeM, there would be violations of different parameters then. We would need to adopt some other approach.
In this article, I would focus on how session data can be maintained by web services . Besides, I would describe using Application object in web services. Using Application state object is always an easy and less resource consuming option because it does not deal on per user basis.
How can you use Session State Object? In web applications, you would use session object like: Session["Userid"] = input_id; And for retrieving, String user_id = (String) Session["Userid"] Let say, we want to check how much time our business logic code in seat reservation web service took while fulfilling requests. The code for time spent calculation would be included in the same web service. Listing 1.1 defines this service: using System; 2) using System.Web; 3) using System.Web.Services; 4) 5) namespace WebBook 6) { 7) public class Reservation : System.Web.Services.WebService 8) { 9) 10) [ WebMethod (EnableSession = true) ] 11) public void ReserveSeat () 12) { 13) DateTime init_value = System.DateTime.Now; 14) 15) // business logic for seat reservation 16) DateTime final_value = System.DateTime.Now; 17) 18) // using Application State object. Would
explain it later 19) int total_requests = 0; 20) total_requests = (int) Application["requests"]; 21) total_requests = total_requests +1; 22) Application ["requests"] = total_requests; 23) 24) // Updating Total time spent in session 25) TimeSpan timespan1; 26) Object Session_time = Session["Time"]; 27) 28) if (Session_Time != null) 29) { 30) timespan1 = (TimeSpan) Session_time; 31) } 32) else 33) { 34) timespan1 = new TimeSpan (0,0,0,0,0); 35) } 36) 37) timespan1 += final_value - init_value; 38) 39) Session["Time"] = timespan1; 40) 41) [ WebMethod (EnableSession = true) ] 42) public String TotalTime() 43) { 44) 45) if (Session["Time"]==null) 46) return new System.TimeSpan().ToString(); 47) else 48) return ((System.TimeSpan) Session["Time"]).ToString(); 49) } 50) 51) } Analysis of Listing 1.1 The method ReserveSeat() do two things: Implements the business logic for reserving a seat and calculates the time spent while reserving seat(s). Before executing the business logic, it notes the current system data and time and at the end of execution, it again records the same. So, we can now calculate the time spent during execution of seat reservation code. Since we were supposed to calculate all the time spent till now by different users, we would take the previous Time value in the session variable and add it with newly calculated value. The other function TotalTime() returns the time spent so far. The problem: Web services can be called from web forms as well as console applications. But manipulating session object in the same way as we do in other web applications can create problems here.
If you call the Seat Reservation web service from a web form, you would receive accurate results as long as cookies are enabled in your browser. If the cookies are disabled, you would not see correct results. I would explain shortly why it happens. If you call this service from a console application, again the same problem of incorrect results would appear. You write a client console application like this: Listing 1.2 1) using System; 2) using System.Net; 3) using WebBook.localhost(); 4) 5) namespace WebBook 6) { 7) public class ServiceClient 8) { 9) public static void Main() 10) { 11) // Using the Reservation class function 12) Reservation res_obj = new Reservation(); 13) res_obj.ReserveSeat(); 14) res_obj.ReserveSeat(); 15) 16) Console.WriteLine ("Total Time spent is:" + 17) res_obj.TotalTime()); 18) } 19) Console.ReadLine(); 20) } 21) 22) } If the total time spent for one time execution of function ReserveSeat() is 1 second, can you guess what should it display? Most probably, you would say: "Total Time spent is 00:00:02" but it would display "Total Time spent is 00:00:00". The problem is explained below: To keep track of user sessions, ASP.NET uses browser cookies. When a browser accesses a site, a cookie and a session Object is created and session ID is stored in the cookie. Upon every subsequent access, this cookie is used to send Session ID to the webserver which contains user’s session data (that is why, if a web service has thousands of clients, its webserver resources may be consumed quickly). The problem is, not all web browsers enable cookies. Also, console applications do not use session cookies. So using a session object is not possible in these cases.
Solution:
There is a two step solution to this problem. 1) As I said, many clients would not use cookies. However for those that use cookies, disable them. This is achieved by making a simple change in web.config file:
<system.web> <session cookieless = "true"> 2) We should force ASP.NET to seek some alternative solution for session handling. In the absence of cookies, ASP.NET uses a technique called "URL Munging". In this technique, session ID is placed in the url of the web service when the first method call is made. For example, in this case, we can have something like: http://localhost/WebBook/(fdsjkalkjfal45kajs2)/Reservation.asmx There is another issue: This new redirected URL will cause an exception in console application. Ignore the cause of this exception . Below is one way to recover from exception and generate the accurate results. Listing 1.3 1) using System; 2) using System.Net; 3) using WebBook.localhost(); 4) 5) namespace WebBook 6) { 7) public class ServiceClient 8) { 9) public static void Main() 10) { 11) // Using the Reservation class function 12) Reservation res_obj = new Reservation(); 13) // Now, We handle the exception that arises in console 14) // application. 15) 16) try 17) { 18) res_obj.ReserveSeat(); 19) }
20) catch (WebException e) 21) { 22) 23) // This catch code would always execute because of the 24) // exception. The exception message e would tell us the 25) // munged redirected url. 26) 27) String message = e.Message; 28) 29) // Since the message contain something like "Object moved to 30) //
0) 40) { 41) 42) // Extract the message now. Message to be extracted from 43) // start of message to 500 characters ahead, You can choose 44) // to even more if message length is more than 500 45) 46) String extracted_message = message.SubString (desired_pos 47) + 500) 48) 49) // Extract the url from the extracted message. 50) // We would split the string using the ' character. The url 51) // would be the 2nd element 52) 53) String[] broken_message = extracted_message.Split ('\''); 54) String url= broken_message[1]; 55) 56) // Now, lets redirect to access the right page and to see the 57) // effect of session object. 58) res_obj.Url = "http://localhost "+ url; 59) }
60) 61) else 62) { 63) // Some other exception 64) throw e; 65) } 66) 67) } 68) // Now these calls would generate right results 69) res_obj.ReserveSeat(); 70) res_obj.ReserveSeat(); 71) 72) Console.WriteLine ("Total Time spent is:" + 73) res_obj.TotalTime()); 74) 75) } 76) Console.ReadLine(); 77) } 78) } When you make a client like Listing 1.3, you would get something like: Total Time spent is: 00:00:02
Using Application State Please refer to Listing 1.1 and see Line 18-22. The application object can be used with the same ease in all web services as is used in other web applications. If you want to calculate the average time for processing of one seat reservation request, you can do so by dividing the output of TotalTime() function in Listing 1.1 to the "requests" variable in Application object which counts all the requests made.
Summary: •
The use of Session object in web services is debatable. • ASP.NET use cookies to keep track of sessions. • Therefore, Session handling is not possible in console applications and web browsers having cookies disabled. • One possible solution is to disable cookies so that ASP.NET may use the technique "URL Munging". In this technique, it appends session ID to the url on first call. • Usage of Application object is easy and can be done with the same logic as web applications.