Section 22.5. Session Tracking in Web Services


22.5. 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: BlackjackWebService.vb  2  ' Blackjack Web Service deals and counts cards.  3  Imports System.Web  4  Imports System.Web.Services  5  Imports System.Web.Services.Protocols  6  7  <WebService(Namespace:="http://www.deitel.com/", Description:= _           8     "A Web service that deals and counts cards for the game Blackjack")> _  9  <WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _            10  <Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _     11  Public Class BlackjackWebService 12     Inherits 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 Function DealCard() As String 18        ' get client's deck 19        Dim deck As ArrayList = CType(Session("deck"), ArrayList) 20        Dim card As String = Convert.ToString(deck(0)) 21        deck.RemoveAt(0) 22        Return card 23     End Function ' DealCard 24 25     ' creates and shuffles a deck of cards 26     <WebMethod(EnableSession:=True, _ 27        Description:="Create and shuffle a deck of cards.")> _ 28     Public Sub Shuffle() 29        Dim temporary As Object ' holds card temporarily during swapping 30        Dim randomObject As New Random() ' generates random numbers 31        Dim newIndex As Integer ' index of randomly selected card 32        Dim deck As New ArrayList() ' stores deck of cards (strings) 33 34       ' generate all possible cards 35       For face As Integer = 1 To 13 ' loop through face values 36          For suit As Integer = 0 To 3 ' loop through suits 37             deck.Add(face & " " & suit) ' add  card (string) to deck 38          Next 39      Next 40 41     ' shuffles deck by swapping each card with another card randomly 42      For i As Integer = 0 To deck.Count - 1 43         ' get random index 44         newIndex = randomObject.Next(deck.Count - 1) 45         temporary = deck(i) ' save current card in temporary variable 46         deck(i) = deck(newIndex) ' copy randomly selected card 47         deck(newIndex) = temporary ' copy current card  back   into deck 48      Next 49 50      Session.Add("deck", deck) ' add this deck to user's session state 51   End Sub ' Shuffle 52 53     ' computes value of hand 54     <WebMethod(Description:= _ 55         "Compute a numerical value for the current hand.")> _ 56     Public Function GetHandValue(ByVal dealt As String) As Integer 57        ' split string containing all cards 58        Dim tab As Char() = New Char() {Convert.ToChar(vbTab)} 59        Dim cards As String() = dealt.Split(tab) ' get array of cards 60        Dim total As Integer = 0 ' total value of cards in hand 61        Dim face As integer ' face of the current card 62        Dim aceCount As Integer = 0 ' number of aces in hand 63 64       ' loop through the cards in the  hand 65        For Each drawn As String In cards 66           ' get face of card 67           face = Int32.Parse(drawn.Substring(0, drawn.IndexOf(" "))) 68 69           Select Case face 70              Case 1 ' if ace, increment aceCount 71                 aceCount += 1 72              Case 11 To 13 ' if jack, queen or king add 10 73                    total += 10 74              Case Else ' otherwise, add value of face 75                 total += face 76           End Select 77        Next 78 79        ' if there are any aces, calculate optimum total 80        If aceCount > 0 Then 81           ' if it is possible to count one ace as 11, and the rest 82           ' as 1 each, do so otherwise, count all aces as 1 each 83           If (total + 11 + aceCount - 1 <= 21) Then 84              total += 11 + aceCount - 1 85           Else 86              total += aceCount 87           End If 88        End If 89 90        Return total 91     End Function ' GetHandValue 92  End Class ' BlackjackWebService 

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

 1  ' Fig. 22.19: FrmBlackjack.vb 2  ' Blackjack game that uses the Blackjack Web service. 3  Imports System.Net 4 5  Public Class FrmBlackjack 6     ' reference to Web service                      7     Private dealer As localhost.BlackjackWebService 8 9     ' string representing the dealer's cards 10    Private dealersCards As String 11 12    ' string representing the player's cards 13     Private playersCards As String 14     Private cardBoxes As ArrayList ' list of PictureBoxes for card images 15     Private currentPlayerCard As Integer ' player's current card number 16     Private currentDealerCard As Integer ' dealer's current card number 17 18    ' enum representing the possible game outcomes 19     Public Enum GameStatus 20        PUSH ' game ends in a tie 21        LOSE ' player loses 22        WIN ' player wins 23        BLACKJACK ' player has blackjack 24     End Enum ' GameStatus 25 26     ' sets up the game 27     Private Sub FrmBlackjack_Load(ByVal sender As Object, _ 28        ByVal e As System.EventArgs) Handles Me.Load 29        ' instantiate object allowing communication with Web service 30        dealer = New localhost.BlackjackWebService()                 31 32        ' allow session state                          33        dealer.CookieContainer = New CookieContainer() 34        cardBoxes = New ArrayList() 35 36        ' put PictureBoxes into cardBoxes ArrayList 37        cardBoxes.Add(pictureBox1) 38        cardBoxes.Add(pictureBox2) 39        cardBoxes.Add(pictureBox3) 40        cardBoxes.Add(pictureBox4) 41        cardBoxes.Add(pictureBox5) 42        cardBoxes.Add(pictureBox6) 43        cardBoxes.Add(pictureBox7) 44        cardBoxes.Add(pictureBox8) 45        cardBoxes.Add(pictureBox9) 46        cardBoxes.Add(pictureBox10) 47        cardBoxes.Add(pictureBox11) 48        cardBoxes.Add(pictureBox12) 49        cardBoxes.Add(pictureBox13) 50        cardBoxes.Add(pictureBox14) 51        cardBoxes.Add(pictureBox15) 52        cardBoxes.Add(pictureBox16) 53        cardBoxes.Add(pictureBox17) 54        cardBoxes.Add(pictureBox18) 55        cardBoxes.Add(pictureBox19) 56        cardBoxes.Add(pictureBox20) 57        cardBoxes.Add(pictureBox21) 58        cardBoxes.Add(pictureBox22) 59     End Sub ' FrmBlackjack_Load 60 61     ' deals cards to dealer while dealer's total is less than 17, 62     ' then computes value of each hand and determines winner 63     Private Sub DealerPlay() 64        ' reveal dealer's second card 65        Dim tab As Char() = New Char() {Convert.ToChar(vbTab)} 66        Dim cards As String() = dealersCards.Split(tab) 67        DisplayCard(1, cards(1)) 68 69        Dim nextCard As String 70 71        ' while value of dealer's hand is below 17, 72        ' dealer must take cards 73        While dealer.GetHandValue(dealersCards) < 17 74           nextCard = dealer.DealCard() ' deal new card 75           dealersCards &= vbTab & nextCard 76 77           ' update GUI to show new card 78           MessageBox.Show("Dealer takes a card") 79        DisplayCard(currentDealerCard, nextCard) 80        currentDealerCard += 1 81     End While 82 83     Dim dealerTotal As Integer = dealer.GetHandValue(dealersCards) 84     Dim playerTotal As Integer = dealer.GetHandValue(playersCards) 85 86     ' if dealer busted, player wins 87     If dealerTotal > 21 Then 88        GameOver(GameStatus.WIN) 89     Else 90        ' if dealer and player have not exceeded 21, 91        ' higher score wins equal scores is a push. 92        If dealerTotal > playerTotal Then 93           GameOver(GameStatus.LOSE) 94        ElseIf playerTotal > dealerTotal Then 95           GameOver(GameStatus.WIN) 96        Else 97           GameOver(GameStatus.PUSH) 98        End If 99     End If 100  End Sub ' DealerPlay 101 102  ' displays card represented by cardValue in specified PictureBox 103  Public Sub DisplayCard( _ 104     ByVal card As Integer, ByVal cardValue As String) 105     ' retrieve appropriate PictureBox from ArrayList 106     Dim displayBox As PictureBox = CType(cardBoxes(card), PictureBox) 107 108     ' if string representing card is empty, 109     ' set displayBox to display back of card 110     If cardValue = "" Then 111        displayBox.Image = _ 112           Image.FromFile("blackjack_images/cardback.png") 113        Return 114     End If 115 116     ' retrieve face value of card from cardValue 117     Dim face As String = _ 118        cardValue.Substring(0, cardValue.IndexOf(" ")) 119 120     ' retrieve the suit of the card from cardValue 121     Dim suit As String = _ 122        cardValue.Substring(cardValue.IndexOf(" ") + 1) 123 124     Dim suitLetter As Char ' suit letter used to form image file name 125 126     ' determine the suit letter of the card 127     Select Case Convert.ToInt32(suit) 128        Case 0 ' clubs 129           suitLetter = "c"c 130        Case 1 ' diamonds 131           suitLetter = "d"c 132        Case 2 ' hearts 133           suitLetter = "h"c 134        Case Else ' spades 135           suitLetter = "s"c 136     End Select 137 138     ' set displayBox to display appropriate image 139     displayBox.Image = Image.FromFile( _ 140        "blackjack_images/" & face & suitLetter & ".png") 141  End Sub ' DisplayCard 142 143  ' displays all player cards and shows 144  ' appropriate game status message 145  Public Sub GameOver(ByVal winner As GameStatus) 146     ' display appropriate status image 147     If winner = GameStatus.PUSH Then ' push 148        statusPictureBox.Image  = _ 149           Image.FromFile("blackjack_images/tie.png") 150     ElseIf winner = GameStatus.LOSE Then ' player loses 151        statusPictureBox.Image  = _ 152           Image.FromFile("blackjack_images/lose.png") 153     ElseIf winner = GameStatus.BLACKJACK Then 154        ' player has blackjack 155        statusPictureBox.Image  = _ 156           Image.FromFile("blackjack_images/blackjack.png") 157     Else ' player wins 158        statusPictureBox.Image  = _ 159           Image.FromFile("blackjack_images/win.png") 160     End If 161 162     ' display final totals for dealer and player 163     lblDealerTotal.Text = _ 164        "Dealer: " & dealer.GetHandValue(dealersCards) 165     lblPlayerTotal.Text = _ 166        "Player: " & dealer.GetHandValue(playersCards) 167 168     ' reset controls for new game 169     btnStay.Enabled = False 170     btnHit.Enabled = False 171     btnDeal.Enabled = True 172  End Sub ' GameOver 173 174  ' deal two cards each to dealer and player 175  Private Sub btnDeal_Click(ByVal sender As System.Object, _ 176     ByVal e As System.EventArgs) Handles btnDeal.Click 177     Dim card As String ' stores a card temporarily until added to a hand 178 179     ' clear card images 180     For Each cardImage As PictureBox In  cardBoxes 181        cardImage.Image = Nothing 182     Next 183 184     statusPictureBox.Image = Nothing ' clear status image 185     lblDealerTotal.Text = "" ' clear final total for dealer 186     lblPlayerTotal.Text = "" ' clear final total for player 187 188     ' create a new, shuffled deck on the remote machine 189     dealer.Shuffle()                         190 191     ' deal two  cards to player 192     playersCards = dealer.DealCard() ' deal a card to player's hand 193 194     ' update GUI to display new card 195     DisplayCard(11, playersCards) 196     card = dealer.DealCard() ' deal  a second card 197     DisplayCard(12, card) ' update GUI to display new card 198     playersCards &= vbTab & card ' add second card to player's hand 199 200     ' deal two  cards to dealer, only display face of first card 201     dealersCards = dealer.DealCard()  ' deal a card to dealer's hand 202     DisplayCard(0, dealersCards) ' update GUI to display new card 203     card = dealer.DealCard() ' deal  a second card 204     DisplayCard(1, "") ' update GUI to show face-down card 205     dealersCards &= vbTab & card ' add second card to dealer's hand 206 207     btnStay.Enabled = True ' allow player to stay 208     btnHit.Enabled = True ' allow player to hit 209     btnDeal.Enabled = False ' disable Deal Button 210 211     ' determine the value of the two hands 212     Dim dealerTotal As Integer = dealer.GetHandValue(dealersCards) 213     Dim playerTotal As Integer = dealer.GetHandValue(playersCards) 214 215     ' if hands equal 21, it is a push 216     If dealerTotal = playerTotal And dealerTotal = 21 Then 217     GameOver(GameStatus.PUSH) 218     ElseIf dealerTotal = 21 Then ' if dealer has 21, dealer wins 219     GameOver(GameStatus.LOSE) 220        ElseIf playerTotal = 21 Then ' player has blackjack 221     GameOver(GameStatus.BLACKJACK) 222     End If 223 224     currentDealerCard = 2 ' next dealer card has index 2 in cardBoxes 225     currentPlayerCard = 13 ' next player card has index 13 in cardBoxes 226  End Sub ' btnDeal_Click 227 228  ' deal another card to player 229  Private Sub btnHit_Click(ByVal sender As System.Object, _ 230     ByVal e As  System.EventArgs) Handles btnHit.Click 231     ' get player another card 232     Dim card As String = dealer.DealCard() ' deal new card 233     playersCards &= vbTab & card ' add new card to player's hand 234 235     ' update GUI to show new card 236     DisplayCard(currentPlayerCard, card) 237     currentPlayerCard += 1 238 239     ' determine the value of the player's hand 240     Dim total As Integer = dealer.GetHandValue(playersCards) 241 242     ' if player exceeds 21, house wins 243     If total >  21  Then 244     GameOver(GameStatus.LOSE) 245     End If 246 247     ' if player has 21, 248     ' they cannot take more cards, and dealer plays 249     If total =  21 Then 250        btnHit.Enabled = False 251        DealerPlay() 252     End If 253  End Sub ' btnHit_Click 254 255  ' play the dealer's hand after the play chooses to stay 256  Private Sub btnStay_Click(ByVal sender As System.Object, _ 257     ByVal e As System.EventArgs) Handles btnStay.Click 258     btnStay.Enabled = False ' disable Stay Button 259     btnHit.Enabled = False ' display Hit Button 260     btnDeal.Enabled = True ' re-enable Deal Button 261     DealerPlay() ' player chose to stay, so play the dealer's hand 262   End Sub ' btnStay_Click 263  End Class ' FrmBlackjack 

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 "2 0". After deploying the Web service, we create a Windows application that uses the BlackjackWebService'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 makes 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 1523) 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 19). After obtaining the user's deck, DealCard removes the top card from the deck (line 21) and returns the card's value as a String (line 22).

