Session Tracking in Web Services

In Chapter 21, we described the advantages of maintaining information about users to personalize their experiences. In particular, we discussed session tracking using cookies and HttpSessionState objects. We will now incorporate session tracking into a Web service. Suppose a client application needs to call several methods from the same Web service, possibly several times each. In such a case, it can be beneficial for the Web service to maintain state information for the client. Session tracking eliminates the need for information about the client to be passed between the client and the Web service multiple times. For example, a Web service providing access to local restaurant reviews would benefit from storing the client user's street address. Once the user's address is stored in a session variable, Web methods can return personalized, localized results without requiring that the address be passed in each method call. This not only improves performance, but also requires less effort on the part of the programmerless information is passed in each method call.

22.5.1. Creating a Blackjack Web Service

Storing session information can provide client programmers with a more intuitive Web service. Our next example is a Web service that assists programmers in developing a blackjack card game (Fig. 22.18). The Web service provides Web methods to deal a card and to evaluate a hand of cards. After presenting the Web service, we use it to serve as the dealer for a game of blackjack (Fig. 22.19). The blackjack Web service uses a session variable to maintain a unique deck of cards for each client application. Several clients can use the service at the same time, but Web method calls made by a specific client use only the deck stored in that client's session. Our example uses a simple subset of casino blackjack rules:

Two cards each are dealt to the dealer and the player. The player's cards are dealt face up. Only the first of the dealer's cards is dealt face up. Each card has a value. A card numbered 2 through 10 is worth its face value. Jacks, queens and kings each count as 10. Aces can count as 1 or 11whichever value is more beneficial to the player (as we will soon see). If the sum of the player's two initial cards is 21 (i.e., the player was dealt a card valued at 10 and an ace, which counts as 11 in this situation), the player has "blackjack" and immediately wins the game. Otherwise, the player can begin taking additional cards one at a time. These cards are dealt face up, and the player decides when to stop taking cards. If the player "busts" (i.e., the sum of the player's cards exceeds 21), the game is over, and the player loses. When the player is satisfied with the current set of cards, the player "stays" (i.e., stops taking cards), and the dealer's hidden card is revealed. If the dealer's total is 16 or less, the dealer must take another card; otherwise, the dealer must stay. The dealer must continue to take cards until the sum of the dealer's cards is greater than or equal to 17. If the dealer exceeds 21, the player wins. Otherwise, the hand with the higher point total wins. If the dealer and the player have the same point total, the game is a "push" (i.e., a tie), and no one wins.

Figure 22.18. Blackjack Web service.

