You are on page 1of 98

O F F I C I A L

M I CRO S O F T

L EA RN I N G

PRO DU C T

2956B

Core Foundations of Microsoft .NET 2.0 Development Companion Content

Information in this document, including URL and other Internet Web site references, is subject to change without notice. Unless otherwise noted, the example companies, organizations, products, domain names, e-mail addresses, logos, people, places, and events depicted herein are fictitious, and no association with any real company, organization, product, domain name, e-mail address, logo, person, place or event is intended or should be inferred. Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation. Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property. The names of manufacturers, products, or URLs are provided for informational purposes only and Microsoft makes no representations and warranties, either expressed, implied, or statutory, regarding these manufacturers or the use of the products with any Microsoft technologies. The inclusion of a manufacturer or product does not imply endorsement of Microsoft of the manufacturer or product. Links may be provided to third party sites. Such sites are not under the control of Microsoft and Microsoft is not responsible for the contents of any linked site or any link contained in a linked site, or any changes or updates to such sites. Microsoft is not responsible for webcasting or any other form of transmission received from any linked site. Microsoft is providing these links to you only as a convenience, and the inclusion of any link does not imply endorsement of Microsoft of the site or the products contained therein. 2008 Microsoft Corporation. All rights reserved. Microsoft and the trademarks listed at http://www.microsoft.com/about/legal/en/us/IntellectualProperty/Trademarks/EN-US.aspx are trademarks of the Microsoft group of companies. All other marks are property oftheir respective owners.

Product Number: 2956B Released: 07/2008

Developing Applications by Using Types and Standard Contracts

1-1

Module 1
Developing Applications by Using Types and Standard Contracts
Contents:
Lab Answer Keys 2

1-2

Core Foundations of Microsoft .NET 2.0 Development

Lab Answer Keys