Method Shuffle (lines 2651) generates an ArrayList representing a deck of cards, shuffles it and stores it cards in the client's Session object. Lines 3539 generate Strings in the form "face suit" to represent each card in a deck. Lines 4248 shuffle the deck by swapping each card with another randomly selected card. Line 50 adds the ArrayList to the Session object to maintain the deck between method calls from a particular client.

Method GetHandValue (lines 5491) 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 59 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 6577 count the value of each card. Line 67 retrieves the first integerthe faceand uses that value in the Select Case statement (lines 6976). If the card is an ace, the method increments variable aceCount (line 71). 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 73). If the card is anything else, the method increases the total by that value (line 75).

Because an ace can have either of two values, additional logic is required to process aces. Lines 8088 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 83 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 84 adjusts the total accordingly. Otherwise, line 86 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 BlackjackWeService (declared in line 7 and created in line 30) 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). As in Section 22.4.4, you must add a Web reference to your project so it can access the Web service. The code and images for this example are provided with the chapter's examples, which can be downloaded from our Web site: www.deitel.com/books/vbforprogrammers2.

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 3758), 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 33 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 145172) shows an appropriate message in the status PictureBox and displays the final point totals of both the dealer and the player. These values are obtained by calling the Web service's GetHandValue method in lines 164 and 166. Method GameOver receives as an argument a member of the GameStatus enumeration (defined in lines 1924). 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, the event handler (lines 175226) clears all of the PictureBoxes and the Labels displaying the final point totals. Line 189 shuffles the deck by calling the Web Service's Shuffle method, then the player and dealer receive two cards each (returned by calls to the Web service's DealCard method in lines 192, 196, 201 and 203). Lines 212213 evaluate both the dealer's and player's hands by calling the Web service's GetHandValue method. 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 229253. Each time a player clicks Hit, the program deals the player one more card (line 232) and displays it in the GUI. Line 240 evaluates the player's hand. 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 63100) is called, causing the dealer to keep taking cards until the dealer's hand has a value of 17 or more (lines 7381). If the dealer exceeds 21, the player wins (line 88); otherwise, the values of the hands are compared, and GameOver is called with the appropriate argument (lines 9298).

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

Method DisplayCard (lines 103141) 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 Select Case statement (lines 127136) 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 139140).



Visual BasicR 2005 for Programmers. DeitelR Developer Series
Visual Basic 2005 for Programmers (2nd Edition)
ISBN: 013225140X
EAN: 2147483647
Year: 2004
Pages: 435

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