1 // Fig. 22.18: BlackjackService.cs 2 // Blackjack Web Service deals and counts cards. 3 using System; 4 using System.Web; 5 using System.Web.Services; 6 using System.Web.Services.Protocols; 7 using System.Collections; 8 9 [ WebService( Namespace = "http://www.deitel.com/", Description = 10 "A Web service that deals and counts cards for the game Blackjack" ) ] 11 [ WebServiceBinding( ConformsTo = WsiProfiles.BasicProfile1_1 ) ] 12 public class BlackjackService : System.Web.Services.WebService 13 { 14 // deals card that has not yet been dealt 15 [ WebMethod( EnableSession = true, 16 Description="Deal a new card from the deck." ) ] 17 public string DealCard() 18 { 19 string card = "2 2"; 20 21 // get client's deck 22 ArrayList deck = ( ArrayList )( Session[ "deck" ] ); 23 card = Convert.ToString( deck[ 0 ] ); 24 deck.RemoveAt( 0 ); 25 return card; 26 } // end method DealCard 27 28 // creates and shuffles a deck of cards 29 [ WebMethod( EnableSession = true, 30 Description="Create and shuffle a deck of cards." ) ] 31 public void Shuffle() 32 { 33 object temporary; // holds card temporarily during swapping 34 Random randomObject = new Random(); // generates random numbers 35 int newIndex; // index of randomly selected card 36 ArrayList deck = new ArrayList(); // stores deck of cards (strings) 37 38 // generate all possible cards 39 for ( int i = 1; i <= 13; i++ ) // loop through face values 40 for ( int j = 0; j <= 3; j++ ) // loop through suits 41 deck.Add( i + " " + j ); // add card (string) to deck 42 43 // shuffles deck by swapping each card with another card randomly 44 for ( int i = 0; i < deck.Count; i++ ) 45 { 46 // get random index 47 newIndex = randomObject.Next( deck.Count - 1 ); 48 temporary = deck[ i ]; // save current card in temporary variable 49 deck[ i ] = deck[ newIndex ]; // copy randomly selected card 50 deck[ newIndex ] = temporary; // copy current card back into deck 51 } // end for 52 53 // add this deck to user's session state 54 Session.Add( "deck", deck ); 55 } // end method Shuffle 56 57 // computes value of hand 58 [ WebMethod( Description = 59 "Compute a numerical value for the current hand." ) ] 60 public int GetHandValue( string dealt ) 61 { 62 // split string containing all cards 63 char[] tab = { ' ' }; 64 string[] cards = dealt.Split( tab ); // get array of cards 65 int total = 0; // total value of cards in hand 66 int face; // face of the current card 67 int aceCount = 0; // number of aces in hand 68 69 // loop through the cards in the hand 70 foreach ( string drawn in cards ) 71 { 72 // get face of card 73 face = Int32.Parse( drawn.Substring( 0, drawn.IndexOf( " " ) ) ); 74 75 switch ( face ) 76 { 77 case 1: // if ace, increment aceCount 78 aceCount++; 79 break; 80 case 11: // if jack add 10 81 case 12: // if queen add 10 82 case 13: // if king add 10 83 total += 10; 84 break; 85 default: // otherwise, add value of face 86 total += face; 87 break; 88 } // end switch 89 } // end foreach 90 91 // if there are any aces, calculate optimum total 92 if ( aceCount > 0 ) 93 { 94 // if it is possible to count one ace as 11, and the rest 95 // as 1 each, do so; otherwise, count all aces as 1 each 96 if ( total + 11 + aceCount - 1 <= 21 ) 97 total += 11 + aceCount - 1; 98 else 99 total += aceCount; 100 } // end if 101 102 return total; 103 } // end method GetHandValue 104 } // end class BlackjackService

Figure 22.19. Blackjack game that uses the Blackjack Web service.

(This item is displayed on pages 1195 - 1202 in the print version)