Lab: Developing Applications by Using Types and Standard Contracts Exercise 1: Using Nullable Types
Task 1: Open the solution application
1. 2. 3. Click Start, point to All Programs, point to Microsoft Visual Studio 2005, and then click Microsoft Visual Studio 2005. In the Microsoft Visual Studio development system, on the File menu, point to Open, and then click Project/Solution. In the Open Project dialog box, browse to E:\Labfiles\Starter\CS\AccountManager (if you are using the Microsoft Visual C# development tool) or E:\Labfiles\Starter\VB\AccountManager (if you are using the Microsoft Visual Basic development system), click AccountManager.sln, and then click Open. In Solution Explorer, review the AccountManager project. The following table describes the source files in the project.

4.

Source file Account.cs Account.vb MainForm.cs MainForm.vb

Description Defines the Account class, which represents a single bank account. You will modify this source file in each of the exercises in this lab. Defines the user interface for the application. The form enables the user to deposit and withdraw money from an Account object and to get details about the current state of the Account object. You will modify this source file in later exercises in this lab.

Transaction.cs Transaction.vb TransactionEvent.cs TransactionEvent.vb

Defines the Transaction structure, which represents a bank account transaction such as a debit or credit. You will modify this source file in later exercises in this lab. Defines the TransactionEventArgs class, which you will use in Exercise 5 if time permits to represent account-related events. This source file is complete. You will not modify the code in this source file.

TransactionException.cs Defines the TransactionException class, which you will use in Exercise 4 if time permits to represent account-related exceptions. TransactionException.vb This source file is complete. You will not modify the code in this source file.

Task 2: Use a nullable type


1. 2. In Solution Explorer, double-click the Account source file. Note the following instance variables in the Account class: _name and _balance hold the name of the account holder and the current balance of the account.

Developing Applications by Using Types and Standard Contracts

1-3

3.

_mostRecentTransaction holds a Transaction structure object, which represents the most recent debit or credit for the account. _allTransactions holds a list of Transaction objects, which represent all of the credits and debits on the account.

Note the following methods in the Account class: The constructor sets the _name, _balance, and _mostRecentTransaction instance variables. _mostRecentTransaction is initialized with a dummy Transaction structure object; it is not possible to assign a null reference to _mostRecentTransaction because it is a value type, not a reference type. The Deposit and Withdraw methods credit an amount to or debit an amount from the balance. The methods then create a Transaction object to represent the credit or debit and add it to the list of all transactions. The methods also assign the Transaction object to the _mostRecentTransaction instance variable. The ToString method returns a string that describes the current state of the account. If no debits or credits have occurred, _mostRecentTransaction still contains the dummy Transaction object that the constructor assigned to it.

4.

Modify the declaration of the _mostRecentTransaction instance variable so that it is a nullable type. Your code should resemble the following examples.

[Visual C#] private Transaction? _mostRecentTransaction;

[Visual Basic] Private _mostRecentTransaction As Nullable(Of Transaction)

5.

Modify the constructor so that it sets _mostRecentTransaction to null (C#) or Nothing (Visual Basic). Your code should resemble the following examples.

[Visual C#] public Account(string name) { _name = name; _balance = 0; _mostRecentTransaction = null; }

[Visual Basic] Public Sub New(ByVal name As String) _name = name _balance = 0 _mostRecentTransaction = Nothing End Sub

6.

Modify the ToString method so that it tests whether _mostRecentTransaction has a value and returns one of the following strings:

1-4

Core Foundations of Microsoft .NET 2.0 Development

If _mostRecentTransaction has a value, return a string that contains the account name and balance plus the value of _mostRecentTransaction. Otherwise, return a string that contains the account name and balance plus a message such as no recent transaction.

Your code should resemble the following examples.


[Visual C#] public override string ToString() { if (_mostRecentTransaction.HasValue) { return string.Format( "{0}, balance: {1:c}, most recent transaction: {2}", _name, _balance, _mostRecentTransaction.Value); } else { return string.Format( "{0}, balance: {1:c}, no recent transaction.", _name, _balance); } }

[Visual Basic] Public Overrides Function ToString() As String If _mostRecentTransaction.HasValue Then Return String.Format( _ "{0}, balance: {1:c}, most recent transaction: {2}", _ _name, _ _balance, _ _mostRecentTransaction.Value) Else Return String.Format( _ "{0}, balance: {1:c}, no recent transaction.", _ _name, _ _balance) End If End Function

Task 3: Build and test the application


1. 2. 3. On the Build menu, click Rebuild Solution, and verify that no errors or warnings occur. On the Debug menu, click Start Without Debugging to start the application. In the Account Manager window, click Details. Verify that the Account details message box appears and displays a message such as John Doe, balance: $0.00, no recent transaction. This indicates that the _mostRecentTransaction instance variable for the account is a null reference. 4. 5. In the Account details dialog box, click OK. In the Account Manager window, in the Amount box, type a number, and then click Deposit. Type another number, and then click Withdraw.

Developing Applications by Using Types and Standard Contracts

1-5

6. 7.

Repeat the previous step several times to make several deposits and withdrawals on the account. Click Details. Verify that the Account details message box appears and displays a message that indicates the current balance and the most recent transaction. This indicates that the _mostRecentTransaction instance variable for the account contains a value.

8. 9.

In the Account details dialog box, click OK. Close the Account Manager window.

Exercise 2: Defining a Generic Type


Task 1: Define the Transaction structure as a generic type
1. 2. In Solution Explorer, double-click the Transaction source file to open the file in the code editor. Modify the definition of the Transaction structure so that it has a type argument that is named T. Your code should resemble the following examples.
[Visual C#] public struct Transaction<T>

[Visual Basic] Public Structure Transaction(Of T)

3.

In the Transaction structure, modify the _amount instance variable declaration so that its type is T rather than double (C#) or Double (Visual Basic). Your code should resemble the following examples.

[Visual C#] private T _amount;

[Visual Basic] Private _amount As T

4.

Modify the Transaction constructor so that its first parameter is of type T. Your code should resemble the following examples.

[Visual C#] public Transaction(T amt, DateTime ts)

[Visual Basic] Public Sub New(ByVal amt As T, ByVal ts As Date)

1-6

Core Foundations of Microsoft .NET 2.0 Development

Task 2: Use the generic Transaction structure in client code


1. 2. In Solution Explorer, double-click the Account source file to open the file. Locate the _mostRecentTransaction instance variable declaration. Currently, _mostRecentTransaction is declared as a nullable Transaction type. Modify the declaration so that the type is a nullable Transaction<double> (C#) or a nullable Transaction(Of Double) (Visual Basic) type. Your code should resemble the following examples.
[Visual C#] private Transaction<double>? _mostRecentTransaction;

[Visual Basic] Private _mostRecentTransaction As Nullable(Of Transaction(Of Double))

3.

Locate the _allTransactions instance variable declaration. Modify the declaration so that it uses Transaction<double> (C#) or Transaction(Of Double) (Visual Basic). Your code should resemble the following examples.

[Visual C#] private List<Transaction<double>> _allTransactions = new List<Transaction<double>>();

[Visual Basic] Private _allTransactions As New List(Of Transaction(Of Double))()

4.

Locate the Deposit method. Modify the code that creates a Transaction object so that it creates a Transaction<double> (C#) or Transaction(Of Double) (Visual Basic) object instead. Your code should resemble the following examples.

[Visual C#] Transaction<double> trans = new Transaction<double>(amount, DateTime.Now);

[Visual Basic] Dim trans As New Transaction(Of Double)(amount, Date.Now)

5.

Locate the Withdraw method, and then modify it in the same way as for the Deposit method. Your code should resemble the following examples.

[Visual C#] Transaction<double> trans = new Transaction<double>(-amount, DateTime.Now);

Developing Applications by Using Types and Standard Contracts

1-7

[Visual Basic] Dim trans As New Transaction(Of Double)(-amount, Date.Now)

6.

Locate the AllTransactions property. Modify the property declaration and implementation so that it returns a Transaction<double> (C#) or Transaction(Of Double) (Visual Basic) array. Your code should resemble the following examples.

[Visual C#] public Transaction<double>[] AllTransactions { get { Transaction<double>[] copy = _allTransactions.ToArray(); return copy; } }

[Visual Basic] Public ReadOnly Property AllTransactions() As Transaction(Of Double)() Get Dim copy As Transaction(Of Double)() = _ _allTransactions.ToArray() Return copy End Get End Property

7. 8.

In Solution Explorer, right-click the MainForm item, and then click View Code. Locate the DetailsButton_Click method. Modify the code that gets the account transactions and displays them in a list so that the code uses Transaction<double> (C#) or Transaction(Of Double) (Visual Basic). Your code should resemble the following examples.

[Visual C#] Transaction<double>[] transactions = _theAccount.AllTransactions; foreach (Transaction<double> trans in transactions) { string str = string.Format("{0}", trans); TransactionsListBox.Items.Add(str); }

[Visual Basic] Dim transactions As Transaction(Of Double)() = _ _theAccount.AllTransactions For Each trans As Transaction(Of Double) In transactions Dim str As String = String.Format("{0}", trans) TransactionsListBox.Items.Add(str) Next

Task 3: Define a constraint on the type argument for the Transaction structure
1. In Solution Explorer, double-click the Transaction source file to open the file.

1-8

Core Foundations of Microsoft .NET 2.0 Development

2.

Define a constraint on the T type argument so that it must implement the IComparable<T> (C#) or IComparable(Of T) (Visual Basic) interface. This constraint means that client code must supply a type argument that implements the generic IComparable interface. The client code in this application specifies double (C#) or Double (Visual Basic) as the type argument, which does implement the generic IComparable interface. Note: In the next exercise, you will enhance the Account class so that it sorts Transaction objects according to the amount of the transaction. To support this behavior, the data type for the transaction amount, T, must be a comparable data type. The type argument constraint ensures that this is the case. Your code should resemble the following examples.

[Visual C#] public struct Transaction<T> where T : IComparable<T>

[Visual Basic] Public Structure Transaction(Of T As IComparable(Of T))

Task 4: Build and test the application


1. 2.
3.

On the Build menu, click Rebuild Solution, and then verify that no errors or warnings occur. On the Debug menu, click Start Without Debugging to start the application. In the Account Manager window, in the Amount box, type a number, and then click Deposit. Type another number, and then click Withdraw. Click Details. Verify that the Account details message box appears and displays a message that indicates the current balance and the most recent transaction.

4.

5. 6.

In the Account details dialog box, click OK. Close the Account Manager window.

Exercise 3: Implementing Standard .NET Framework Interfaces


Task 1: Implement the IFormattable interface
1. 2. In Solution Explorer, double-click the Transaction source file to open the file. Modify the definition of the Transaction structure so that it implements the IFormattable interface. Your code should resemble the following examples.
[Visual C#] public struct Transaction<T> : IFormattable where T : IComparable<T>

Developing Applications by Using Types and Standard Contracts

1-9

[Visual Basic] Public Structure Transaction(Of T As IComparable(Of T)) Implements IFormattable

3.

In the Transaction structure, implement the ToString method for the IFormattable interface as follows: Important: Leave the existing ToString method that takes no parameters in place. Add this new ToString method after the existing implementation. If the format parameter is null (C#) or Nothing (Visual Basic), invoke the existing ToString method to return full details for the transaction object. If the format parameter is "d", return a string that contains the timestamp for the transaction object. If the format parameter is "a", return a string that contains the amount of the transaction object. If the format parameter is any other value, throw a FormatException exception to indicate that the client code has specified an invalid format for the transaction object.

Your code should resemble the following examples.


[Visual C#] public string ToString(string format, IFormatProvider formatProvider) { if (format == null) return this.ToString(); else if (format == "d") return string.Format("{0}", _timestamp.ToString()); else if (format == "a") return string.Format("{0:c}", _amount); else throw new FormatException( string.Format("Invalid format: '{0}'.", format)); }

[Visual Basic] Public Overloads Function ToString( _ ByVal format As String, _ ByVal formatProvider As System.IFormatProvider) As String _ Implements System.IFormattable.ToString If format Is Nothing Then Return Me.ToString() ElseIf format = "d" Then Return String.Format("{0}", _timestamp.ToString()) ElseIf format = "a" Then Return String.Format("{0:c}", _amount) Else Throw New FormatException( _ String.Format("Invalid format: '{0}'.", format)) End If End Function

1-10

Core Foundations of Microsoft .NET 2.0 Development

Task 2: Build and test the application


1. 2. 3. 4. On the Build menu, click Rebuild Solution, and then verify that no errors or warnings occur. On the Debug menu, click Start Without Debugging to start the application. In the Account Manager window, in the Amount box, type a number, and then click Deposit. Type another number, and then click Withdraw. Click Details. Verify that the Account details message box appears and displays a message that indicates the current balance and the most recent transaction, and then click OK to close the message box. In the Account Manager window, verify that the Transactions list displays the full details for each transaction object. 5. 6. 7. Close the Account Manager window. In Visual Studio, in Solution Explorer, right-click the MainForm item, and then click View Code. In the MainForm class, locate the DetailsButton_Click method, and then locate the statement that invokes the String.Format method to format a transaction object. Modify the statement so that it specifies "{0:d}" as the format string. Your code should resemble the following examples.
[Visual C#] string str = string.Format("{0:d}", trans);

[Visual Basic] Dim str As String = String.Format("{0:d}", trans)

8.

Repeat Steps 1 to 4 to test the application. Verify that the Transactions list displays only the timestamp for each transaction object.

9.

Close the Account Manager window.

10. Return to Visual Studio, and then modify the DetailsButton_Click method so that it specifies "{0:a}" as the format string for transaction objects. 11. Repeat Steps 1 to 4 to test the application. Verify that the Transactions list displays only the amount of each transaction object. 12. Close the Account Manager window.

Task 3: Implement the generic IComparable interface


1. 2. In Solution Explorer, double-click the Transaction source file to open the file. Modify the definition of the Transaction structure so that it implements the IComparable<Transaction<T>> (C#) or IComparable(Of Transaction(Of T)) (Visual Basic) interface. This interface enables transaction objects to be compared with each other. Your code should resemble the following examples.

Developing Applications by Using Types and Standard Contracts

1-11

[Visual C#] public struct Transaction<T> : IFormattable, IComparable<Transaction<T>> where T : IComparable<T>

[Visual Basic] Public Structure Transaction(Of T As IComparable(Of T)) Implements IFormattable, IComparable(Of Transaction(Of T))

3.

In the Transaction structure, implement the CompareTo method for the generic IComparable interface as follows: Get the _amount value for the current transaction object. Get the _amount value for the transaction object that is passed as a parameter into the CompareTo method. Compare the two _amount values by using the CompareTo method on those types.

Note: The data type of the _amount values, T, is guaranteed to define a CompareTo method because of the type argument constraint on the Transaction structure. Your code should resemble the following examples.
[Visual C#] public int CompareTo(Transaction<T> other) { return this._amount.CompareTo(other._amount); }

[Visual Basic] Public Function CompareTo( _ ByVal other As Transaction(Of T)) As Integer _ Implements System.IComparable(Of Transaction(Of T)).CompareTo Return Me._amount.CompareTo(other._amount) End Function

Task 4: Sort transaction objects in the client code


1.
2.

In Solution Explorer, double-click the Account source file to open the file. In the Account class, locate the AllTransactions property. Enhance the property so that, after it gets an array of transactions for the account, it sorts the array by invoking the Array.Sort method. Note: The Array.Sort method implicitly invokes the CompareTo method on the transaction objects in the array to sort the array elements into order. Your code should resemble the following examples.

[Visual C#] Transaction<double>[] copy = _allTransactions.ToArray(); Array.Sort(copy);

1-12

Core Foundations of Microsoft .NET 2.0 Development

return copy;

[Visual Basic] Dim copy As Transaction(Of Double)() = _allTransactions.ToArray() Array.Sort(copy) Return copy

Task 5: Build and test the application


1. 2. 3. 4. On the Build menu, click Rebuild Solution, and then verify that no errors or warnings occur. On the Debug menu, click Start Without Debugging to start the application. In the Account Manager window, in the Amount box, type a number, and then click Deposit. Type another number, and then click Withdraw. Click Details. Verify that the Account details message box appears and displays a message that indicates the current balance and the most recent transaction, and then click OK to close the message box. In the Account Manager window, verify that the Transactions list displays the transaction objects in order of ascending value. 5. Close the Account Manager window.

Exercise 4: Throwing and Catching Exceptions (If Time Permits)


Task 1: Review the TransactionException class
1. 2. In Solution Explorer, double-click the TransactionException source file to open the file. Note the following points about the TransactionException class: TransactionException inherits from Exception, as recommended for custom exception classes. TransactionException defines three constructors, as recommended for custom exception classes. The first constructor is a parameterless constructor. The second constructor takes a string message parameter. The third constructor takes a string parameter and an inner exception parameter.

Task 2: Throw a TransactionException exception


1. 2. In Solution Explorer, double-click the Account source file to open the file in the code editor. Locate the Withdraw method. At the start of the method, add code to test whether the balance is negative. If so, throw a TransactionException exception to indicate that the user cannot withdraw from an overdrawn account. Your code should resemble the following examples.
[Visual C#] public void Withdraw(double amount) { if (_balance < 0) {

Developing Applications by Using Types and Standard Contracts

1-13

string message = string.Format( "Cannot withdraw {0:c} from overdrawn account for {1}.", amount, _name); throw new TransactionException(message); } _balance -= amount;

[Visual Basic] Public Sub Withdraw(ByVal amount As Double) If _balance < 0 Then Dim message As String = String.Format( _ "Cannot withdraw {0:c} from overdrawn account for {1}.", _ amount, _name) Throw New TransactionException(message) End If _balance -= amount

Task 3: Catch TransactionException exceptions


1. 2. In Solution Explorer, right-click the MainForm item, and then click View Code. Locate the WithdrawButton_Click method. Modify the method so that it catches TransactionException exceptions that may occur when the user withdraws money from the account. In the catch handler, show a message box that displays the Message property of the exception object. Your code should resemble the following examples.
[Visual C#] double amount = double.Parse(AmountTextBox.Text); try { _theAccount.Withdraw(amount); } catch (TransactionException ex) { MessageBox.Show(ex.Message, "Error"); }

[Visual Basic] Dim amount As Double = Double.Parse(AmountTextBox.Text) Try _theAccount.Withdraw(amount) Catch ex As TransactionException MessageBox.Show(ex.Message, "Error") End Try

Task 4: Build and test the application


1. 2. 3. On the Build menu, click Rebuild Solution, and verify that no errors or warnings occur. On the Debug menu, click Start Without Debugging to start the application. In the Account Manager window, in the Amount box, type a number, and then click Withdraw.

1-14

Core Foundations of Microsoft .NET 2.0 Development

4.

Click Details. Verify that the Account details message box appears and indicates that the balance is now negative.

5.

Repeat Steps 3 and 4 to attempt to withdraw money from an overdrawn account. Verify that a message box appears that indicates that the user has attempted to withdraw from an overdrawn account.

6.

Close the Account Manager window.

Exercise 5: Raising and Handling Events (If Time Permits)


Task 1: Review the TransactionEventArgs class
1. 2. In Solution Explorer, double-click the TransactionEvent source file to open the file. Note the following points about the TransactionEventArgs class: TransactionEventArgs inherits from EventArgs. The _amount instance variable and the Amount property represent the amount of the transaction. You will be able to access this information when you handle transactionrelated events in client code.

Task 2: Define the LargeCredit and LargeDebit events in the Account class
1. 2. In Solution Explorer, double-click the Account source file to open the file. In the Account class, define public events that are named LargeCredit and LargeDebit. Specify the data type for both events as EventHandler<TransactionEventArgs> (C#) or EventHandler(Of TransactionEventArgs) (Visual Basic). Your code should resemble the following examples.
[Visual C#] public class Account { public event EventHandler<TransactionEventArgs> LargeCredit; public event EventHandler<TransactionEventArgs> LargeDebit;

[Visual Basic] Public Class Account Public Event LargeCredit As EventHandler(Of TransactionEventArgs) Public Event LargeDebit As EventHandler(Of TransactionEventArgs)

Task 3: Raise LargeCredit and LargeDebit events in the Account class


1. In the Account class, locate the Deposit method. At the end of the method, add code to test whether the deposit amount is $1,000 or more. If so, raise a LargeCredit event as follows: a. Create a TransactionEventArgs object that contains the amount of the credit.

b. Raise the LargeCredit event. Specify this (C#) or Me (Visual Basic) as the first parameter for the event, and specify the TransactionEventArgs object as the second parameter.

Developing Applications by Using Types and Standard Contracts

1-15

Note: If you are using Visual C#, you must test whether the LargeCredit event is a null reference. A null reference indicates that no handlers are registered for the event. In this case, you must not raise the event because it would cause a null reference exception at run time.

To avoid possible thread synchronization errors, you must work with a copy of the LargeCredit event object during these operations. Your code should resemble the following examples.
[Visual C#] public void Deposit(double amount) { if (amount >= 1000) { EventHandler<TransactionEventArgs> handler = LargeCredit; if (handler != null) { TransactionEventArgs args = new TransactionEventArgs(amount); handler(this, args); } } }

[Visual Basic] Public Sub Deposit(ByVal amount As Double) If (amount >= 1000) Then Dim args As New TransactionEventArgs(amount) RaiseEvent LargeCredit(Me, args) End If End Sub

2.

In the Account class, locate the Withdraw method. At the end of the method, add code to test whether the withdrawal amount withdrawn is $1,000 or more. If so, raise a LargeDebit event by following similar steps as for the LargeCredit event in the previous step. Your code should resemble the following examples.

[Visual C#] public void Withdraw(double amount) { if (amount >= 1000) { EventHandler<TransactionEventArgs> handler = LargeDebit; if (handler != null) { TransactionEventArgs args = new TransactionEventArgs(amount); handler(this, args); } } }

1-16

Core Foundations of Microsoft .NET 2.0 Development

[Visual Basic] Public Sub Withdraw(ByVal amount As Double) If (amount >= 1000) Then Dim args As New TransactionEventArgs(amount) RaiseEvent LargeDebit(Me, args) End If End Sub

Task 4: Handle LargeCredit and LargeDebit events in client code


1. 2. 3. In Solution Explorer, right-click the MainForm item, and then click View Code. In the MainForm class, near the end of the source file, locate the code that is enclosed in #if#endif (C#) or #If#End If (Visual Basic) conditional compilation directives. Remove the conditional compilation directives to reinstate the following methods contained inside the directives: OnLargeCredit is an event-handler method for LargeCredit events. The method writes a message to a text file to indicate that a large credit has occurred and indicates the amount of the credit. OnLargeDebit is an event-handler method for LargeDebit events. The method writes a message to a text file to indicate that a large debit has occurred and indicates the amount of the debit.

4.

Near the start of the source file, locate the MainForm_Load method. Add code to handle the LargeCredit and LargeDebit events on the _theAccount object as follows: Specify OnLargeCredit as the event-handler method for LargeCredit events. Specify OnLargeDebit as the event-handler method for LargeDebit events.

Your code should resemble the following examples.


[Visual C#] private void MainForm_Load(object sender, EventArgs e) { _theAccount.LargeCredit += OnLargeCredit; _theAccount.LargeDebit += OnLargeDebit; }

[Visual Basic] Private Sub MainForm_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles MyBase.Load AddHandler _theAccount.LargeCredit, AddressOf OnLargeCredit AddHandler _theAccount.LargeDebit, AddressOf OnLargeDebit End Sub

Task 5: Build and test the application


1. 2. On the Build menu, click Rebuild Solution, and then verify that no errors or warnings occur. On the Debug menu, click Start Without Debugging to start the application.

Developing Applications by Using Types and Standard Contracts

1-17

3. 4. 5. 6.

In the Account Manager window, in the Amount box, type 3000 and then click Deposit. In the Amount box, type 2000 and then click Withdraw. Close the Account Manager window. Verify that the application raised and handled the LargeCredit and LargeDebit events as follows: a. In Visual Studio, in Solution Explorer, click Show All Files.

b. Expand the bin folder, and then expand the Debug folder. Verify that the Debug folder contains a file that is named LargeTransactions.txt. c. 7. Double-click LargeTransactions.txt. Verify that the file contains two lines that indicate that a large credit of $3,000 and a large debit of $2,000 have occurred.

Close Visual Studio.

Managing Common Data by Using Collections

2-1

Module 2
Managing Common Data by Using Collections
Contents:
Lab Answer Keys 2

2-2

Core Foundations of Microsoft .NET 2.0 Development

Lab Answer Keys


Lab: Managing Common Data by Using Collections Exercise 1: Using the Dictionary Generic Collection
Task 1: Open the solution application
1. 2. 3. Click Start, point to All Programs, point to Microsoft Visual Studio 2005, and then click Microsoft Visual Studio 2005. In the Microsoft Visual Studio development system, on the File menu, point to Open, and then click Project/Solution. In the Open Project dialog box, browse to E:\Labfiles\Starter\CS\AccountManager (if you are using the Microsoft Visual C# development tool) or E:\Labfiles\Starter\VB\AccountManager (if you are using the Microsoft Visual Basic development system), click AccountManager.sln, and then click Open. In Solution Explorer, double-click the Account source file to view the source code for the Account class. Note the following members in the Account class: 6. 7. The _name and _balance instance variables hold the name of the account holder and the current balance. The Deposit and Withdraw methods credit and debit money in the account. The ToString method returns a string that describes the current state of the account.

4. 5.

In Solution Explorer, double-click the MainForm form to view the form in the Designer window. Note the following features in the form: The AccountsDataGridView control will display details for all of the accounts that the form manages. The first column will display account numbers, and the second column will display account details. The AddAccountButton control will create a new Account object, add it to a Dictionary collection of Account objects, and then display all of the accounts in the AccountsDataGridView control.

Task 2: Create and use a Dictionary collection


1. 2. In Solution Explorer, right-click the MainForm form, and then click View Code. In the MainForm class, locate the comment TODO: Define a Dictionary of accounts. Add code to define a Dictionary instance variable that is named _accounts, and assign a new Dictionary object to it. Specify the following type parameters for the Dictionary object: Key type: int (C#) or Integer (Visual Basic) Value type: Account

Your code should resemble the following examples.


[Visual C#]

Managing Common Data by Using Collections

2-3

private Dictionary<int, Account> _accounts = new Dictionary<int,Account>();

[Visual Basic] Private _accounts As New Dictionary(Of Integer, Account)()

3.

Locate the AddAccountButton_Click method, and then review the existing code. Where indicated by the TODO comment, write code to add the acc object to the dictionary. Specify _nextAccountNumber as the key for the new dictionary entry. Your code should resemble the following examples.

[Visual C#] _accounts.Add(_nextAccountNumber, acc);

[Visual Basic] _accounts.Add(_nextAccountNumber, acc)

4.

Locate the DisplayAccounts method. Where indicated by the TODO comment, write code to iterate through the entries in the dictionary. For each entry, add a row to the AccountsDataGridView control to display the account number in the first column and the account details in the second column. Tip: Invoke the AccountsDataGridView.Rows.Add method. Pass the dictionary entry key as the first parameter to display the account number in the first column. Pass the dictionary entry value as the second parameter to display the Account object details in the second column. Your code should resemble the following examples.

[Visual C#] foreach (KeyValuePair<int, Account> entry in _accounts) { // Add a (key, value) row to the DataGridView. AccountsDataGridView.Rows.Add(entry.Key, entry.Value); }

[Visual Basic] For Each entry As KeyValuePair(Of Integer, Account) In _accounts ' Add a (key, value) row to the DataGridView. AccountsDataGridView.Rows.Add(entry.Key, entry.Value) Next

5.

Locate the AccountsDataGridView_CellDoubleClick method, and then review the existing code. Where indicated by the TODO comment, modify the code to look up an Account object in the dictionary. Specify accNum as the key, and then assign the result of the look-up to the theAccount variable. Your code should resemble the following examples.
[Visual C#] Account theAccount = _accounts[accNum];

2-4

Core Foundations of Microsoft .NET 2.0 Development

[Visual Basic] Dim theAccount As Account = _accounts(accNum)

Task 3: Build and test the application


1. 2. 3. On the Build menu, click Rebuild Solution, and then verify that no errors or warnings occur. On the Debug menu, click Start Without Debugging to start the application. In the Account Manager window, in the Name box, type John Smith and then click Add Account. Verify that the grid view displays the account. Verify that the first column displays the account number 0, which is the key for the entry in the dictionary. Verify that the second column displays the details for the Account object. 4. Repeat the previous step to add an account for Jane Doe. Verify that the grid view displays two rows. The first row displays account number 0 and the details for John Smiths account. The second row displays account number 1 and the details for Jane Does account. 5. In the grid view, double-click the first row. Verify that an Account: John Smith dialog box appears. The dialog box enables you to deposit and withdraw money for John Smiths account. 6. Close the dialog box, and then close the Account Manager window.

Exercise 2: Using the List Generic Collection


Task 1: Define classes that implement the IComparer generic interface
1. 2. In Solution Explorer, double-click the TransactionComparers source file to open the file. Define a class that is named TransactionDateComparer that implements the IComparer<Transaction<double>> (C#) or IComparer(Of Transaction(Of Double)) (Visual Basic) interface. Your code should resemble the following examples
[Visual C#] public class TransactionDateComparer : IComparer<Transaction<double>> { }

[Visual Basic] Public Class TransactionDateComparer Implements IComparer(Of Transaction(Of Double)) End Class

3.

In the TransactionDateComparer class, implement the Compare method so that it compares the Timestamp property of two Transaction<double> (C#) or Transaction(Of Double) (Visual Basic) objects.

Managing Common Data by Using Collections

2-5

Your code should resemble the following examples.

[Visual C#] public int Compare(Transaction<double> x, Transaction<double> y) { // Compare the timestamps of the two objects. return x.TimeStamp.CompareTo(y.TimeStamp); }

[Visual Basic] Public Function Compare(ByVal x As Transaction(Of Double), _ ByVal y As Transaction(Of Double)) As Integer _ Implements IComparer(Of Transaction(Of Double)).Compare ' Compare the timestamps of the two objects. Return x.Timestamp.CompareTo(y.Timestamp) End Function

4.

Define a class that is named TransactionAmountComparer that implements the IComparer<Transaction<double>> (C#) or IComparer(Of Transaction(Of Double)) (Visual Basic) interface. Your code should resemble the following examples.

[Visual C#] public class TransactionAmountComparer : IComparer<Transaction<double>> { }

[Visual Basic] Public Class TransactionAmountComparer Implements IComparer(Of Transaction(Of Double)) End Class

5.

In the TransactionAmountComparer class, implement the Compare method so that it compares the Amount property of two Transaction<double> (C#) or Transaction(Of Double) (Visual Basic) objects. Your code should resemble the following examples.

[Visual C#] public int Compare(Transaction<double> x, Transaction<double> y) { // Compare the amounts of the two objects. return x.Amount.CompareTo(y.Amount); }

[Visual Basic] Public Function Compare(ByVal x As Transaction(Of Double), _ ByVal y As Transaction(Of Double)) As Integer _ Implements IComparer(Of Transaction(Of Double)).Compare ' Compare the amounts of the two objects. Return x.Amount.CompareTo(y.Amount) End Function

2-6

Core Foundations of Microsoft .NET 2.0 Development

Task 2: Sort objects in a List collection by using the IComparer implementation


classes
1. 2. In Solution Explorer, double-click the Account source file to open the file. Near the end of the Account class, locate the GetTransactionsByDate method. The purpose of this method is to populate its three List parameters as follows: Populate all with a list of all transactions sorted by date. Populate deposits with a list of deposit transactions sorted by date. Populate withdrawals with a list of withdrawal transactions sorted by date.

Locate the statement that assigns the all parameter. Currently, the statement assigns an empty list. Modify the statement so that it creates a new list that is a copy of the _allTransactions instance variable. Your code should resemble the following examples.
[Visual C#] all = new List<Transaction<double>>(_allTransactions);

[Visual Basic] all = New List(Of Transaction(Of Double))(_allTransactions)

3.

Add a statement to sort the all list by transaction date. To do this, invoke the Sort method on the all object, and then pass a TransactionDateComparer object as a parameter. Your code should resemble the following examples.

[Visual C#] all.Sort(new TransactionDateComparer());

[Visual Basic] all.Sort(New TransactionDateComparer())

4.

Locate the GetTransactionsByAmount method. The purpose of this method is similar to the purpose of the GetTransactionsByDate method, except that it will sort transactions by amount. Modify the statement that assigns the all parameter so that it creates a new list that is a copy of the _allTransactions instance variable. Your code should resemble the following examples.

5.

[Visual C#] all = new List<Transaction<double>>(_allTransactions);

[Visual Basic] all = New List(Of Transaction(Of Double))(_allTransactions)

Managing Common Data by Using Collections

2-7

6.

Add a statement to sort the all list by transaction amount. To do this, invoke the Sort method on the all object, and then pass a TransactionAmountComparer object as a parameter. Your code should resemble the following examples.

[Visual C#] all.Sort(new TransactionAmountComparer());

[Visual Basic] all.Sort(New TransactionAmountComparer())

Task 3: Build and test the application


1. 2. 3. On the Build menu, click Rebuild Solution, and then verify that no errors or warnings occur. On the Debug menu, click Start Without Debugging to start the application. In the Account Manager window, in the Name box, type John Smith and then click Add Account. Verify that the first row in the grid view displays the account. 4. 5. 6. 7. 8. In the grid view, double-click the first row. In the Account: John Smith dialog box, in the Amount box, type 2000 and then click Deposit. Repeat the previous step several times to deposit several sums into the account. Repeat the previous step several times but click Withdraw instead of Deposit to withdraw several sums from the account. Click Sort by Date, and then click Details. A message box displays the current balance and the most recent transaction. Click OK to close the message box. In the Account: John Smith dialog box, verify that the All Transactions list displays all of the transactions sorted by date. 9. Click Sort by Amount, and then click Details. A message box displays the current balance and the most recent transaction. Click OK to close the message box. In the Account: John Smith dialog box, verify that the All Transactions list displays all of the transactions sorted by amount. The withdrawals appear first in the list because they represent negative transactions. 10. Close the dialog box, and then close the Account Manager window.

Task 4: Find objects in a List collection by using predicates


1. Near the end of the Account class, locate the comment TODO: Define IsDeposit method. Implement the IsDeposit method so that it takes a Transaction<double> (C#) or Transaction(Of Double) (Visual Basic) object and returns a Boolean value to indicate whether the amount of the transaction is zero or more. Your code should resemble the following examples.

2-8

Core Foundations of Microsoft .NET 2.0 Development

[Visual C#] private bool IsDeposit(Transaction<double> trans) { return trans.Amount >= 0; }

[Visual Basic] Private Function IsDeposit(ByVal trans As Transaction(Of Double)) As Boolean Return trans.Amount >= 0 End Function

2.

Locate the comment TODO: Define IsWithdrawal method. Implement the IsWithdrawal method so that it takes a Transaction<double> (C#) or Transaction(Of Double) (Visual Basic) object and returns a Boolean value to indicate whether the amount of the transaction is less than zero. Your code should resemble the following examples.

[Visual C#] private bool IsWithdrawal(Transaction<double> trans) { return trans.Amount < 0; }

[Visual Basic] Private Function IsWithdrawal(ByVal trans As Transaction(Of Double)) As Boolean Return trans.Amount < 0 End Function

3.

Locate the GetTransactionsByDate method. Modify the method as follows: a. Locate the statement that assigns an empty list to the deposits parameter, and then modify the statement so that it assigns a list of deposit transactions instead. To do this, invoke the FindAll method on the all object, and then specify the IsDeposit method as the predicate parameter.

b. Locate the statement that assigns an empty list to the withdrawals parameter, and then modify the statement so that it assigns a list of withdrawal transactions instead. To do this, invoke the FindAll method on the all object, and then specify the IsWithdrawal method as the predicate parameter. Your code should resemble the following examples.
[Visual C#] deposits = all.FindAll(IsDeposit); withdrawals = all.FindAll(IsWithdrawal);

[Visual Basic] deposits = all.FindAll(AddressOf IsDeposit) withdrawals = all.FindAll(AddressOf IsWithdrawal)

Managing Common Data by Using Collections

2-9

4.

Locate the GetTransactionsByAmount method, and then modify it in the same way that you modified the GetTransactionsByDate method in the previous step. Your code should resemble the following examples.

[Visual C#] deposits = all.FindAll(IsDeposit); withdrawals = all.FindAll(IsWithdrawal);

[Visual Basic] deposits = all.FindAll(AddressOf IsDeposit) withdrawals = all.FindAll(AddressOf IsWithdrawal)

Task 5: Build and test the application


1. 2. 3. On the Build menu, click Rebuild Solution, and then verify that no errors or warnings occur. On the Debug menu, click Start Without Debugging to start the application. In the Account Manager window, in the Name box, type John Smith and then click Add Account. Verify that the first row in the grid view displays the account. 4. 5. 6. 7. In the grid view, double-click the first row. In the Account: John Smith dialog box, in the Amount box, type 2000 and then click Deposit. Repeat the previous step several times to deposit several sums into the account. Repeat the previous step several times but click Withdraw instead of Deposit to withdraw several sums from the account. 8. Click Sort by Date, and then click Details. A message displays the current balance and the most recent transaction. Click OK to close the message box. In the Account: John Smith dialog box, verify that the All Transactions list displays all of the transactions sorted by date. Also, verify that the Deposits list displays deposit transactions sorted by date and the Withdrawals list displays withdrawal transactions sorted by date. 9. Click Sort by Amount, and then click Details. A message box displays the current balance and the most recent transaction. Click OK to close the message box. In the Account: John Smith dialog box, verify that the All Transactions list displays all of the transactions sorted by amount. Also, verify that the Deposits list displays deposit transactions sorted by amount and the Withdrawals list displays withdrawal transactions sorted by amount. The larger withdrawals will appear first in the list because they represent more negative transactions. 10. Close the dialog box, and then close the Account Manager window.

2-10

Core Foundations of Microsoft .NET 2.0 Development

Exercise 3: Using the NameValueCollection Specialized Collection


Task 1: Create and initialize a NameValueCollection collection
1. 2. In Solution Explorer, double-click the Account source file to open the file. At the top of the source file, add a statement to import the System.Collections.Specialized namespace. Your code should resemble the following examples.
[Visual C#] using System.Collections.Specialized;

[Visual Basic] Imports System.Collections.Specialized

3.

In the Account class, locate the comment TODO: Define a NameValueCollection instance variable. Add a statement to declare a NameValueCollection instance variable that is named _notes, and then assign an empty NameValueCollection object to it. Your code should resemble the following examples.

[Visual C#] private NameValueCollection _notes = new NameValueCollection();

[Visual Basic] Private _notes As New NameValueCollection()

4.

In the Account constructor, locate the comment TODO: Add keys to the _notes collection. Write code to add the following three keys to the _notes collection (specify a null reference as the value for each key): Telephone notes E-mail notes Letter notes

Your code should resemble the following examples.


[Visual C#] _notes.Add("Telephone notes", null); _notes.Add("E-mail notes", null); _notes.Add("Letter notes", null);

[Visual Basic] _notes.Add("Telephone notes", Nothing) _notes.Add("E-mail notes", Nothing) _notes.Add("Letter notes", Nothing)

5.

Locate the comment TODO: Define a property to get _notes. Write code to define a readonly property that is named Notes that returns the _notes collection.

Managing Common Data by Using Collections

2-11

Your code should resemble the following examples.


[Visual C#] public NameValueCollection Notes { get { return _notes; } }

[Visual Basic] Public ReadOnly Property Notes() As NameValueCollection Get Return _notes End Get End Property

Task 2: Display the keys for a NameValueCollection collection


1. In Solution Explorer, double-click the NotesForm form to view the form in the Designer window. The NotesForm form will enable the user to view and add notes for a particular account. 2. Note the following features in the form: 3. The KeysComboBox control will display the keys for the Notes collection for an account. The keys are Telephone notes, E-mail notes, and Letter notes. The AddButton control will add another value to the currently selected key in the Notes collection. The NotesListBox control will display all of the values for the currently selected key in the Notes collection.

On the View menu, click Code to view the code for the NotesForm form. Note that the NotesForm class has an instance variable that is named _theAccount, which identifies an Account object. The form will display and edit the Notes collection for this Account object.

4.

In the constructor, locate the comment TODO: Display the Notes keys in the combo box. Add a statement to get all of the keys for the _theAccount.Notes collection, and then bind the keys to the KeysComboBox control. To do this, set the KeysComboBox.DataSource property. Your code should resemble the following examples.
[Visual C#] KeysComboBox.DataSource = _theAccount.Notes.AllKeys;

[Visual Basic] KeysComboBox.DataSource = _theAccount.Notes.AllKeys

Task 3: Add and display values for an entry in a NameValueCollection collection


1. Locate the AddButton_Click method, and then review the existing code. Where indicated by the comment TODO: Add value to key, add a statement to add a value to the key in the

2-12

Core Foundations of Microsoft .NET 2.0 Development

_theAccount.Notes collection. Specify the key local variable as the key and the noteValue local variable as the value. Your code should resemble the following examples.
[Visual C#] _theAccount.Notes.Add(key, noteValue);

[Visual Basic] _theAccount.Notes.Add(key, noteValue)

2.

Locate the DisplayNotes method, and then review the existing code. Locate the comment TODO: Get values for key, and then review the statement that initializes the noteValues local variable. Currently, the statement assigns an empty string array. Modify the statement so that it gets all of the values for the currently selected key in the _theAccount.Notes collection. Your code should resemble the following examples.

[Visual C#] string[] noteValues = _theAccount.Notes.GetValues(key);

[Visual Basic] Dim noteValues As String() = _theAccount.Notes.GetValues(key)

Task 4: Build and test the application


1. 2. 3. On the Build menu, click Rebuild Solution, and then verify that no errors or warnings occur. On the Debug menu, click Start Without Debugging to start the application. In the Account Manager window, in the Name box, type John Smith and then click Add Account. Verify that the first row in the grid view displays the account. 4. 5. 6. In the grid view, double-click the first row. In the Account: John Smith dialog box, click Notes to view the notes for the account. In the Notes for Account: John Smith dialog box, test that the application enables you to add and view notes as follows: a. Verify that the Notes Categories combo box displays the following keys for the notes collection: Telephone notes, E-mail notes, and Letter notes.

b. In the New Note box, type This is telephone note 1 and then click Add. c. Verify that the Notes list displays the note that you just added.

d. Repeat the previous two steps to add a telephone note that says This is telephone note 2. Verify that the Notes list displays both of the values that you have added. e. f. In the Notes Categories combo box, select E-mail notes. Repeat Steps b to d above to add various e-mail notes. Verify that the Notes list displays the e-mail notes that you have added.

Managing Common Data by Using Collections

2-13

g. In the Notes Categories combo box, select Letter notes. h. 7. 8. Repeat Steps b to d above to add various letter notes. Verify that the Notes list displays the letter notes that you have added.

Close the Notes for Account: John Smith dialog box, close the Account: John Smith dialog box, and then close the Account Manager window. Close Visual Studio.

Deploying and Configuring Assemblies

3-1

Module 3
Deploying and Configuring Assemblies
Contents:
Lab Answer Keys 2

3-2

Core Foundations of Microsoft .NET 2.0 Development

Lab Answer Keys


Lab: Deploying and Configuring Assemblies Exercise 1: Managing the Configuration Settings of an Assembly
Task 1: Open the solution application
1. 2. Click Start, point to All Programs, point to Microsoft Visual Studio 2005, and then click Microsoft Visual Studio 2005. Open the OrdersApplication Windows Forms application in the E:\Labfiles\Starter\CS\OrdersApplication folder (if you are using the Microsoft Visual C# development tool) or the E:\Labfiles\Starter\VB\OrdersApplication folder (if you are using the Microsoft Visual Basic development system). Build and run the application. 4. 5. 6. 7. On the Debug menu, click Start Without Debugging.

3.

In the Main form, in the Order ID box, type an integer, and then click Add Order. In the Create Order form, select one or more items in the list box, and then click Add Item(s). Verify that the total number of items for the order corresponds to the number of items that are selected in the list box. Close the application.

Task 2: Add an application setting to the application


1. Add an application configuration file to the application. a. In Solution Explorer, click the OrdersApplication project.

b. On the Project menu, click Add New Item. c. 2. 3. In the Add New Item OrdersApplication dialog box, in the Templates: list box, click Application Configuration File, and then click Add.

In the application configuration file, create an appSettings element as a child of the configuration element. In the appSettings element, create an add element that contains the following attributes: key="LastOrderAdded" value="N/A"

Your code should resemble the following example.


<appSettings> <add key="LastOrderAdded" value="N/A"/> </appSettings>

Task 3: Write code to read the application setting


1. In Solution Explorer, add a reference to the System.Configuration.dll assembly. a. Right-click the OrdersApplication project and then click Add Reference.

Deploying and Configuring Assemblies

3-3

b. In the Add Reference dialog box, click the .NET tab. c. 2. 3. Click the System.Configuration assembly, and then click OK.

In Solution Explorer, open the code editor window for the Main form. In the code editor window, add a using (Visual C#) or Imports (Visual Basic) statement for the System.Configuration namespace. Your code should resemble the following examples.

[Visual C#] using System.Configuration;

[Visual Basic] Imports System.Configuration

4. 5. 6.

In the code editor window, locate the Click event handler for the AddOrderButton control. In the Click event handler, locate the comment TODO: Display last order added. Underneath the comment, write code to display the content of the LastOrderAdded application setting in the LastOrderLabel control on the Main form. Your code should resemble the following examples.

[Visual C#] LastOrderLabel.Text = ConfigurationManager.AppSettings["LastOrderAdded"];

[Visual Basic] LastOrderLabel.Text = ConfigurationManager.AppSettings("LastOrderAdded")

Task 4: Write code to edit the application setting


1.
2.

In the Click event handler for the AddOrderButton control, locate the comment TODO: Save last order added in configuration file. Underneath the comment, write code to perform the following actions: a. Create a Configuration variable, and then use the OpenExeConfiguration method to access the application configuration file.

b. Create an AppSettingsSection variable, and then initialize it with the content of the orderConfig.AppSettings property. c. Set the value of the LastOrderAdded application setting to the current value in the OrderIdTextBox control.

d. Save the updated configuration. e. Refresh the application settings section of the configuration.

Your code should resemble the following examples.


[Visual C#] Configuration orderConfig = ConfigurationManager.OpenExeConfiguration (ConfigurationUserLevel.None);

3-4

Core Foundations of Microsoft .NET 2.0 Development

AppSettingsSection appSection = orderConfig.AppSettings; int lastOrder = id; appSection.Settings["LastOrderAdded"].Value = lastOrder.ToString(); orderConfig.Save(); ConfigurationManager.RefreshSection("appSettings");

[Visual Basic] Dim orderConfig As Configuration = ConfigurationManager.OpenExeConfiguration _ (ConfigurationUserLevel.None) Dim appSection As AppSettingsSection = orderConfig.AppSettings Dim lastOrder As Integer = id appSection.Settings("LastOrderAdded").Value = lastOrder.ToString orderConfig.Save() ConfigurationManager.RefreshSection("appSettings")

3. 4. 5. 6. 7. 8.

Build and run the application. In the Main form, in the Order ID box, type an integer, and then click Add Order. Close the Create Order form. Verify that the number of the last order that was added is displayed in the label on the Main form. Repeat Steps 4 to 6 by using a different order number. Close the application.

Exercise 2: Deploying an Application by Using Windows Installer


Task 1: Create an Installer class and override the Install and Uninstall methods
1. In the OrdersApplication project, add a new Installer Class item that is named LogInstaller. a. In Solution Explorer, right-click the OrdersApplication project, point to Add, and then click New Item.

b. In the Add New Item dialog box, click Installer Class. c. 2. 3. 4. In the Name box, type LogInstaller.cs or LogInstaller.vb and then click Add.

Open the code editor window for the LogInstaller class. Examine the code in the LogInstaller class. If you are using Visual C#, in the code editor window for the LogInstaller class, add a using statement for the System.Diagnostics namespace. Your code should resemble the following code example.

[Visual C#] Using System.Diagnostics;

5.

Add the code in the following examples to the LogInstaller class. This code creates an event log source as part of the install process and deletes the event log source as part of the uninstall process.

Deploying and Configuring Assemblies

3-5

Note: For more information about event logs, see Module 4, Monitoring and Debugging Applications, in Course 2956: Core Foundations of Microsoft .NET 2.0 Development.

[Visual C#] public override void Install(System.Collections.IDictionary stateSaver) { base.Install(stateSaver); System.Diagnostics.EventLog.CreateEventSource("OrdersSource", "Orders Log"); } public override void Uninstall(System.Collections.IDictionary savedState) { base.Uninstall(savedState); System.Diagnostics.EventLog.DeleteEventSource("OrdersSource"); }

[Visual Basic] Public Overrides Sub Install(ByVal stateSaver As System.Collections.IDictionary) MyBase.Install(stateSaver) EventLog.CreateEventSource("OrdersSource", "Orders Log") End Sub Public Overrides Sub Uninstall(ByVal savedState As System.Collections.IDictionary) MyBase.Uninstall(savedState) EventLog.DeleteEventSource("OrdersSource") End Sub 6.

Save the OrdersApplication project.

Task 2: Create a Windows Installer project


1. 2. 3. Change the configuration of the OrdersApplication project from Debug to Release. Build the OrdersApplication project. In the OrdersApplication solution, add a setup project that is named Orders Setup. a. In Solution Explorer, right-click the solution, point to Add, and then click New Project.

b. In the Project Types pane, expand Other Project Types, and then click Setup and Deployment. c. In the Templates pane, click Setup Project.

d. In the Name box, type Orders Setup e. In the Location box, type E:\Labfiles\Starter\CS\OrdersApplication (if you are using Visual C#) or E:\Labfiles\Starter\VB\OrdersApplication (if you are using Visual Basic), and then click OK.

4.

In the File System editor, add the primary output for the OrdersApplication project to the Application folder. a. In the File System editor, right-click the Application Folder folder, point to Add, and then click Project Output.

b. In the Add Project Output Group dialog box, click Primary output, and then click OK.

3-6

Core Foundations of Microsoft .NET 2.0 Development

Task 3: Add a custom action to the setup project


1. 2. 3. 4. In Solution Explorer, right click the setup project, point to View, and then click Custom Actions. In the Custom Actions editor, right-click the Install folder, and then click Add Custom Action. In the Select Item in Project dialog box, open the Application Folder folder, click Primary output from OrdersApplication (Active), and then click OK. In the Custom Actions editor, right-click the Uninstall folder, and then click Add Custom Action. In the Select Item in Project dialog box, open the Application Folder folder, click Primary output from OrdersApplication (Active), and then click OK. Note: The custom actions configure the setup project to ensure that your custom installer class runs as part of the install and uninstall processes.

Task 4: Customize the installation


1. In the File System editor, add a shortcut for the OrdersApplication project to the userss Desktop folder. a. In the File System editor, click the Users Desktop folder to open the folder.

b. In the File System editor, in the right pane, right-click anywhere in the pane, and then click Create New Shortcut. c. In the Select Item in Project dialog box, open the Application Folder folder, click Primary output from OrdersApplication (Active), and then click OK.

d. In the Properties window, change the name of the shortcut to Orders Application. 2. Save and build the OrdersApplication solution. a. On the File menu, click Save All.

b. On the Build menu, click Build Orders Setup. 3. Verify that the project builds successfully.

Task 5: Run the installation program


1. 2. In Solution Explorer, right-click the Orders Setup project, and then click Install. Follow the setup program, accept the default options, wait for the deployment to finish, and then click Close.

Task 6: Verify the installation


1. 2. 3. 4. 5. In Visual Studio 2005, open Server Explorer. In Server Explorer, expand Servers, and then expand your local server. Right-click Event Logs, and then click Refresh. Expand Event Logs, and then verify that a new log that is named Orders Log exists. Minimize all windows, and then verify that there is a shortcut to your application on the desktop.

Deploying and Configuring Assemblies

3-7

6. 7. 8.

Double-click the shortcut to start the OrderApplication application. Verify that the application runs correctly. Exit the application.

Monitoring and Debugging Applications

4-1

Module 4
Monitoring and Debugging Applications
Contents:
Lab Answer Keys 2

4-2

Core Foundations of Microsoft .NET 2.0 Development

Lab Answer Keys


Lab: Monitoring and Debugging Applications Exercise 1: Monitoring Application Performance
Task 1: Open the solution application
1. 2. Click Start, point to All Programs, point to Microsoft Visual Studio 2005, and then click Microsoft Visual Studio 2005. Open the OrdersApplication Windows Forms application in the E:\Labfiles\Starter\CS\OrdersApplication folder (if you are using the Microsoft Visual C# development tool) or the E:\Labfiles\Starter\VB\OrdersApplication folder (if you are using the Microsoft Visual Basic development system). Build and run the application. In the Main form, in the Order ID box, type an integer, and then click Add Order. In the Create Order form, select one or more items in the list box, and then click Add Item(s). Verify that the total number of items for the order corresponds to the number of items that are selected in the list box. Close the application.

3. 4. 5. 6. 7.

Task 2: Create a custom performance counter


1. In the Main form, create a Load event handler for the form. a. Display the Main form in the designer window

b. In the toolbar for the Properties window, click the Events button to display the events for the form. c. 2. In the Properties window, double-click the Load event.

If you are using Visual C#, in the code editor window for the Main class, add a using statement for the System.Diagnostics namespace. Your code should resemble the following example.

[Visual C#] using System.Diagnostics;

3.

In the Load event handler, write code to create a custom performance counter category that contains two counters. Use the information in the following table when you write the code.

Property
Category name Category description Category type Counter 1 name

Value
Order Tracking Order information Single instance ActiveOrders

Monitoring and Debugging Applications

4-3

Property
Counter 1 description Counter 1 type Counter 2 name Counter 2 description Counter 2 type

Value
Total number of active orders NumberOfItems64 OrderItems Total of items for an order NumberOfItems64

Note: If you are using Visual C#, you must add a using statement for the System.Diagnostics namespace to the code editor window of the form. 4. At the start of the Load event handler, write code to delete the Order Tracking performance counter category if it exists. Note: In a production application, it is good practice to create the custom counter when you install the application. For the purposes of this exercise, the counter is created each time that the application starts. Your code should resemble the following examples.
[Visual C#] if(PerformanceCounterCategory.Exists("Order Tracking")) { PerformanceCounterCategory.Delete("Order Tracking"); } CounterCreationDataCollection counterCollection = new CounterCreationDataCollection(); CounterCreationData c1 = new CounterCreationData(); CounterCreationData c2 = new CounterCreationData(); c1.CounterName = "ActiveOrders"; c1.CounterHelp = "Total number of active orders"; c1.CounterType = PerformanceCounterType.NumberOfItems64; c2.CounterName = "OrderItems"; c2.CounterHelp = "Total of items for an order"; c2.CounterType = PerformanceCounterType.NumberOfItems64; counterCollection.Add(c1); counterCollection.Add(c2); PerformanceCounterCategory.Create("Order Tracking", "Order information", PerformanceCounterCategoryType.SingleInstance, counterCollection);

[Visual Basic] If PerformanceCounterCategory.Exists("Order Tracking") Then PerformanceCounterCategory.Delete("Order Tracking") End If Dim counterCollection As New CounterCreationDataCollection() Dim c1 As New CounterCreationData() Dim c2 As New CounterCreationData() c1.CounterName = "ActiveOrders" c1.CounterHelp = "Total number of active orders" c1.CounterType = PerformanceCounterType.NumberOfItems64 c2.CounterName = "OrderItems" c2.CounterHelp = "Total of items for an order" c2.CounterType = PerformanceCounterType.NumberOfItems64

4-4

Core Foundations of Microsoft .NET 2.0 Development

counterCollection.Add(c1) counterCollection.Add(c2) PerformanceCounterCategory.Create("Order Tracking", "Order information", _ PerformanceCounterCategoryType.SingleInstance, counterCollection)

Task 3: Read from and write to the custom performance counter


1. 2. In Solution Explorer, open the Order class. If you are using Visual C#, in the code editor window for the Order class, add a using statement for the System.Diagnostics namespace. Your code should resemble the following example.
[Visual C#] using System.Diagnostics;

3.

In the class, create a new static (Visual C#) or Shared (Visual Basic) class-level integer variable that is named _activeOrders. Your code should resemble the following examples.

[Visual C#] private static int _activeOrders;

[Visual Basic] Private Shared _activeOrders As Integer

4.

In the constructor for the class, add code to perform the following actions: a. Create a new performance counter instance that is named activeOrderCounter.

b. Set the category name of the new counter to Order Tracking. c. Set the name of the new counter to ActiveOrders.

d. Make the new counter writable. e. f. Increment the new counter by 1. Set the value of the _activeOrders variable to the value of the new counter.

Your code should resemble the following examples.


[Visual C#] PerformanceCounter activeOrderCounter = new PerformanceCounter(); activeOrderCounter.CategoryName = "Order Tracking"; activeOrderCounter.CounterName = "ActiveOrders"; activeOrderCounter.ReadOnly = false; activeOrderCounter.Increment(); _activeOrders = Convert.ToInt32(activeOrderCounter.RawValue);

[Visual Basic] Dim activeOrderCounter As New PerformanceCounter activeOrderCounter.CategoryName = "Order Tracking" activeOrderCounter.CounterName = "ActiveOrders" activeOrderCounter.ReadOnly = False activeOrderCounter.Increment()

Monitoring and Debugging Applications

4-5

_activeOrders = activeOrderCounter.RawValue

5.

In the Order class, create a new static (Visual C#) or Shared (Visual Basic) integer read-only property that is named ActiveOrders that retrieves the value of the _activeOrders variable. Your code should resemble the following examples.

[Visual C#] public static int ActiveOrders { get { return _activeOrders; } }

[Visual Basic] Public Shared ReadOnly Property ActiveOrders() As Integer Get Return _activeOrders End Get End Property

6. 7.

In the CreateOrder form, locate the Load event procedure. In the Load event procedure, add code to display the value of the ActiveOrders property in the activeOrdersLabel control. Your code should resemble the following examples.

[Visual C#] activeOrdersLabel.Text = Order.ActiveOrders.ToString();

[Visual Basic] activeOrdersLabel.Text = Order.ActiveOrders.ToString()

8.

Save and build the application.

Task 4: Test the custom counter


1. 2. 3. 4. 5. 6. 7. Run the application. In the Main form, in the Order ID box, type an integer, and then click Add Order. In the Create Order form, verify that the total number of active orders is set to 1. Close the Create Order form. In the Main form, in the Order ID box, type a different integer, and then click Add Order. In the Create Order form, verify that the total number of active orders is now set to 2. Close the application.

Task 5: Review the custom counter by using the Performance utility


1. 2. In Control Panel, in the Administrative Tools window, open the Performance utility. In the Performance utility, in the System Monitor tool, remove the existing counters.

4-6

Core Foundations of Microsoft .NET 2.0 Development

Note: To remove an existing counter, select the counter, and then press DELETE. 3. 4. 5. 6. 7. 8. 9. In the System Monitor tool, click Add to add a new instance of the ActiveOrders performance counter. In the Add Counters dialog box, in the Performance object list, click Order Tracking. In the Select counters from list list, click ActiveOrders, click Add, and then click Close. In the System Monitor tool, click Properties to change the vertical scale to a maximum of 20. In the System Monitor Properties dialog box, on the Graph tab, in the Maximum box, type 20 and then click OK. In Visual Studio 2005, run the OrdersApplication solution. Switch to the System Monitor tool, and then verify that the value of the performance counter is 0.

10. In the Main form, in the Order ID box, type an integer, and then click Add Order. 11. In the System Monitor tool, verify that the value of the counter increases. 12. In the application, create at least one more order, and then in the System Monitor tool, verify that the value of the counter increases for each new order. 13. Close the solution, and then close the Performance utility.

Exercise 2: Logging Information in a Custom Event Log


Task 1: Create a custom event log
1. 2. In Solution Explorer, open the code editor window for the Main form. In the Load event handler for the Main form, add code to perform the following actions: a. Delete the event source that is named OrdersApplicationSource if it exists.

b. Create an event source that is named OrdersApplicationSource with a log name of OrdersEventLog. Your code should resemble the following examples.
[Visual C#] if (EventLog.SourceExists("OrdersApplicationSource")) { EventLog.DeleteEventSource("OrdersApplicationSource"); } EventLog.CreateEventSource("OrdersApplicationSource", "OrdersEventLog");

[Visual Basic] If EventLog.SourceExists("OrdersApplicationSource") Then EventLog.DeleteEventSource("OrdersApplicationSource") End If EventLog.CreateEventSource("OrdersApplicationSource", "OrdersEventLog")

Task 2: Log information from a performance counter in a custom event log


1. In the Main form, create an event handler for the FormClosing event.

Monitoring and Debugging Applications

4-7

a.

Display the Main form in the designer window

b. In the toolbar for the Properties window, click the Events button to display the c. events for the form.

d. In the Properties window, double-click the FormClosing event. 2. In the FormClosing event handler, add code to perform the following actions: a. Create a new instance of an event log.

b. Set the source of the new event log to OrdersApplicationSource. c. Write an entry to the log that contains the text Active Orders: followed by the current number of active orders in the application.

d. Close the event log. Your code should resemble the following examples.
[Visual C#] EventLog orderLog = new EventLog(); orderLog.Source = "OrdersApplicationSource"; orderLog.WriteEntry("Active orders: " + Order.ActiveOrders.ToString()); orderLog.Close();

[Visual Basic] Dim orderLog As New EventLog orderLog.Source = "OrdersApplicationSource" orderLog.WriteEntry("Active orders: " + Order.ActiveOrders.ToString()) orderLog.Close()

3. 4. 5. 6. 7. 8. 9.

Save and build the application. Run the application. In the Main form, in the Order ID box, type an integer, and then click Add Order. In the Create Order form, verify that the total number of active orders is set to 1. Close the Create Order form. In the Main form, in the Order ID box, type a different integer, and then click Add Order. In the Create Order form, verify that the total number of active orders is now set to 2.

10. Close the application.

Task 3: View the content of the custom event log


1. 2. 3. 4. In Server Explorer, expand Servers, expand your server, and then expand Event Logs. In Server Explorer, right-click Event Logs, and then click Refresh. In the list of event logs, expand OrdersEventLog, expand OrdersApplicationSource, and then view the details of the event log entry. Verify that the number of active orders is stored in the event log.

4-8

Core Foundations of Microsoft .NET 2.0 Development

Note: You can also retrieve information from an event log by using the Event Viewer tool in the Windows operating system or by using code.

Exercise 3: Adding and Configuring Trace Statements in an Application


Task 1: Add an event log trace listener to the application
1. 2. In Solution Explorer, open the code editor window for the Main form. In the Load event handler for the Main form, add code to perform the following actions: a. Create a new instance of an EventLogTraceListener listener that is named ordersListener, which writes to the OrdersEventLog event log.

b. Add the new listener to the Listeners collection of the Trace object. Your code should resemble the following examples.
[Visual C#] EventLogTraceListener ordersListener = new EventLogTraceListener("OrdersApplicationSource"); Trace.Listeners.Add(ordersListener);

[Visual Basic] Dim ordersListener As New EventLogTraceListener("OrdersApplicationSource") Trace.Listeners.Add(ordersListener)

3. 4.

In the Main form, locate the event handler for the FormClosing event. In the FormClosing event handler, add code to call the Flush method of the Trace class. Your code should resemble the following examples.

[Visual C#] Trace.Flush();

[Visual Basic] Trace.Flush()

Task 2: Create and configure a trace switch


1. In the code editor window for the CreateOrder form, create a new, form-level, private instance of a TraceSwitch class that is named ordersSwitch. Set the displayName property of the switch to Orders and the description property to Order Information. Your code should resemble the following examples.
[Visual C#] private TraceSwitch ordersSwitch = new TraceSwitch("Orders", "Order Information");

[Visual Basic] Private ordersSwitch As New TraceSwitch("Orders", "Order information")

Monitoring and Debugging Applications

4-9

2.

If you are using Visual C#, in the code editor window for the CreateOrder class, add a using statement for the System.Diagnostics namespace. Your code should resemble the following example.

[Visual C#] using System.Diagnostics;

3.

Add an application configuration file to your application. You will use this file to configure the trace switch. a. In Solution Explorer, click the OrdersApplication project.

b. On the Project menu, click Add New Item. c. 4. In the Add New Item OrdersApplication dialog box, in the Templates: list box, click Application Configuration File, and then click Add.

If you are using Visual C#, perform the following tasks: a. In the application configuration file, create a system.diagnostics element as a child of the configuration element.

b. Add a switches element as a child of the system.diagnostics element. Your code should resemble the following examples.
[Visual C#] <system.diagnostics> <switches> </switches> </system.diagnostics>

5.

Add an add element as a child of the switches element that contains the following attributes: name="Orders" value="3"

Your code should resemble the following examples.


[Visual C#] <system.diagnostics> <switches> <add name="Orders" value="3"/> </switches> </system.diagnostics>

[Visual Basic] <system.diagnostics> <switches> <add name="DefaultSwitch" value="Information"/> <add name="Orders" value="3"/> </switches> </system.diagnostics>

4-10

Core Foundations of Microsoft .NET 2.0 Development

Task 3: Add Trace statements to the application


1. 2. 3. In Solution Explorer, open the code editor window for the CreateOrder form. Locate the constructor for the CreateOrder form. In the constructor, underneath the current lines of code, add code to output a Trace statement that has the following properties: a. Use the WriteLineIf method of the Trace class.

b. Output the Order created message only if the trace level is set to Info or higher. c. Set the category of the Trace statement to Object creation.

Your code should resemble the following examples.


[Visual C#] Trace.WriteLineIf(ordersSwitch.TraceInfo, "Order created", "Object creation");

[Visual Basic] Trace.WriteLineIf(ordersSwitch.TraceInfo, "Order created", "Object creation")

4. 5.

In the CreateOrder form, locate the Click event handler for the addItemsButton control. In the For loop, underneath the call to the AddItem method, add code to output a Trace statement that has the following settings: a. Use the WriteLineIf method of the Trace class.

b. Output the order ID and the Order item created message only if the trace level is set to Verbose. c. Set the category of the Trace statement to Object creation.

Your code should resemble the following examples.

[Visual C#] Trace.WriteLineIf(ordersSwitch.TraceVerbose, "Order " + orderIdLabel.Text + ": Order item created", "Object creation");

[Visual Basic] Trace.WriteLineIf(ordersSwitch.TraceVerbose, "Order " & orderIdLabel.Text _ & ": Order item created", "Object creation")

6. 7.

In the Click event handler, locate the Catch statement. Underneath the call to the Show method of the message box, add code to output a Trace statement that has the following settings: a. Use the WriteLineIf method of the Trace class.

b. Output the order ID and the Order item not created message only if the trace level is set to Verbose. c. Set the category of the Trace statement to Object creation.

Monitoring and Debugging Applications

4-11

Your code should resemble the following examples.


[Visual C#] Trace.WriteLineIf(ordersSwitch.TraceVerbose, "Order " + orderIdLabel.Text + ": Order item not created", "Object creation");

[Visual Basic] Trace.WriteLineIf(ordersSwitch.TraceVerbose, "Order " & orderIdLabel.Text _ & ": Order item not created", "Object creation")

8.

Save and build the application.

Task 4: Test the Trace statements


1. 2. 3. 4. 5. 6. 7. 8. Run the application. In the Main form, in the Order ID box, type an integer, and then click Add Order. In the Create Order form, select one or more items in the list box, and then click Add Item(s). Close the application. Examine the content of the Output window and identify whether the output of any of the Trace statements is present. In Server Explorer, expand Servers, expand your server, and then expand Event Logs. In Server Explorer, right-click Event Logs, and then click Refresh. In the list of event logs, expand OrdersEventLog, expand OrdersApplicationSource, and then view the details of the most recent event log entries. The output of the Order created trace statement is present but the output of the Order item created statement is not. This is because the trace switch level is currently set to Information and not Verbose.

Task 5: Configure the trace output and retest the application


1. 2. 3. 4. 5. 6. 7. 8. 9. Open the application configuration file for your application. In the configuration file, change the value of the Orders trace switch to 4 (verbose). Save the file. Run the application. In the Main form, in the Order ID box, type an integer, and then click Add Order. In the Create Order form, select one or more items in the list box, and then click Add Item(s). Close the application. Examine the content of the Output window and identify whether the output of any of the Trace statements is present. In Server Explorer, expand Servers, expand your server, and then expand Event Logs.

10. In Server Explorer, right-click Event Logs, and then click Refresh. 11. In the list of event logs, expand OrdersEventLog, expand OrdersApplicationSource, and then view the details of the most recent event log entries.

4-12

Core Foundations of Microsoft .NET 2.0 Development

The output of both the Order created and Order item created trace statements is present. This is because the trace switch level is now set to Verbose. 12. In the application configuration file, reconfigure the Orders switch and set the value to 0 to turn off tracing. Your code should resemble the following example.
<add name="Orders" value="0"/>

13. Run the application again, and then verify that the Trace statements are not produced. 14. Close the application.

Reading and Writing Files

5-1

Module 5
Reading and Writing Files
Contents:
Lab Answer Keys 2

5-2

Core Foundations of Microsoft .NET 2.0 Development

Lab Answer Keys


Lab: Reading and Writing Files Exercise 1: Archiving Files
Task 1: Parse the command-line arguments for the application
1. 2. Click Start, point to All Programs, point to Microsoft Visual Studio 2005, and then click Microsoft Visual Studio 2005. Open the Archiver solution in the E:\Labfiles\Starter\CS\Ex1\Archiver folder (if you are using the Microsoft Visual C# development tool) or the E:\Labfiles\Starter\VB\Ex1\Archiver folder (if you are using the Microsoft Visual Basic development system). In the Program class, before the Main method, add the following private static (C#) or Shared (Visual Basic) fields to hold the values in the command-line parameters.

3.

Field name
sourceFolder destinationFolder ageOfFiles compressFiles

Type
String String Integer Boolean

Default value
String.Empty String.Empty 0 false

Your code should resemble the statements that appear in bold in the following examples.
[Visual C#] class Program { private private private private

static static static static

string sourceFolder = String.Empty; string destinationFolder = String.Empty; int ageOfFiles = 0; bool compressFiles = false;

static void Main(string[] args) { }

[Visual Basic] Class Program Private Shared sourceFolder As String = String.Empty Private Shared destinationFolder As String = String.Empty Private Shared ageOfFiles As Integer = 0 Private Shared compressFiles As Boolean = False Public Shared Sub Main() End Sub End Class

4.

In the Main method of the Program class, add a try block and parse the commandline parameters into the static (C#) or Shared (Visual Basic) fields. Throw an ArgumentException

Reading and Writing Files

5-3

exception if there are insufficient arguments or if any of the command-line parameters cannot be converted to the appropriate type. In the catch block, report the exception message to the user. Your code should resemble the statements that appear in bold in the following examples.
[Visual C#] static void Main(string[] args) { try { if (args.Length != 4) { throw new ArgumentException("Too many or too few arguments"); } sourceFolder = args[0]; destinationFolder = args[1]; if (!int.TryParse(args[2], out ageOfFiles)) { throw new ArgumentException("Age parameter must be an integer"); } if (!bool.TryParse(args[3], out compressFiles)) { throw new ArgumentException("Compress parameter must be true or false"); } } catch (Exception e) { Console.WriteLine(e.Message); } }

[Visual Basic] Public Shared Sub Main() Try If (My.Application.CommandLineArgs.Count <> 4) Then Throw New ArgumentException("Too many or too few arguments") End If sourceFolder = My.Application.CommandLineArgs(0) destinationFolder = My.Application.CommandLineArgs(1) If Not Integer.TryParse(My.Application.CommandLineArgs(2), ageOfFiles) Then Throw New ArgumentException("Age parameter must be an integer") End If If Not Boolean.TryParse(My.Application.CommandLineArgs(3), compressFiles) Then Throw New ArgumentException("Compress parameter must be true or false") End If Catch e As Exception Console.WriteLine(e.Message) End Try End Sub

5.

Build the solution, and then correct any compiler errors.

5-4

Core Foundations of Microsoft .NET 2.0 Development

Task 2: Check the validity of the source and destination directories


1. In the Program class, create a private static (C#) or Shared (Visual Basic), void (Sub) method that is called CheckDirectories. This method takes a string parameter that is called source and a string parameter that is called destination. These parameters hold the names of the source and destination folders. Your code should resemble the statements in the following examples.
[Visual C#] private static void CheckDirectories(string source, string destination) { }

[Visual Basic] Private Shared Sub CheckDirectories(ByVal source As String, ByVal destination As String) End Sub

2.

In the CheckDirectories method, add a statement that calls the Exists method of the Directory class to check whether the folder that the source parameter specified exists. If it does not, throw an ArgumentException exception. Your code should resemble the statements that appear in bold in the following examples.

[Visual C#] private static void CheckDirectories(string source, string destination) { if (!Directory.Exists(source)) { throw new ArgumentException("Source folder does not exist"); } }

[Visual Basic] Private Shared Sub CheckDirectories(ByVal source As String, ByVal destination As String) If Not Directory.Exists(source) Then Throw New ArgumentException("Source folder does not exist") End If End Sub

3.

In the CheckDirectories method, add another statement that calls the Exists method of the Directory class to determine whether the folder that the destination parameter specified exists. If it does not, use the CreateDirectory method of the Directory class to create it. Note: Permit any exceptions to be propagated to the calling method. These will include security exceptions that are thrown if the user does not have sufficient rights to create the destination folder. Your code should resemble the statements that appear in bold in the following examples.

[Visual C#] private static void CheckDirectories(string source, string destination)

Reading and Writing Files

5-5

if (!Directory.Exists(source)) { throw new ArgumentException("Source folder does not exist"); } if (!Directory.Exists(destination)) { Directory.CreateDirectory(destination); }

[Visual Basic] Private Shared Sub CheckDirectories(ByVal source As String, ByVal destination As String) If Not Directory.Exists(source) Then Throw New ArgumentException("Source folder does not exist") End If If Not Directory.Exists(destination) Then Directory.CreateDirectory(destination) End If End Sub

4.

At the end of the try block in the Main method, add a statement to call the CheckDirectories method, passing the values in the sourceFolder and destinationFolder fields as parameters. Your code should resemble the statement that appears in bold in the following examples.

[Visual C#] static void Main(string[] args) { try { ... CheckDirectories(sourceFolder, destinationFolder); } catch (Exception e) { ... } }

[Visual Basic] Public Shared Sub Main() Try ... CheckDirectories(sourceFolder, destinationFolder) Catch e As Exception ... End Try End Sub

5.

Build the solution, and then correct any compiler errors.

5-6

Core Foundations of Microsoft .NET 2.0 Development

Task 3: Archive the files and folders in the source directory that have the specified
age to the destination folder
1. In the Program class, create a private static (C#) or Shared (Visual Basic), void (Sub) method that is called ArchiveFiles. This method takes a string parameter that is called source and a string parameter that is called destination. These parameters hold the names of the source and destination folders. Your code should resemble the statements in the following examples.
[Visual C#] private static void ArchiveFiles(string source, string destination) { }

[Visual Basic] Private Shared Sub ArchiveFiles(ByVal source As String, ByVal destination As String) End Sub

2.

In the ArchiveFiles method, add code to create a DirectoryInfo object for the value in the source parameter. Name this object sourceInfo. Your code should resemble the statements that appear in bold in the following examples.

[Visual C#] private static void ArchiveFiles(string source, string destination) { DirectoryInfo sourceInfo = new DirectoryInfo(source); }

[Visual Basic] Private Shared Sub ArchiveFiles(ByVal source As String, ByVal destination As String) Dim sourceInfo As new DirectoryInfo(source) End Sub

3.

Add a statement that calls the GetFiles method of the sourceInfo object to obtain a list of all of the files in the source folder as an array of FileInfo objects. Name this array fileList. Your code should resemble the statement that appears in bold in the following examples.

[Visual C#] private static void ArchiveFiles(string source, string destination) { DirectoryInfo sourceInfo = new DirectoryInfo(source); FileInfo[] fileList = sourceInfo.GetFiles(); }

[Visual Basic] Private Shared Sub ArchiveFiles(ByVal source As String, ByVal destination As String) Dim sourceInfo As new DirectoryInfo(source)

Reading and Writing Files

5-7

Dim fileList() As FileInfo = sourceInfo.GetFiles() End Sub

4.

In the fileList array, iterate through the FileInfo objects. If a file meets the age criterion that is specified in the ageOfFiles field of the Archiver class, use the CopyTo method of the FileInfo object to copy this file to the folder that the destInfo object specified. Overwrite the archive copy if it already exists. Note: To determine the age of a file, examine the CreationTime property of the FileInfo object and compare it to the current date and time. Use the Path.Combine method to append the name of the file to the destination folder. Your code should resemble the statements that appear in bold in the following examples.

[Visual C#] private static void ArchiveFiles(string source, string destination) { DirectoryInfo sourceInfo = new DirectoryInfo(source); FileInfo[] fileList = sourceInfo.GetFiles(); foreach (FileInfo file in fileList) { DateTime creationDate = file.CreationTime; if (creationDate.AddDays(ageOfFiles) < DateTime.Now) { file.CopyTo(Path.Combine(destination, file.Name), true); } } }

[Visual Basic] Private Shared Sub ArchiveFiles(ByVal source As String, ByVal destination As String) Dim sourceInfo As new DirectoryInfo(source) Dim fileList() As FileInfo = sourceInfo.GetFiles() For Each file As FileInfo In fileList Dim creationDate As DateTime = file.CreationTime If (creationDate.AddDays(ageOfFiles) < DateTime.Now) Then file.CopyTo(Path.Combine(destination, file.Name), True) End If Next file End Sub

5.

At the end of the ArchiveFiles method, add a statement that calls the GetDirectories method of the sourceInfo object to obtain a list of all of the child folders in the source folder as an array of DirectoryInfo objects. Name this array childFolderList. Your code should resemble the statement that appears in bold in the following examples.
[Visual C#] private static void ArchiveFiles(string source, string destination) { ... DirectoryInfo[] childFolderList = sourceInfo.GetDirectories(); }

5-8

Core Foundations of Microsoft .NET 2.0 Development

[Visual Basic] Private Shared Sub ArchiveFiles(ByVal source As String, ByVal destination As String) ... Dim childFolderList() As DirectoryInfo = sourceInfo.GetDirectories() End Sub

6.

In the childFolderList array, iterate through the DirectoryInfo objects. For each DirectoryInfo object, perform the following tasks: a. Extract the name of the child folder, and then construct the name for the destination folder for files in this child folder by using the Path.Combine method to append this name to the value in the destination parameter. Store this value in a string variable that is called childDestination.

b. Use the static (C#) or Shared (Visual Basic) CreateDirectory method of the c. Directory class to create the folder that is named in the childDestination string variable.

d. Recursively call the ArchiveFiles method, passing the full name of the folder from the DirectoryInfo object and the value in the childDestination string variable as parameters. Your code should resemble the statements that appear in bold in the following examples.
[Visual C#] private static void ArchiveFiles(string source, string destination) { ... DirectoryInfo[] childFolderList = sourceInfo.GetDirectories(); foreach (DirectoryInfo folder in childFolderList) { string childDestination = Path.Combine(destination, folder.Name); Directory.CreateDirectory(childDestination); ArchiveFiles(folder.FullName, childDestination); } }

[Visual Basic] Private Shared Sub ArchiveFiles(ByVal source As String, ByVal destination As String) ... Dim childFolderList() As DirectoryInfo = sourceInfo.GetDirectories() For Each folder As DirectoryInfo In childFolderList Dim childDestination As String = Path.Combine(destination, folder.Name) Directory.CreateDirectory(childDestination) ArchiveFiles(folder.FullName, childDestination) Next folder End Sub

7.

In the Main method, at the end of the try block, add a statement to call the ArchiveFiles method, passing the values in the sourceFolder and destinationFolder fields as parameters. Your code should resemble the statement that appears in bold in the following examples.

[Visual C#] static void Main(string[] args)

Reading and Writing Files

5-9

try {

... CheckDirectories(sourceFolder, destinationFolder); ArchiveFiles(sourceFolder, destinationFolder);

} catch (Exception e) { ... }

[Visual Basic] Public Shared Sub Main() Try ... CheckDirectories(sourceFolder, destinationFolder) ArchiveFiles(sourceFolder, destinationFolder) Catch e As Exception ... End Try End Sub

8.

Build the solution, and then correct any compiler errors.

Task 4: Test the application


1. Open a Command Prompt window, and then go to the E:\Labfiles\Starter\CS\Ex1\Archiver\Archiver\bin\Debug folder (if you are using Visual C#) or the E:\Labfiles\Starter\VB\Ex1\Archiver\Archiver\bin\Debug folder (if you are using Visual Basic). In the Command Prompt window, type the command in the following code example, and then press ENTER.

2.

Archiver "C:\Program Files\Common Files" E:\Archive 0 false

This command copies all files in the C:\Program Files\Common Files folder and child folders that are more than 0 days old to the E:\Archive folder. 3. 4. In Windows Explorer, browse to the E:\ folder, and then verify that the E:\Archive folder has been created. Right-click the C:\Program Files\Common Files folder, and then click Properties. In the Common Files Properties window, make a note of the Size and Contains properties, and then close the Common Files Properties window. Right-click the E:\Archive folder, and then click Properties. In the Archive Properties window, verify that this folder contains the same number of files and is the same size as the C:\Program Files\Common Files folder, and then close the Archive Properties window. Delete the E:\Archive folder and its contents. In the Command Prompt window, type the command in the following code example, and then press ENTER.

5.

6. 7.

5-10

Core Foundations of Microsoft .NET 2.0 Development

Archiver "C:\Program Files\Common Files" E:\Archive 10000 false

This command copies all files in the C:\Program Files\Common Files folder and child folders that are more than 10,000 days old to the E:\Archive folder. 8. 9. In Windows Explorer, browse to the E:\ folder, and then verify that the E:\Archive folder and the child folders have been created but are empty. Close Windows Explorer, close the Command Prompt window, and then return to Visual Studio 2005.

Exercise 2: Compressing Files


Task 1: Compress files as they are archived
1. 2. Open the Archiver solution in the E:\Labfiles\Starter\CS\Ex2\Archiver folder (if you are using Visual C#) or the E:\Labfiles\Starter\VB\Ex2\Archiver folder (if you are using Visual Basic). At the top of the Program.cs file (if you are using Visual C#) or the Program.vb file (if you are using Visual Basic), add a statement to bring the System.IO.Compression namespace into scope. Your code should resemble the statement in the following examples.
[Visual C#] using System.IO.Compression;

[Visual Basic] Imports System.IO.Compression

3.

In the Program class, above the Main method, add a private constant integer value that is called BUFFSIZE that has the value 4096. This value represents the block size that the Archiver application will use when it writes data to the compressed stream for a file. Your code should resemble the statement that appears in bold in the following examples.

[Visual C#] class Program { ... private const int BUFFSIZE = 4096; static void Main(string[] args) { ... }

... }

[Visual Basic] Class Program ... Private Const BUFFSIZE = 4096 Public Shared Sub Main() ...

Reading and Writing Files

5-11

End Sub ... End Class

4.

In the ArchiveFiles method, locate the if statement in the following code examples, which copies a file from the source to the destination folder.

[Visual C#] if (creationDate.AddDays(ageOfFiles) < DateTime.Now) { file.CopyTo(Path.Combine(destination, file.Name), true); }

[Visual Basic] If (creationDate.AddDays(ageOfFiles) < DateTime.Now) Then file.CopyTo(Path.Combine(destination, file.Name), True) End If

5.

In the if statement, add another if statement that tests the value in the compressFiles field. If this value is true, add code to perform the following tasks: a. Open a FileStream object for reading the file that the file variable identifies.

b. Open a FileStream object for writing to the corresponding file in the destination folder. Append the suffix .gzip to the destination file name. Overwrite the destination file if it already exists. c. Add a using (Using) statement that creates a GZipStream object for writing compressed data to the destination file stream.

d. Inside the using (Using) statement block, read the data from the FileStream object for the file variable, and write this data to the GZipStream object for the destination file. Read and write this data in chunks of the size that the BUFFSIZE constant defines. e. Inside the using (Using) statement block, close the GZipStream object.

If the value of the compressFiles field is false, add an else branch to the new if statement that uses the existing statement to copy the file to the destination folder without compressing it. Your code should resemble the statements that appear in bold in the following examples.
[Visual C#] if (creationDate.AddDays(ageOfFiles) < DateTime.Now) { if (compressFiles) { FileStream sourceFile = file.OpenRead(); FileStream destFile = new FileStream(Path.Combine(destination, file.Name) + ".gzip", FileMode.Create, FileAccess.Write); using (GZipStream gzipFile = new GZipStream(destFile, CompressionMode.Compress)) { byte[] data = new byte[BUFFSIZE]; int numBytesRead = sourceFile.Read(data, 0, BUFFSIZE); while (numBytesRead > 0) { gzipFile.Write(data, 0, BUFFSIZE); numBytesRead = sourceFile.Read(data, 0, BUFFSIZE); }

5-12

Core Foundations of Microsoft .NET 2.0 Development

gzipFile.Close(); } } else { file.CopyTo(Path.Combine(destination, file.Name), true); } }

[Visual Basic] If (creationDate.AddDays(ageOfFiles) < DateTime.Now) Then If compressFiles Then Dim sourceFile As FileStream = file.OpenRead() Dim destFile As New FileStream(Path.Combine(destination, file.Name) + ".gzip", FileMode.Create, FileAccess.Write) Using gzipFile As New GZipStream(destFile, CompressionMode.Compress) Dim data(BUFFSIZE - 1) As Byte Dim numBytesRead As Integer = sourceFile.Read(Data, 0, BUFFSIZE) While numBytesRead > 0 gzipFile.Write(data, 0, BUFFSIZE) numBytesRead = sourceFile.Read(data, 0, BUFFSIZE) End While gzipFile.Close() End Using Else file.CopyTo(Path.Combine(destination, file.Name), True) End If End If

6.

Build the solution, and then correct any compiler errors.

Task 2: Test the application


1. Open a Command Prompt window, and then browse to the E:\Labfiles\Starter\CS\Ex2\Archiver\Archiver\bin\Debug folder (if you are using Visual C#) or the E:\Labfiles\Starter\VB\Ex2\Archiver\Archiver\bin\Debug folder (if you are using Visual Basic). In the Command Prompt window, type the command in the following code example, and then press ENTER.

2.

Archiver "C:\Program Files\Common Files" E:\Archive 0 true

This command copies all files in the C:\Program Files\Common Files folder and child folders that are more than 0 days old to the E:\Archive folder, and then compresses them as they are copied. 3. 4. 5. 6. In Windows Explorer, right-click the C:\Program Files\Common Files folder, and then click Properties. In the Common Files Properties dialog box, make a note of the Size and Contains properties, and then close the Common Files Properties dialog box. Right-click the E:\Archive folder, and then click Properties. In the Archive Properties dialog box, verify that this folder contains the same number of files as the C:\Program Files\Common Files folder but that the size of the folder and its contents is smaller, and then close the Archive Properties dialog box.

Reading and Writing Files

5-13

7. 8. 9.

Examine the files in the E:\Archive folder and subfolders, and then verify that they all have the .gzip file extension. Delete the E:\Archive folder and its contents. Close Windows Explorer, close the Command Prompt window, and then return to Visual Studio 2005.

Exercise 3: Storing and Retrieving User Preferences


Task 1: Store user preferences in isolated storage
1. 2. Open the Archiver solution in the E:\Labfiles\Starter\CS\Ex3\Archiver folder (if you are using Visual C#) or the E:\Labfiles\Starter\VB\Ex3\Archiver folder (if you are using Visual Basic). At the top of the Program.cs file (if you are using Visual C#) or the Program.vb file (if you are using Visual Basic), add a statement to bring the System.IO.IsolatedStorage namespace into scope. Your code should resemble the statement in the following examples.
[Visual C#] using System.IO.IsolatedStorage;

[Visual Basic] Imports System.IO.IsolatedStorage

3.

In the Program class, above the Main method, add a private constant string value that is called OPTIONSFOLDER that has the value "ArchiveOptionsFolder". Also add a private constant string value that is called OPTIONSFILE that has the value "ArchiverOptions". These strings represent the folder and name of the file that the Archiver application will use to store user preferences in isolated storage. Your code should resemble the statements that appear in bold in the following examples.

[Visual C#] class Program { ... private const string OPTIONSFOLDER = "ArchiverOptionsFolder"; private const string OPTIONSFILE = "ArchiverOptions"; static void Main(string[] args) { ... } ...

[Visual Basic] Class Program ... Private Const OPTIONSFOLDER = "ArchiverOptionsFolder" Private Const OPTIONSFILE = "ArchiverOptions" Public Shared Sub Main() ...

5-14

Core Foundations of Microsoft .NET 2.0 Development

End Sub ... End Class

4.

In the Program class, add a private static (C#) or Shared (Visual Basic) void (Sub) method that is called StoreOptionsInIsolatedStorage. This method does not take any parameters. Your code should resemble the statements that appear in bold in the following examples.

[Visual C#] class Program { ... private static void StoreOptionsInIsolatedStorage() { } ... }

[Visual Basic] Class Program ... Private Shared Sub StoreOptionsInIsolatedStorage() End Sub ... End Class

5.

In the StoreOptionsInIsolatedStorage method, add code to perform the following tasks: a. Create an IsolatedStorageFile object that represents a user-scoped store for the application domain and assembly by calling the GetUserStoreForDomain method of the IsolatedStorageFile class.

b. Create a folder in this isolated storage by calling the CreateDirectory method of the IsolatedStorageFile object. Specify the folder name in the OPTIONSFOLDER constant string. c. Create an IsolatedStorageFileStream object for creating and writing to the file that is specified by the OPTIONSFILE constant string in the new folder in isolated storage.

d. Create a StreamWriter object for writing text data to this IsolatedStorageFileStream object. e. f. Using the StreamWriter object, write the values in the sourceFolder, destinationFolder, ageOfFiles, and compressFiles fields as string values to the IsolatedStorageFileStream object.

g. Close the StreamWriter object. h. Close the IsolatedStorageFile object.

Your code should resemble the statements in the following examples.


[Visual C#] private static void StoreOptionsInIsolatedStorage() { IsolatedStorageFile isolatedStorage = IsolatedStorageFile.GetUserStoreForDomain();

Reading and Writing Files

5-15

isolatedStorage.CreateDirectory(OPTIONSFOLDER); using (IsolatedStorageFileStream isolatedStream = new IsolatedStorageFileStream(Path.Combine(OPTIONSFOLDER, OPTIONSFILE), FileMode.Create, FileAccess.Write, isolatedStorage)) { StreamWriter optionsWriter = new StreamWriter(isolatedStream); optionsWriter.WriteLine(sourceFolder); optionsWriter.WriteLine(destinationFolder); optionsWriter.WriteLine(ageOfFiles); optionsWriter.WriteLine(compressFiles); optionsWriter.Close(); isolatedStorage.Close(); } }

[Visual Basic] Private Shared Sub StoreOptionsInIsolatedStorage() Dim isolatedStorage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForDomain() isolatedStorage.CreateDirectory(OPTIONSFOLDER) Using isolatedStream As New IsolatedStorageFileStream(Path.Combine(OPTIONSFOLDER, OPTIONSFILE), FileMode.Create, FileAccess.Write, isolatedStorage) Dim optionsWriter As New StreamWriter(isolatedStream) optionsWriter.WriteLine(sourceFolder) optionsWriter.WriteLine(destinationFolder) optionsWriter.WriteLine(ageOfFiles) optionsWriter.WriteLine(compressFiles) optionsWriter.Close() isolatedStorage.Close() End Using End Sub

6.

In the Main method, after parsing the command-line options, add a statement that calls the StoreOptionsInIsolatedStorage method to store these options in isolated storage. Your code should resemble the statement that appears in bold in the following examples.

[Visual C#] if (!bool.TryParse(args[3], out compressFiles)) { throw new ArgumentException("Compress parameter must be true or false"); } StoreOptionsInIsolatedStorage();

[Visual Basic] If Not Boolean.TryParse(My.Application.CommandLineArgs(3), compressFiles) Then Throw New ArgumentException("Compress parameter must be true or false") End If StoreOptionsInIsolatedStorage()

7.

Build the solution, and then correct any compiler errors.

5-16

Core Foundations of Microsoft .NET 2.0 Development

Task 2: Retrieve user preferences from isolated storage


1. In the Program class, add a private static (C#) or Shared (Visual Basic) void (Sub) method that is called PopulateFieldsFromIsolatedStorage. This method does not take any parameters. Your code should resemble the statements that appear in bold in the following examples.
[Visual C#] class Program { ... private static void PopulateFieldsFromIsolatedStorage() { } ... }

[Visual Basic] Class Program ... Private Shared Sub PopulateFieldsFromIsolatedStorage() End Sub ... End Class

2.

In the PopulateFieldsFromIsolatedStorage method, add code to perform the following tasks: a. Create an IsolatedStorageFile object that represents a user-scoped store for the application domain and assembly by calling the GetUserStoreForDomain method of the IsolatedStorageFile class.

b. Create an IsolatedStorageFileStream object for reading from the file that is specified by the OPTIONSFILE constant string in the folder that is specified by the OPTIONSFOLDER constant string in isolated storage. c. Create a StreamReader object for reading text data from this IsolatedStorageFileStream object.

d. Using the StreamReader object, read the first line of text and use it to populate the sourceFolder field. e. f. Read the second line of text and use it to populate the destinationFolder field. Read the third line of text, convert it to an integer value, and then use it to populate the ageOfFiles field.

g. Read the fourth line of text, convert it to a Boolean value, and then use it to populate the compressFiles field. h. i. Close the StreamReader object. Close the IsolatedStorageFile object.

Your code should resemble the statements in the following examples.


[Visual C#] private static void PopulateFieldsFromIsolatedStorage() { IsolatedStorageFile isolatedStorage =

Reading and Writing Files

5-17

IsolatedStorageFile.GetUserStoreForDomain(); using (IsolatedStorageFileStream isolatedStream = new IsolatedStorageFileStream(Path.Combine(OPTIONSFOLDER, OPTIONSFILE), FileMode.Open, FileAccess.Read, isolatedStorage)) { StreamReader optionsReader = new StreamReader(isolatedStream); string opt = optionsReader.ReadLine(); sourceFolder = opt; opt = optionsReader.ReadLine(); destinationFolder = opt; opt = optionsReader.ReadLine(); ageOfFiles = Convert.ToInt32(opt); opt = optionsReader.ReadLine(); compressFiles = Convert.ToBoolean(opt); optionsReader.Close(); isolatedStream.Close(); }

[Visual Basic] Private Shared Sub PopulateFieldsFromIsolatedStorage() Dim isolatedStorage As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForDomain() Using isolatedStream As New IsolatedStorageFileStream(Path.Combine(OPTIONSFOLDER, OPTIONSFILE), FileMode.Open, FileAccess.Read, IsolatedStorage) Dim optionsReader = New StreamReader(isolatedStream) Dim opt As String = optionsReader.ReadLine() sourceFolder = opt opt = optionsReader.ReadLine() destinationFolder = opt opt = optionsReader.ReadLine() ageOfFiles = Convert.ToInt32(opt) opt = optionsReader.ReadLine() compressFiles = Convert.ToBoolean(opt) optionsReader.Close() isolatedStream.Close() End Using End Sub

3.

In the Main method, modify the statement that checks for the number of commandline arguments; the user can now specify either one argument (ditto) or four. Your code should resemble the statement that appears in bold in the following examples.

[Visual C#] if (args.Length != 4 && args.Length != 1) { throw new ArgumentException("Too many or too few arguments"); }

[Visual Basic] If (My.Application.CommandLineArgs.Count <> 4 And My.Application.CommandLineArgs.Count <> 1) Then Throw New ArgumentException("Too many or too few arguments") End If

5-18

Core Foundations of Microsoft .NET 2.0 Development

4.

Add code that checks whether the first command-line argument is the string ditto. If it is, call the PopulateFieldsFromIsolatedStorage method. Otherwise, populate the fields from the command-line arguments as the application did previously. Your code should resemble the statements that appear in bold in the following examples.

[Visual C#] if (args.Length != 4 && args.Length != 1) { throw new ArgumentException("Too many or too few arguments"); } if (args[0].CompareTo("ditto") == 0) { PopulateFieldsFromIsolatedStorage(); } else { sourceFolder = args[0]; destinationFolder = args[1]; if (!int.TryParse(args[2], out ageOfFiles)) { throw new ArgumentException("Age parameter must be an integer"); } if (!bool.TryParse(args[3], out compressFiles)) { throw new ArgumentException("Compress parameter must be true or false"); } storeOptionsInIsolatedStorage(); }

[Visual Basic] If (My.Application.CommandLineArgs.Count <> 4 And My.Application.CommandLineArgs.Count <> 1) Then Throw New ArgumentException("Too many or too few arguments") End If If My.Application.CommandLineArgs(0).CompareTo("ditto") = 0 Then PopulateFieldsFromIsolatedStorage() Else sourceFolder = My.Application.CommandLineArgs(0) destinationFolder = My.Application.CommandLineArgs(1) If Not Integer.TryParse(My.Application.CommandLineArgs(2), ageOfFiles) Then Throw New ArgumentException("Age parameter must be an integer") End If If Not Boolean.TryParse(My.Application.CommandLineArgs(3), compressFiles) Then Throw New ArgumentException("Compress parameter must be true or false") End If End If

5.

Build the solution, and then correct any compiler errors.

Task 3: Test the application


1. Open a Command Prompt window, and then browse to the E:\Labfiles\Starter\CS\Ex3\Archiver\Archiver\bin\Debug folder (if you are using Visual C#) or the E:\Labfiles\Starter\VB\Ex3\Archiver\Archiver\bin\Debug folder (if you are using Visual Basic).

Reading and Writing Files

5-19

2.

In the Command Prompt window, type the command in the following code example, and then press ENTER.

Archiver "C:\Program Files\Common Files" E:\Archive 0 false

This command copies all files in the C:\Program Files\Common Files folder and child folders that are more than 0 days old to the E:\Archive folder. 3. 4. Delete the E:\Archive folder and its contents. In the Command Prompt window, type the command in the following code example, and then press ENTER.

Archiver ditto

This command runs the Archiver application by using the same options that were specified in Step 2. 5. In Windows Explorer, browse to the E:\ folder, and then verify that the E:\Archive folder has been created and contains an uncompressed copy of all of the files in the C:\Program Files\Common Files folder and child folders. In Windows Explorer, browse to the C:\Documents and Settings\Student\Local Settings\Application Data\IsolatedStorage folder. This folder contains child folders that hold the isolated storage tree for the Student user. 7. 8. 9. Browse to the child folder in this folder (it will have a random name). Browse to the child folder in this folder (it will also have a random name). Browse to the child folder in this folder (it will have a random name that begins with the prefix Url).

6.

10. Browse to the child folder in this folder (it will also have a random name that begins with the prefix Url). 11. Browse to the Files folder. This folder represents the isolated storage for the Archiver assembly. 12. Browse to the ArchiverOptionsFolder folder. 13. Using Notepad, open the ArchiverOptions file. Verify that this file contains the text in the following code example, which specifies the most recent set of command-line arguments that you specified.
C:\Program Files\Common Files E:\Archive 0 False

14. Close Notepad, close Windows Explorer, and then close the Command Prompt window.

Serializing Data

6-1

Module 6
Serializing Data
Contents:
Lab Answer Keys 2

6-2

Core Foundations of Microsoft .NET 2.0 Development

Lab Answer Keys


Lab: Serializing Data Exercise 1: Serializing and Deserializing Data Across a Network by Using Runtime Serialization
Task 1: Review and modify the definition of the BankCustomer class to support
serialization
1. 2. Click Start, point to All Programs, point to Microsoft Visual Studio 2005, and then click Microsoft Visual Studio 2005. Open the Serializer solution in the E:\Labfiles\Starter\CS\Ex1\Serializer folder (if you are using the Microsoft Visual C# development tool) or the E:\Labfiles\Starter\VB\Ex1\Serializer folder (if you are using the Microsoft Visual Basic development system). This solution contains the following four projects: Bank, which is a class library project that contains the definition of the BankCustomer class. NetworkLib, which is a class library project that contains two helper classes that are called Client and Server to provide static (C#) or Shared (Visual Basic) methods to connect and create a NetworkStream object. Receiver, which is a console application project that you will use to deserialize and display a BankCustomer object that is transmitted over the network. Sender, which is a console application project that you will use to create and serialize a BankCustomer object for transmission over the network.

3.

In the Bank project, in the Bank.cs file (if you are using Visual C#) or the Bank.vb file (if you are using Visual Basic), examine the BankCustomer class. Note the following features of the class: It is in a namespace that is called Bank. It contains four private fields that hold the customer ID, name, balance, and personal identification number (PIN). It provides a constructor that populates the ID and name fields and sets the balance and PIN to default values. It does not provide a default constructor. It provides public read-only access to the customer ID and name through the CustomerID and CustomerName properties. It enables customers to change their PIN by using the SetCustomerPin method, specifying the existing PIN and the new PIN. It enables customers to use the Deposit method to add money to the account. This method is not PIN-protected and performs no checks as to the validity of the amount that is deposited in this prototype class. It enables customers to use the Withdraw method to remove money from the account. This method is PIN-protected but performs no further checks as to the validity of the amount that is withdrawn.

Serializing Data

6-3

4.

It enables customers to use the GetCustomerBalance method to find out their current balance. This method is PIN-protected.

Tag the BankCustomer class with the SerializableAttribute attribute to enable it to be serialized by using runtime serialization. Your code should resemble the statement that appears in bold in the following examples.

[Visual C#] [Serializable()] public class BankCustomer { ... }

[Visual Basic] <Serializable()> _ Public Class BankCustomer ... End Class

5.

Build the solution, and then correct any compiler errors.

Task 2: Serialize and transmit customer information in binary format


1. 2. 3. In Solution Explorer, right-click the Sender project, and then add references to the Bank and NetworkLib projects. In the Sender project, open the Program.cs file (if you are using Visual C#) or the Program.vb file (if you are using Visual Basic). At the top of the file, add statements to bring the System.Runtime.Serialization.Formatters.Binary, System.Net.Sockets, NetworkLib, and Bank namespaces into scope. Note: If you are using Visual Basic, you must import the NetworkLib.NetworkLib and Bank.Bank namespaces.
[Visual C#] using System.Runtime.Serialization.Formatters.Binary; using System.Net.Sockets; using NetworkLib; using Bank;

[Visual Basic] Imports System.Runtime.Serialization.Formatters.Binary Imports System.Net.Sockets Imports NetworkLib.NetworkLib Imports Bank.Bank

4.

In the Program class, in the Main method, add code to create an instance of the BankCustomer class that is called cust. Specify a customer ID of 1 and a name of Fred. Your code should resemble the statement that appears in bold in the following examples.

6-4

Core Foundations of Microsoft .NET 2.0 Development

[Visual C#] static void Main(string[] args) { BankCustomer cust = new BankCustomer(1, "Fred"); }

[Visual Basic] Shared Sub Main() Dim cust As New BankCustomer(1, "Fred") End Sub

5.

To verify the functionality of the BankCustomer object, add statements to perform the following tasks: a. Change the PIN of this new customer from the string 1234 (the default) to the string 8765.

b. Deposit 150 into the account. c. Withdraw 120 from the account.

d. Display the balance of the account. Your code should resemble the statements that appear in bold in the following examples.
[Visual C#] static void Main(string[] args) { BankCustomer cust = new BankCustomer(1, "Fred"); cust.SetCustomerPin("1234", "8765"); cust.Deposit(150); cust.Withdraw(120, "8765"); Console.WriteLine("Balance is {0:C}", cust.GetCustomerBalance("8765")); }

[Visual Basic] Shared Sub Main() Dim cust As New BankCustomer(1, "Fred") cust.SetCustomerPin("1234", "8765") cust.Deposit(150) cust.Withdraw(120, "8765") Console.WriteLine("Balance is {0:C}", cust.GetCustomerBalance("8765")) End Sub

6.

To create a network connection to transmit this object across the network, add statements to perform the following tasks: a. Print the message Press ENTER when the receiver is running to the console.

b. Wait for the user to press ENTER. c. Create a NetworkStream object by calling the static (C#) or Shared (Visual Basic) Client.Connect method (Client is one of the helper classes in the NetworkLib class library).

Your code should resemble the statements that appear in bold in the following examples.
[Visual C#] static void Main(string[] args)

Serializing Data

6-5

{ ... Console.WriteLine("Press ENTER when the receiver is running"); Console.ReadLine(); NetworkStream networkStream = Client.Connect(); }

[Visual Basic] Shared Sub Main() ... Console.WriteLine("Press ENTER when the receiver is running") Console.ReadLine() Dim networkStream As NetworkStream = Client.Connect() End Sub

7.

Serialize and transmit the BankCustomer object over the network connection by adding code to perform the following tasks: a. Create a BinaryFormatter object.

b. Call the Serialize method of the BinaryFormatter object, and then specify the NetworkStream object and the BankCustomer object as parameters. c. Close the NetworkStream object.

d. Display the message Customer data sent on the console. Your code should resemble the statements that appear in bold in the following examples.
[Visual C#] static void Main(string[] args) { ... BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(networkStream, cust); networkStream.Close(); Console.WriteLine("Customer data sent"); }

[Visual Basic] Shared Sub Main() ... Dim formatter As New BinaryFormatter() formatter.Serialize(networkStream, cust) networkStream.Close() Console.WriteLine("Customer data sent") End Sub

8.

Build the solution, and then correct any compiler errors.

Task 3: Deserialize customer information received in binary format


1. 2. In Solution Explorer, right-click the Receiver project, and then add references to the Bank and NetworkLib projects. In the Receiver project, open the Program.cs file (if you are using Visual C#) or the Program.vb file (if you are using Visual Basic).

6-6

Core Foundations of Microsoft .NET 2.0 Development

3.

At the top of the file, add statements to bring the System.Runtime.Serialization.Formatters.Binary, System.Net.Sockets, NetworkLib, and Bank namespaces into scope.

Note: If you are using Visual Basic, you must import the NetworkLib.NetworkLib and Bank.Bank namespaces. Your code should resemble the statements in the following examples.
[Visual C#] using System.Runtime.Serialization.Formatters.Binary; using System.Net.Sockets; using NetworkLib; using Bank;

[Visual Basic] Imports System.Runtime.Serialization.Formatters.Binary Imports System.Net.Sockets Imports NetworkLib.NetworkLib Imports Bank.Bank

4.

In the Program class, in the Main method, add code to display the message Receiver is running to the console. Your code should resemble the statement that appears in bold in the following examples.

[Visual C#] static void Main(string[] args) { Console.WriteLine("Receiver is running"); }

[Visual Basic] Shared Sub Main() Console.WriteLine("Receiver is running") End Sub

5.

Create a NetworkStream object that receives data from the sender by calling the static (C#) or Shared (Visual Basic) Server.Listen method (Server is another helper class in the NetworkLib class library). Your code should resemble the statement that appears in bold in the following examples.

[Visual C#] static void Main(string[] args) { ... NetworkStream networkStream = Server.Listen(); }

[Visual Basic] Shared Sub Main()

Serializing Data

6-7

... Dim networkStream As NetworkStream = Server.Listen() End Sub

6.

Receive and deserialize the BankCustomer object from the network by performing the following tasks: a. Create a BinaryFormatter object.

b. Call the Deserialize method of the BinaryFormatter object, specify the NetworkStream object as the parameter, and then convert the value that is returned into a BankCustomer object. c. Close the NetworkStream object.

Your code should resemble the statements that appear in bold in the following examples.
[Visual C#] static void Main(string[] args) { ... BinaryFormatter formatter = new BinaryFormatter(); BankCustomer cust = formatter.Deserialize(networkStream) as BankCustomer; networkStream.Close(); }

[Visual Basic] Shared Sub Main() ... Dim formatter As New BinaryFormatter() Dim cust As BankCustomer = formatter.Deserialize(networkStream) networkStream.Close() End Sub

7.

To verify that the BankCustomer object has been received successfully, add statements to perform the following tasks (remember that the PIN for this customer is the string 8765). a. Display the ID and name of the customer and the current balance of the account.

b. Deposit 20 into the account. c. Withdraw 30 from the account.

d. Display the new balance. Your code should resemble the statements that appear in bold in the following examples.
[Visual C#] static void Main(string[] args) { ... Console.WriteLine("ID:{0}, Name:{1}, Balance:{2:C}", cust.CustomerID, cust.CustomerName, cust.GetCustomerBalance("8765")); cust.Deposit(20); cust.Withdraw(30, "8765"); Console.WriteLine("New balance:{0:C}", cust.GetCustomerBalance("8765")); }

6-8

Core Foundations of Microsoft .NET 2.0 Development

[Visual Basic] Shared Sub Main() ... Console.WriteLine("ID:{0}, Name:{1}, Balance:{2:C}", cust.CustomerID, cust.CustomerName, cust.GetCustomerBalance("8765")) cust.Deposit(20) cust.Withdraw(30, "8765") Console.WriteLine("New balance:{0:C}", cust.GetCustomerBalance("8765")) End Sub

8.

Build the solution, and then correct any compiler errors.

Task 4: Test the solution


1. In Solution Explorer, right-click the Serializer solution, click Set Startup Projects, and then verify that the Receiver and Sender projects are configured to start when you run the solution (if they are not, click Multiple startup projects, set the Action for both projects to Start, and set the Action for the Bank and NetworkLib projects to None). Close the Solution Serializer Property Pages window. Start the solution without debugging, and then move the console windows for the Sender and Receiver applications so that they are both visible. In the Sender console window, verify that it displays the balance as 30, and then press ENTER. In the Receiver console window, verify that it displays a customer ID of 1, a name of Fred, a balance of 30, and a new balance of 20 (after the deposit and withdraw operations have completed). Close both console windows. Note: This exercise has shown you how to send serialized data over a network, but you can use exactly the same technique to serialize data to a file by creating and using a FileStream object rather than a NetworkStream object.

2. 3. 4.

5.

Exercise 2: Customizing the Runtime Serialization Process


Task 1: Implement the GetObjectData method of the ISerializable interface
1. Open the Serializer solution in the E:\Labfiles\Starter\CS\Ex2\Serializer folder (if you are using Visual C#) or the E:\Labfiles\Starter\VB\Ex2\Serializer folder (if you are using Visual Basic). This solution contains an additional class library project that is called EncryptionLib. This class contains a helper class that is called EncryptionHelper that provides static (C#) or Shared (Visual Basic) methods to generate a public/private key pair and for encrypting and decrypting data by using this key pair. 2.
3.

In Solution Explorer, right-click the Bank project, and then add a reference to the EncryptionLib project. In the Bank.cs file (if you are using Visual C#) or the Bank.vb file (if you are using Visual Basic), add a statement to bring the System.Runtime.Serialization, System.Text, System.Security.Cryptography, and EncryptionLib namespaces into scope. Note: If you are using Visual Basic, you must import the EncryptionLib.EncryptionLib namespace.

Serializing Data

6-9

Your code should resemble the statements in the following examples.


[Visual C#] using System.Runtime.Serialization; using System.Text; using System.Security.Cryptography; using EncryptionLib;

[Visual Basic] Imports System.Runtime.Serialization Imports System.Text Imports System.Security.Cryptography Imports EncryptionLib.EncryptionLib

4.

Modify the definition of the BankCustomer class, and specify that it implements the ISerializable interface. Your code should resemble the statement that appears in bold in the following examples.

[Visual C#] class BankCustomer : ISerializable { ... }

[Visual Basic] Class BankCustomer Implements ISerializable ... End Class

5.

Add a public void (Sub) method that is called GetObjectData to the BankCustomer class. This method implements the method definition that is specified in the ISerializable interface and takes two parameters: a SerializationInfo object that is called info and a StreamingContext object that is called context. Your code should resemble the statements in the following examples.

[Visual C#] public void GetObjectData(SerializationInfo info, StreamingContext context) { }

[Visual Basic] Public Sub GetObjectData(ByVal info As SerializationInfo, ByVal context As StreamingContext) _ Implements ISerializable.GetObjectData End Sub

6.

In the GetObjectData method, add code to encrypt the data in the pin field of the BankCustomer object by performing the following tasks:

6-10

Core Foundations of Microsoft .NET 2.0 Development

a.

Create an RSAParameters object that is called publicKey, and initialize it by calling the static (C#) or Shared (Visual Basic) EncryptionHelper.GetPublicKey method.

b. Create a byte array that is called pinData, and populate it with the data in the pin field of the BankCustomer object converted into an array of Unicode characters by calling the static (C#) or Shared (Visual Basic) Encoding.Unicode.GetBytes method. c. Call the static (C#) or Shared (Visual Basic) EncryptionHelper.EncryptData method. Pass the pinData and publicKey variables as parameters. Store the return value in another byte array that is called encryptedPin.

Your code should resemble the statements that appear in bold in the following examples.
[Visual C#] public void GetObjectData(SerializationInfo info, StreamingContext context) { RSAParameters publicKey = EncryptionHelper.GetPublicKey(); byte[] pinData = Encoding.Unicode.GetBytes(pin); byte[] encryptedPin = EncryptionHelper.EncryptData(pinData, publicKey); }

[Visual Basic] Public Sub GetObjectData(ByVal info As SerializationInfo, ByVal context As StreamingContext) _ Implements ISerializable.GetObjectData Dim publicKey As RSAParameters = EncryptionHelper.GetPublicKey() Dim pinData() As Byte = Encoding.Unicode.GetBytes(pin) Dim encryptedPin() As Byte = EncryptionHelper.EncryptData(pinData, publicKey) End Sub

7.

In the GetObjectData method, add code to store each of the items that is shown in the following table into the SerializationInfo object that was passed in as a parameter, by calling the AddValue method. Specify the value in the Name column as the first parameter and the object in the Item column as the second parameter.

Name
"id" "name" "balance" "pin"

Item
Id Name Balance encryptedPin

Your code should resemble the statements that appear in bold in the following examples.
[Visual C#] public void GetObjectData(SerializationInfo info, StreamingContext context) { ... info.AddValue("id", id); info.AddValue("name", name); info.AddValue("balance", balance); info.AddValue("pin", encryptedPin); }

Serializing Data

6-11

[Visual Basic] Public Sub GetObjectData(ByVal info As SerializationInfo, ByVal context As StreamingContext) _ Implements ISerializable.GetObjectData ... info.AddValue("id", id) info.AddValue("name", name) info.AddValue("balance", balance) info.AddValue("pin", encryptedPin) End Sub 8.

Build the solution, and then correct any compiler errors.

Task 2: Implement a deserialization constructor


1. Add a private constructor to the BankCustomer class. The constructor takes two parameters: a SerializationInfo object that is called info and a StreamingContext object that is called context. Your code should resemble the statements in the following examples.
[Visual C#] private BankCustomer(SerializationInfo info, StreamingContext context) { }

[Visual Basic] Private Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext) End Sub

2.

In the constructor, add code to retrieve the data from the SerializationInfo object and populate the BankCustomer object by performing the following tasks: a. Call the GetInt32 method of the info object, passing the string id as the parameter, and storing the result in the id field of the BankCustomer object.

b. Call the GetString method of the info object, passing the string name as the parameter, and storing the result in the name field of the BankCustomer object. c. Call the GetDecimal method of the info object, passing the string balance as the parameter, and storing the result in the balance field of the BankCustomer object.

d. Create a byte array that is called encryptedPin, and call the GetValue method of the info object, passing the string pin and the byte array type as parameters. Store the result in the encryptedPin object. e. Print the data in the encryptedPin object to the console window to enable you to verify that the data is encrypted when you test the solution.

Note: You can convert the data in the encryptedPin byte array into a string by using the static (Shared) Encoding.Unicode.GetString method. Your code should resemble the statements that appear in bold in the following examples.

6-12

Core Foundations of Microsoft .NET 2.0 Development

[Visual C#] private BankCustomer(SerializationInfo info, StreamingContext context) { id = info.GetInt32("id"); name = info.GetString("name"); balance = info.GetDecimal("balance"); byte[] encryptedPin = info.GetValue("pin", typeof(byte[])) as byte[]; Console.WriteLine("In deserialization constructor: Encrypted pin is {0}", Encoding.Unicode.GetString(encryptedPin)); }

[Visual Basic] Private Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext) id = info.GetInt32("id") name = info.GetString("name") balance = info.GetDecimal("balance") Dim encryptedPin() As Byte = info.GetValue("pin", GetType(Byte())) Console.WriteLine("In deserialization constructor: Encrypted pin is {0}", Encoding.Unicode.GetString(encryptedPin)) ) End Sub

3.

In the constructor, add code to decrypt the encrypted PIN by performing the following tasks: a. Create an RSAParameters object that is called privateKey, and initialize it by calling the static (C#) or Shared (Visual Basic) EncryptionHelper.GetPrivateKey method.

b. Create a byte array that is called decryptedPin, and call the static (C#) or Shared (Visual Basic) EncryptionHelper.DecryptData method. Pass the encryptedPin and privateKey objects as parameters. Store the return value in the decryptedPin variable. c. Convert the decryptedPin byte array into a string by calling the static (C#) or Shared (Visual Basic) Encoding.Unicode.GetString method. Store the result in the pin field of the BankCustomer object.

d. Print the data in the pin field to the console window to enable you to verify that the data is successfully decrypted when you test the solution. Your code should resemble the statements that appear in bold in the following examples.
[Visual C#] public void BankCustomer(SerializationInfo info, StreamingContext context) { ... RSAParameters privateKey = EncryptionHelper.GetPrivateKey(); byte[] decryptedPin = EncryptionHelper.DecryptData(encryptedPin, privateKey); pin = Encoding.Unicode.GetString(decryptedPin); Console.WriteLine("In deserialization constructor: Decrypted pin is {0}", pin); }

[Visual Basic] Private Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext) ... Dim privateKey As RSAParameters = EncryptionHelper.GetPrivateKey() Dim decryptedPin() As Byte = EncryptionHelper.DecryptData(encryptedPin,

Serializing Data

6-13

privateKey) pin = Encoding.Unicode.GetString(decryptedPin) Console.WriteLine("In deserialization constructor: Decrypted pin is {0}", pin) End Sub

4.

Build the solution, and then correct any compiler errors.

Task 3: Test the solution


1. In Solution Explorer, right-click the Serializer solution, click Set Startup Projects, and then verify that the Receiver and Sender projects are configured to start when you run the solution (if they are not, click Multiple startup projects, set the Action for both projects to Start, and set the Action for the Bank, NetworkLib, and EncryptionLib projects to None). Close the Solution Serializer Property Pages window. Start the solution without debugging, and then move the console windows for the Sender and Receiver applications so that they are both visible. In the Sender console window, verify that it displays the balance as 30, and then press ENTER. In the Receiver console window, verify that it displays an encrypted PIN (it will appear as a series of characters, many of which are unprintable and displayed as ? characters), a decrypted PIN of 8765, a customer ID of 1, a name of Fred, a balance of 30, and a new balance of 20 (after the deposit and withdraw operations have completed). Close both console windows.

2. 3. 4.

5.

Exercise 3: Serializing and Deserializing Data As XML


Task 1: Modify the BankCustomer class to support XML serialization
1. 2. Open the Serializer solution in the E:\Labfiles\Starter\CS\Ex3\Serializer folder (if you are using Visual C#) or the E:\Labfiles\Starter\VB\Ex3\Serializer folder (if you are using Visual Basic). In the Bank.cs file (if you are using Visual C#) or the Bank.vb file (if you are using Visual Basic), remove the following items from the BankCustomer class because they are not required to implement XML serialization: a. The SerializableAttribute attribute.

b. The reference to the ISerializable interface in the class definition. c. The GetObjectData method.

d. The deserialization constructor. 3. Add a default constructor to the BankCustomer class. In the body of this constructor, add code to initialize the fields in the object by using the values in the following table.

Field
id name balance pin

Value
0 String.Empty 0 String.Empty

6-14

Core Foundations of Microsoft .NET 2.0 Development

Your code should resemble the statements in the following examples.


[Visual C#] public BankCustomer() { id = 0; name = String.Empty; balance = 0; pin = String.Empty; }

[Visual Basic] Public Sub New() id = 0 name = String.Empty balance = 0 pin = String.Empty End Sub

4.

Build the solution, and then correct any compiler errors.

Task 2: Modify the Sender and Receiver applications to transmit data as XML
1. In the Sender project, in the Program.cs file (if you are using Visual C#) or the Program.vb file (if you are using Visual Basic), add a statement to the top of the file to bring the System.Xml.Serialization namespace into scope. Your code should resemble the statement in the following examples.
[Visual C#] using System.Xml.Serialization;

[Visual Basic] Imports System.Xml.Serialization

2.

In the Program class, near the end of the Main method, remove the two statements that appear in bold in the following examples.

[Visual C#] static void Main(string[] args) { ... BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(networkStream, cust); networkStream.Close(); Console.WriteLine("Customer data sent"); }

[Visual Basic] Shared Sub Main() ... Dim formatter As New BinaryFormatter() formatter.Serialize(networkStream, cust) networkStream.Close() Console.WriteLine("Customer data sent"

Serializing Data

6-15

End Sub

3.

Serialize and transmit the BankCustomer object as XML by adding code to perform the following tasks, and replace the code that you deleted in the previous step: a. Create an XmlSerializer object, specifying the type of the BankCustomer class as the parameter to the constructor.

b. Call the Serialize method of the XmlSerializer object, specifying the NetworkStream object and the BankCustomer object as parameters. Your code should resemble the statements that appear in bold in the following examples.
[Visual C#] static void Main(string[] args) { ... XmlSerializer serializer = new XmlSerializer(typeof(BankCustomer)); serializer.Serialize(networkStream, cust); networkStream.Close(); Console.WriteLine("Customer data sent"); }

[Visual Basic] Shared Sub Main() ... Dim serializer As New XmlSerializer(GetType(BankCustomer)) serializer.Serialize(networkStream, cust) networkStream.Close() Console.WriteLine("Customer data sent") End Sub

4.

In the Receiver project, in the Program.cs file (if you are using Visual C#) or the Program.vb file (if you are using Visual Basic), add a statement to the top of the file to bring the System.Xml.Serialization namespace into scope. Your code should resemble the statement in the following examples.

[Visual C#] using System.Xml.Serialization;

[Visual Basic] Imports System.Xml.Serialization

5.

In the Program class, in the Main method, remove the two statements that appear in bold in the following examples.

[Visual C#] static void Main(string[] args) { ... NetworkStream networkStream = Server.Listen(); BinaryFormatter formatter = new BinaryFormatter(); BankCustomer cust = formatter.Deserialize(networkStream) as BankCustomer; networkStream.Close(); ...

6-16

Core Foundations of Microsoft .NET 2.0 Development

[Visual Basic] Public Shared Sub Main() ... Dim NetworkStream As NetworkStream = Server.Listen() Dim formatter As New BinaryFormatter() Dim cust As BankCustomer = formatter.Deserialize(NetworkStream) NetworkStream.Close() ... End Sub

6.

Receive and deserialize the BankCustomer object from the network by performing the following tasks: a. Create an XmlSerializer object and specify the type of the BankCustomer class as the parameter to the constructor.

b. Call the Deserialize method of the XmlSerializer object, specifying the NetworkStream object as the parameter, and converting the value that is returned into a BankCustomer object. Your code should resemble the statements that appear in bold in the following examples.
[Visual C#] static void Main(string[] args) { ... NetworkStream networkStream = Server.Listen(); XmlSerializer serializer = new XmlSerializer(typeof(BankCustomer)); BankCustomer cust = serializer.Deserialize(networkStream) as BankCustomer; networkStream.Close(); ... }

Visual Basic] Public Shared Sub Main() ... Dim NetworkStream As NetworkStream = Server.Listen() Dim serializer As New XmlSerializer(GetType(BankCustomer)) Dim cust As BankCustomer = serializer.Deserialize(NetworkStream) networkStream.Close() ... End Sub

7. 8.

Build the solution, and then correct any compiler errors. In Solution Explorer, right-click the Serializer solution, click Set Startup Projects, and then verify that the Receiver and Sender projects are configured to start when you run the solution (if they are not, click Multiple startup projects, set the Action for both projects to Start, and set the Action for the Bank and NetworkLib projects to None). Close the Solution Serializer Property Pages window. In the Receiver project, in the Main method of the Program class, set a breakpoint on the statement in the following examples.

9.

Serializing Data

6-17

[Visual C#] static void Main(string[] args) { ... networkStream.Close(); ... }

[Visual Basic] Public Shared Sub Main() ... networkStream.Close() ... End Sub

10. Start the solution in debug mode, and then move the console windows for the Sender and Receiver applications so that they are both visible. 11. In the Sender console window, verify that it displays the balance as 30, and then press ENTER. Visual Studio stops at the breakpoint in the Main method of the Receiver application. 12. In Visual Studio, in the Autos window, expand the cust object. Verify that the fields and properties in this object have the values in the following table.

Field/property
balance CustomerID CustomerName Id name pin

Value
0D 0 "" 0 "" ""

Note: The fields in the BankCustomer class are all private, and the only public properties are read-only. By default, the XmlSerializer class does not serialize or deserialize private fields, and it cannot use read-only properties to populate an object. Consequently, the fields are all initialized to the values that are assigned in the default constructor. However, you can modify the way in which XML serialization occurs by implementing the IXmlSerializable interface in the BankCustomer class. 13. Stop debugging.

Task 3: Implement the IXmlSerializable interface and customize the XML


serialization process
1. In the Bank.cs file (if you are using Visual C#) or the Bank.vb file (if you are using Visual Basic), add a statement to the top of the file to bring the System.Xml.Serialization, System.Xml.Schema, and System.Xml namespaces into scope.

6-18

Core Foundations of Microsoft .NET 2.0 Development

Your code should resemble the statements in the following examples.


[Visual C#] using System.Xml.Serialization; using System.Xml.Schema; using System.Xml;

[Visual Basic] Imports System.Xml.Serialization Imports System.Xml.Schema Imports System.Xml

2.

Modify the definition of the BankCustomer class to implement the IXmlSerializable interface. Your code should resemble the statement that appears in bold in the following examples.

[Visual C#] class BankCustomer : IXmlSerializable { ... }

[Visual Basic] Class BankCustomer Implements IXmlSerializable ... End Class

3.

Add a public method that is called GetSchema to the BankCustomer class. This method takes no parameters and returns an XmlSchema object. This method implements the method definition that is specified in the IXmlSerializable interface. In the body of this method, add a statement to return a null (Nothing) object. Your code should resemble the statements in the following examples.

[Visual C#] public XmlSchema GetSchema() { return null; }

[Visual Basic] Public Function GetSchema() As XmlSchema _ Implements IXmlSerializable.GetSchema Return Nothing End Function

Note: The GetSchema method of the IXmlSerializable interface is currently reserved and should always return a null (Nothing) value.

Serializing Data

6-19

4.

Add a public void (Sub) method that is called WriteXml to the BankCustomer class. This method takes an XmlWriter object that is called writer as a parameter. This method also implements the method definition that is specified in the IXmlSerializable interface. Your code should resemble the statements in the following examples.

[Visual C#] public void WriteXml(XmlWriter writer) { }

[Visual Basic] Public Sub WriteXml(ByVal writer As XmlWriter) _ Implements IXmlSerializable.WriteXml End Sub

5.

In the WriteXml method, add code to write the names and values of the four fields, id, name, balance, and pin, to the XmlWriter object. Output these fields as XML elements by using the WriteElementString method. You convert the values in the id and balance fields to strings before you output them. Your code should resemble the statements that appear in bold in the following examples.

[Visual C#] public void WriteXml(XmlWriter writer) { writer.WriteElementString("id", id.ToString()); writer.WriteElementString("name", name); writer.WriteElementString("balance", balance.ToString()); writer.WriteElementString("pin", pin); }

[Visual Basic] Public Sub WriteXml(ByVal writer As XmlWriter) _ Implements IXmlSerializable.WriteXml writer.WriteElementString("id", id.ToString()) writer.WriteElementString("name", name) writer.WriteElementString("balance", balance.ToString()) writer.WriteElementString("pin", pin) End Sub

Note: This implementation of the WriteXml method serializes the PIN in its original unencrypted format. If you must encrypt this data, you can use the technique that was shown in Exercise 2. 6. Add a public void (Sub) method that is called ReadXml to the BankCustomer class. This method takes an XmlReader object that is called reader as a parameter. This method also implements the method definition that is specified in the IXmlSerializable interface. Your code should resemble the statements in the following examples.
[Visual C#] public void ReadXml(XmlReader reader) {

6-20

Core Foundations of Microsoft .NET 2.0 Development

[Visual Basic] Public Sub ReadXml(ByVal reader As XmlReader) _ Implements IXmlSerializable.ReadXml End Sub

7.

In the ReadXml method, add code to read each element from the XmlReader object and use these elements to populate the four fields, id, name, balance, and pin, by performing the following tasks: a. Use the Read method of the XmlReader object to skip to the first element.

b. Call the ReadStartElement method of the XmlReader object and specify the string id as the parameter to read the opening XML tag for the id element in the XML stream. c. Call the ReadContentAsInt method of the XmlReader object to read the data in the id element and convert it to an integer value. Store the value that is returned in the id field of the BankCustomer object.

d. Call the ReadEndElement method of the XmlReader object to read the closing XML tag for the id element in the XML stream. e. Repeat Steps b to d for the name, balance, and pin elements. Use the ReadString method to read the data for the name and pin elements, and the ReadContentAsDecimal method to read the data for the balance element.

Your code should resemble the statements in the following examples.


[Visual C#] public void ReadXml(XmlReader reader) { reader.Read(); reader.ReadStartElement("id"); id = reader.ReadContentAsInt(); reader.ReadEndElement(); reader.ReadStartElement("name"); name = reader.ReadString(); reader.ReadEndElement(); reader.ReadStartElement("balance"); balance = reader.ReadContentAsDecimal(); reader.ReadEndElement(); reader.ReadStartElement("pin"); pin = reader.ReadString(); reader.ReadEndElement(); }

[Visual Basic] Public Sub ReadXml(ByVal reader As XmlReader) _ Implements IXmlSerializable.ReadXml reader.Read() reader.ReadStartElement("id") id = reader.ReadContentAsInt() reader.ReadEndElement() reader.ReadStartElement("name") name = reader.ReadString()

Serializing Data

6-21

reader.ReadEndElement() reader.ReadStartElement("balance") balance = reader.ReadContentAsDecimal() reader.ReadEndElement() reader.ReadStartElement("pin") pin = reader.ReadString() reader.ReadEndElement() End Sub

8.

Build the solution, and then correct any compiler errors.

Task 4: Test the solution


1. 2. Start the solution in debug mode, and then move the console windows for the Sender and Receiver applications so that they are both visible. In the Sender console window, verify that it displays the balance as 30, and then press ENTER. Visual Studio stops at the breakpoint in the Main method of the Receiver application. 3. In Visual Studio, in the Autos window, expand the cust object. Verify that the fields and properties in this object have the values in the following table.

Field/property
balance CustomerID CustomerName id name pin 4.

Value
30D 1 "Fred" 1 "Fred" "8765"

Step through the remaining statements of the application and verify that it displays a customer ID of 1, a name of Fred, a balance of 30, and a new balance of 20 (after the deposit and withdraw operations have completed) in the console window. Close Visual Studio.

5.

Core Foundations of Microsoft .NET 2.0 Development

R-1

Resources
Contents:
Internet Links 2

R-2

Core Foundations of Microsoft .NET 2.0 Development

Internet Links
The Web sites listed below provide additional resources. CollectionBase Class DictionaryBase Class Garbage Collection IComparable Generic Interface IComparable Interface Microsoft Corporation Microsoft Developer Network Microsoft Internet Explorer Microsoft Learning Microsoft Product Support Services Microsoft Security Microsoft Visual Studio Microsoft Windows ReadOnlyCollectionBase Class Walkthrough: Installing an Event Log Component

Core Foundations of Microsoft .NET 2.0 Development

R-3

Send Us Your Feedback


You can search the Microsoft Knowledge Base for known issues at Microsoft Help and Support before submitting feedback. Search using either the course number and revision, or the course title. Note Not all training products will have a Knowledge Base article if that is the case, please ask your instructor whether or not there are existing error log entries.

Courseware Feedback
Send all courseware feedback to support@mscourseware.com. We truly appreciate your time and effort. We review every e-mail received and forward the information on to the appropriate team. Unfortunately, because of volume, we are unable to provide a response but we may use your feedback to improve your future experience with Microsoft Learning products.

Reporting Errors
When providing feedback, include the training product name and number in the subject line of your email. When you provide comments or report bugs, please include the following: Document or CD part number Page number or location Complete description of the error or suggested change

Please provide any details that are necessary to help us verify the issue.

Important All errors and suggestions are evaluated, but only those that are validated are added to the product Knowledge Base article.