1 // Fig. 22.19: Blackjack.cs 2 // Blackjack game that uses the Blackjack Web service. 3 using System; 4 using System.Collections.Generic; 5 using System.ComponentModel; 6 using System.Data; 7 using System.Drawing; 8 using System.Text; 9 using System.Windows.Forms; 10 using System.Net; 11 using System.Collections; 12 13 namespace Blackjack 14 { 15 public partial class BlackjackForm : Form 16 { 17 // reference to Web service 18 private localhost.BlackjackService dealer; 19 20 // string representing the dealer's cards 21 private string dealersCards; 22 23 // string representing the player's cards 24 private string playersCards; 25 private ArrayList cardBoxes; // list of PictureBoxes for card images 26 private int currentPlayerCard; // player's current card number 27 private int currentDealerCard; // dealer's current card number 28 29 // enum representing the possible game outcomes 30 public enum GameStatus 31 { 32 PUSH, // game ends in a tie 33 LOSE, // player loses 34 WIN, // player wins 35 BLACKJACK // player has blackjack 36 } // end enum GameStatus 37 38 public BlackjackForm() 39 { 40 InitializeComponent(); 41 } // end constructor 42 43 // sets up the game 44 private void BlackjackForm_Load( object sender, EventArgs e ) 45 { 46 // instantiate object allowing communication with Web service 47 dealer = new localhost.BlackjackService(); 48 49 // allow session state 50 dealer.CookieContainer = new CookieContainer(); 51 cardBoxes = new ArrayList(); 52 53 // put PictureBoxes into cardBoxes 54 cardBoxes.Add( pictureBox1 ); 55 cardBoxes.Add( pictureBox2 ); 56 cardBoxes.Add( pictureBox3 ); 57 cardBoxes.Add( pictureBox4 ); 58 cardBoxes.Add( pictureBox5 ); 59 cardBoxes.Add( pictureBox6 ); 60 cardBoxes.Add( pictureBox7 ); 61 cardBoxes.Add( pictureBox8 ); 62 cardBoxes.Add( pictureBox9 ); 63 cardBoxes.Add( pictureBox10 ); 64 cardBoxes.Add( pictureBox11 ); 65 cardBoxes.Add( pictureBox12 ); 66 cardBoxes.Add( pictureBox13 ); 67 cardBoxes.Add( pictureBox14 ); 68 cardBoxes.Add( pictureBox15 ); 69 cardBoxes.Add( pictureBox16 ); 70 cardBoxes.Add( pictureBox17 ); 71 cardBoxes.Add( pictureBox18 ); 72 cardBoxes.Add( pictureBox19 ); 73 cardBoxes.Add( pictureBox20 ); 74 cardBoxes.Add( pictureBox21 ); 75 cardBoxes.Add( pictureBox22 ); 76 } // end method BlackjackForm_Load 77 78 // deals cards to dealer while dealer's total is less than 17, 79 // then computes value of each hand and determines winner 80 private void DealerPlay() 81 { 82 // while value of dealer's hand is below 17, 83 // dealer must take cards 84 while ( dealer.GetHandValue( dealersCards ) < 17 ) 85 { 86 dealersCards += ' ' + dealer.DealCard(); // deal new card 87 88 // update GUI to show new card 89 DisplayCard( currentDealerCard, "" ); 90 currentDealerCard++; 91 MessageBox.Show( "Dealer takes a card" ); 92 } // end while 93 94 int dealersTotal = dealer.GetHandValue( dealersCards ); 95 int playersTotal = dealer.GetHandValue( playersCards ); 96 97 // if dealer busted, player wins 98 if ( dealersTotal > 21 ) 99 { 100 GameOver( GameStatus.WIN ); 101 return; 102 } // end if 103 104 // if dealer and player have not exceeded 21, 105 // higher score wins; equal scores is a push. 106 if ( dealersTotal > playersTotal ) 107 GameOver( GameStatus.LOSE ); 108 else if ( playersTotal > dealersTotal ) 109 GameOver( GameStatus.WIN ); 110 else 111 GameOver( GameStatus.PUSH ); 112 } // end method DealerPlay 113 114 // displays card represented by cardValue in specified PictureBox 115 public void DisplayCard( int card, string cardValue ) 116 { 117 // retrieve appropriate PictureBox from ArrayList 118 PictureBox displayBox = ( PictureBox )( cardBoxes[ card ] ); 119 120 // if string representing card is empty, 121 // set displayBox to display back of card 122 if ( cardValue == "" ) 123 { 124 displayBox.Image = 125 Image.FromFile( "blackjack_images/cardback.png" ); 126 return; 127 } // end if 128 129 // retrieve face value of card from cardValue 130 string face = cardValue.Substring( 0, cardValue.IndexOf( " " ) ); 131 132 // retrieve the suit of the card from cardValue 133 string suit = 134 cardValue.Substring( cardValue.IndexOf( " " ) + 1 ); 135 136 char suitLetter; // suit letter used to form image file name 137 138 // determine the suit letter of the card 139 switch ( Convert.ToInt32( suit ) ) 140 { 141 case 0: // clubs 142 suitLetter = 'c'; 143 break; 144 case 1: // diamonds 145 suitLetter = 'd'; 146 break; 147 case 2: // hearts 148 suitLetter = 'h'; 149 break; 150 default: // spades 151 suitLetter = 's'; 152 break; 153 } // end switch 154 155 // set displayBox to display appropriate image 156 displayBox.Image = Image.FromFile( 157 "blackjack_images/" + face + suitLetter + ".png" ); 158 } // end method DisplayCard 159 160 // displays all player cards and shows 161 // appropriate game status message 162 public void GameOver( GameStatus winner ) 163 { 164 char[] tab = { ' ' }; 165 string[] cards = dealersCards.Split( tab ); 166 167 // display all the dealer's cards 168 for ( int i = 0; i < cards.Length; i++ ) 169 DisplayCard( i, cards[ i ] ); 170 171 // display appropriate status image 172 if ( winner == GameStatus.PUSH ) // push 173 statusPictureBox.Image = 174 Image.FromFile( "blackjack_images/tie.png" ); 175 else if ( winner == GameStatus.LOSE ) // player loses 176 statusPictureBox.Image = 177 Image.FromFile( "blackjack_images/lose.png" ); 178 else if ( winner == GameStatus.BLACKJACK ) 179 // player has blackjack 180 statusPictureBox.Image = 181 Image.FromFile( "blackjack_images/blackjack.png" ); 182 else // player wins 183 statusPictureBox.Image = 184 Image.FromFile( "blackjack_images/win.png" ); 185 186 // display final totals for dealer and player 187 dealerTotalLabel.Text = 188 "Dealer: " + dealer.GetHandValue( dealersCards ); 189 playerTotalLabel.Text = 190 "Player: " + dealer.GetHandValue( playersCards ); 191 192 // reset controls for new game 193 stayButton.Enabled = false; 194 hitButton.Enabled = false; 195 dealButton.Enabled = true; 196 } // end method GameOver 197 198 // deal two cards each to dealer and player 199 private void dealButton_Click( object sender, EventArgs e ) 200 { 201 string card; // stores a card temporarily until added to a hand 202 203 // clear card images 204 foreach ( PictureBox cardImage in cardBoxes ) 205 cardImage.Image = null; 206 207 statusPictureBox.Image = null; // clear status image 208 dealerTotalLabel.Text = ""; // clear final total for dealer 209 playerTotalLabel.Text = ""; // clear final total for player 210 211 // create a new, shuffled deck on the remote machine 212 dealer.Shuffle(); 213 214 // deal two cards to player 215 playersCards = dealer.DealCard(); // deal a card to player's hand 216 217 // update GUI to display new card 218 DisplayCard( 11, playersCards ); 219 card = dealer.DealCard(); // deal a second card 220 DisplayCard( 12, card ); // update GUI to display new card 221 playersCards += ' ' + card; // add second card to player's hand 222 223 // deal two cards to dealer, only display face of first card 224 dealersCards = dealer.DealCard(); // deal a card to dealer's hand 225 DisplayCard( 0, dealersCards ); // update GUI to display new card 226 card = dealer.DealCard(); // deal a second card 227 DisplayCard( 1, "" ); // update GUI to show face-down card 228 dealersCards += ' ' + card; // add second card to dealer's hand 229 230 stayButton.Enabled = true; // allow player to stay 231 hitButton.Enabled = true; // allow player to hit 232 dealButton.Enabled = false; // disable Deal Button 233 234 // determine the value of the two hands 235 int dealersTotal = dealer.GetHandValue( dealersCards ); 236 int playersTotal = dealer.GetHandValue( playersCards ); 237 238 // if hands equal 21, it is a push 239 if ( dealersTotal == playersTotal && dealersTotal == 21 ) 240 GameOver( GameStatus.PUSH ); 241 else if ( dealersTotal == 21 ) // if dealer has 21, dealer wins 242 GameOver( GameStatus.LOSE ); 243 else if ( playersTotal == 21 ) // player has blackjack 244 GameOver( GameStatus.BLACKJACK ); 245 246 // next dealer card has index 2 in cardBoxes 247 currentDealerCard = 2; 248 249 // next player card has index 13 in cardBoxes 250 currentPlayerCard = 13; 251 } // end method dealButton_Click 252 253 // deal another card to player 254 private void hitButton_Click( object sender, EventArgs e ) 255 { 256 // get player another card 257 string card = dealer.DealCard(); // deal new card 258 playersCards += ' ' + card; // add new card to player's hand 259 260 // update GUI to show new card 261 DisplayCard( currentPlayerCard, card ); 262 currentPlayerCard++; 263 264 // determine the value of the playerís hand 265 int total = dealer.GetHandValue( playersCards ); 266 267 // if player exceeds 21, house wins 268 if ( total > 21 ) 269 GameOver( GameStatus.LOSE ); 270 271 // if player has 21, 272 // they cannot take more cards, and dealer plays 273 if ( total == 21 ) 274 { 275 hitButton.Enabled = false; 276 DealerPlay(); 277 } // end if 278 } // end method hitButton_Click 279 280 // play the dealer's hand after the play chooses to stay 281 private void stayButton_Click( object sender, EventArgs e ) 282 { 283 stayButton.Enabled = false; // disable Stay Button 284 hitButton.Enabled = false; // display Hit Button 285 dealButton.Enabled = true; // re-enable Deal Button 286 DealerPlay(); // player chose to stay, so play the dealer's hand 287 } // end method stayButton_Click 288 } // end class BlackjackForm 289 } // end namespace Blackjack

a) Initial cards dealt to the player and the dealer when the user pressed the Deal button.

b) Cards after the player pressed the Hit button twice, then the Stay button. In this case, the player won the game with a higher total than the dealer.

c) Cards after the player pressed the Hit button once, then the Stay button. In this case, the player busted (exceeded 21) and the dealer won the game.

d) Cards after the player pressed the Deal button. In this case, the player won with Blackjack because the first two cards were an ace and a card with a value of 10 (a jack in this case).

e) Cards after the player pressed the Stay button. In this case, the player and dealer pushthey have the same card total.

The Web service (Fig. 22.18) provides methods to deal a card and to determine the point value of a hand. We represent each card as a string consisting of a digit (e.g., 113) representing the card's face (e.g., ace through king), followed by a space and a digit (e.g., 03) representing the card's suit (e.g., clubs, diamonds, hearts or spades). For example, the jack of hearts is represented as "11 2", and the two of clubs is represented as "20". After deploying the Web service, we create a Windows application that uses the BlackjackService's Web methods to implement a game of blackjack. To create and deploy this Web service follow the steps presented in Sections 22.4.222.4.3 for the HugeInteger service.

Lines 1516 define method DealCard as a Web method. Setting property EnableSession to TRue indicates that session information should be maintained and should be accessible to this method. This is required only for methods that must access the session information. Doing so allows the Web service to use an HttpSessionState object (named Session by ASP.NET) to maintain the deck of cards for each client application that uses this Web service (line 22). We can use Session to store objects for a specific client between method calls. We discussed session state in detail in Chapter 21.

Method DealCard removes a card from the deck and sends it to the client. Without using a session variable, the deck of cards would need to be passed back and forth with each method call. Using session state make the method easy to call (it requires no arguments), and avoids the overhead of sending the deck over the network multiple times.

At this point, our Web service contains methods that use session variables. However, the Web service still cannot determine which session variables belong to which user. If two clients successfully call the DealCard method, the same deck would be manipulated. To avoid this problem, the Web service automatically creates a cookie to uniquely identify each client. A Web browser client that has cookie handling enabled stores cookies automatically. A non-browser client application that consumes this Web service must create a CookieContainer object to store cookies sent from the server. We discuss this in more detail in Section 22.5.2, when we examine the blackjack Web service's client.

Web method DealCard (lines 1526) selects a card from the deck and sends it to the client. The method first obtains the current user's deck as an ArrayList from the Web service's Session object (line 22). After obtaining the user's deck, DealCard removes the top card from the deck (line 24) and returns the card's value as a string (line 25).

Method Shuffle (lines 2955) generates an ArrayList representing a deck of cards, shuffles it and stores the shuffled cards in the client's Session object. Lines 3941 use nested for statements to generate strings in the form "face suit" to represent each possible card in a deck. Lines 4451 shuffle the deck by swapping each card with another card selected at random. Line 54 adds the ArrayList to the Session object to maintain the deck between method calls from a particular client.

Method GetHandValue (lines 58103) determines the total value of the cards in a hand by trying to attain the highest score possible without going over 21. Recall that an ace can be counted as either 1 or 11, and all face cards count as 10.

As you will see in Fig. 22.19, the client application maintains a hand of cards as a string in which each card is separated by a tab character. Line 64 tokenizes the hand of cards (represented by dealt) into individual cards by calling string method Split and passing to it an array that contains the delimiter characters (in this case, just a tab). Split uses the delimiter characters to separate tokens in the string. Lines 7089 count the value of each card. Line 73 retrieves the first integerthe faceand uses that value in the switch statement (lines 7588). If the card is an ace, the method increments variable aceCount. We discuss how this variable is used shortly. If the card is an 11, 12 or 13 (jack, queen or king), the method adds 10 to the total value of the hand (line 83). If the card is anything else, the method increases the total by that value (line 86).

Because an ace can have either of two values, additional logic is required to process aces. Lines 92100 of method GetHandValue process the aces after all the other cards. If a hand contains several aces, only one ace can be counted as 11 (if two aces each are counted as 11, the hand would have a losing value of 22). The condition in line 96 determines whether counting one ace as 11 and the rest as 1 will result in a total that does not exceed 21. If this is possible, line 97 adjusts the total accordingly. Otherwise, line 99 adjusts the total, counting each ace as 1.

Method GetHandValue maximizes the value of the current cards without exceeding 21. Imagine, for example, that the dealer has a 7 and receives an ace. The new total could be either 8 or 18. However, GetHandValue always maximizes the value of the cards without going over 21, so the new total is 18.

22.5.2. Consuming the Blackjack Web Service

Now we use the blackjack Web service in a Windows application (Fig. 22.19). This application uses an instance of BlackjackService (declared in line 18 and created in line 47) to represent the dealer. The Web service keeps track of the player's and the dealer's cards (i.e., all the cards that have been dealt).

Each player has 11 PictureBoxesthe maximum number of cards that can be dealt without automatically exceeding 21 (i.e., four aces, four twos and three threes). These PictureBoxes are placed in an ArrayList (lines 5475), so we can index the ArrayList during the game to determine the PictureBox that will display a particular card image.

In Section 22.5.1, we mentioned that the client must provide a way to accept cookies created by the Web service to uniquely identify users. Line 50 in BlackjackForm's Load event handler creates a new CookieContainer object for the dealer's CookieContainer property. A CookieContainer (namespace System.Net) stores the information from a cookie (created by the Web service) in a Cookie object in the CookieContainer. The Cookie contains a unique identifier that the Web service can use to recognize the client when the client makes future requests. As part of each request, the cookie is automatically sent back to the server. If the client did not create a CookieContainer object, the Web service would create a new Session object for each request, and the user's state information would not persist across requests.

Method GameOver (lines 162196) displays all the dealer's cards, shows the appropriate message in the status PictureBox and displays the final point totals of both the dealer and the player. Method GameOver receives as an argument a member of the GameStatus enumeration (defined in lines 3036). The enumeration represents whether the player tied, lost or won the game; its four members are PUSH, LOSE, WIN and BLACKJACK.

When the player clicks the Deal button (whose event handler appears in lines 199251), all of the PictureBoxes and the Labels displaying the final point totals are cleared. Next, the deck is shuffled, and the player and dealer receive two cards each. If the player and the dealer both obtain scores of 21, the program calls method GameOver, passing GameStatus.PUSH. If only the player has 21 after the first two cards are dealt, the program passes GameStatus.BLACKJACK to method GameOver. If only the dealer has 21, the program passes GameStatus.LOSE to method GameOver.

If dealButton_Click does not call GameOver, the player can take more cards by clicking the Hit button. The event handler for this button is in lines 254278. Each time a player clicks Hit, the program deals the player one more card and displays it in the GUI. If the player exceeds 21, the game is over, and the player loses. If the player has exactly 21, the player is not allowed to take any more cards, and method DealerPlay (lines 80112) is called, causing the dealer to keep taking cards until the dealer's hand has a value of 17 or more (lines 8492). If the dealer exceeds 21, the player wins (line 100); otherwise, the values of the hands are compared, and GameOver is called with the appropriate argument (lines 106111).

Clicking the Stay button indicates that a player does not want to be dealt another card. The event handler for this button (lines 281287) disables the Hit and Stay buttons, then calls method DealerPlay.

Method DisplayCard (lines 115158) updates the GUI to display a newly dealt card. The method takes as arguments an integer representing the index of the PictureBox in the ArrayList that must have its image set, and a string representing the card. An empty string indicates that we wish to display the card face down. If method DisplayCard receives a string that's not empty, the program extracts the face and suit from the string and uses this information to find the correct image. The switch statement (lines 139153) converts the number representing the suit to an integer and assigns the appropriate character to suitLetter (c for clubs, d for diamonds, h for hearts and s for spades). The character in suitLetter is used to complete the image's file name (lines 156157).

Using Web Forms and Web Services

Категории