Вы находитесь на странице: 1из 121

Programming .NET With Visual Basic.

NET

Contents
1. Introduction to .NET Framework
The Common Language Runtime, The Intermediate Language, Mixed Language Programming

Branching and Looping, Basic and User Defined Types, User Defined Methods. Classes and Namespaces, Interfaces and Delegates, Raising and Handling Events

2. VB.NET Language fundamentals

3. Object Oriented Programming in VB.NET

14

Exposing assemblies and types, Dynamic instantiation and Invocations, Creating and Using Attributes

4. Reflection

33

5. Multithreading

Processes and Threads, Thread synchronization and Coordination, Asynchronous Programming Model Input and Output Streams, Object Serialization, Working with XML Documents.

40

6. Streams

49

Networking Basics, Client-Server programming with TCP/IP, Multicasting with UDP.

7 Sockets

..

60

8 Distributed Programming with Remoting

Remoting Architecture, Server-activated remoting objects. Client-activated remoting objects.

68

9. Database Programming with ADO.NET

Accessing and Updating Data using ADO, Paramaterized SQL and Stored Procedures, Working with Disconnected Datasets. Windows Forms and Controls, Windows Forms and Data Access, Creating Custom Controls.

77

10 GUI Programming with Windows Forms

88

Web Forms and Server Controls, Web Forms and Data Access, Creating Web-Services.

11 Web Programming with ASP.NET

97

Using Native Dlls and COM Components, Messaging with MSMQ, Creating Serviced Components with COM+.

12. Intereop and Enterprise Services

112

Copyright 2001 K.M.Hussain <km@hussain.com>

-1-

Programming .NET With Visual Basic.NET


Chapter 1: Introduction to the .NET Framework
1.1 Common Language Runtime: The .NET Framework provides a run-time environment called the Common Language Runtime, which manages the execution of code and provides services that make the development process easier. Compilers and tools expose the runtimes functionality and enable you to write code that benefits from this managed execution environment. Code that you develop with a language compiler that targets the runtime is called managed code; it benefits from features such as crosslanguage integration, cross-language exception handling, enhanced security, versioning and deployment support, a simplified model for component interaction, and debugging and profiling services. The runtime automatically handles object layout and manages references to objects, releasing them when they are no longer being used. Objects whose lifetimes are managed in this way by the runtime are called managed data. Automatic memory management eliminates memory leaks as well as some other common programming errors. The Common Language Runtime makes it easy to design components and applications whose objects interact across languages. Objects written in different languages can communicate with each other, and their behaviors can be tightly integrated. For example, you can define a class, then, using a different language, derive a class from your original class or call a method on it. You can also pass an instance of a class to a method on a class written in a different language. This cross-language integration is possible because language compilers and tools that target the runtime use a common type system defined by the runtime, and they follow the runtimes rules for defining new types, as well as creating, using, persisting, and binding to types. Registration information and state data are no longer stored in the registry where it can be difficult to establish and maintain; instead, information about the types you define (and their dependencies) is stored with the code as metadata, making the tasks of component replication and removal much less complicated. The Common Language Runtime can only execute code in assemblies. An assembly consists of code modules and resources that are loaded from disk by the runtime. The assembly may be an executable (exe) or a library (dll). The benefits of the runtime are as follows: Performance improvements. The ability to easily use components developed in other languages. Extensible types provided by a class library. A broad set of language features.

1.2 The Intermidiate Language: Currently Microsoft provides compilers for C# (Pronounced as C Sharp), VisualBasic.NET, Managed C++ and JScript in their .NET Framework SDK. These compilers generate the so called Intermediate Language(IL) code which is then assembled to a Portable Executable(PE) code. The PE code is
Copyright 2001 K.M.Hussain <km@hussain.com> -2-

Programming .NET With Visual Basic.NET


interpreted at runtime by CLR for execution. Our first example is the traditional Hello World program written in IL. (ilhello.il) .assembly Hello{} .method public static void run() il managed{ .entrypoint ldstr Hello World.NET call void [mscorlib]System.Console::WriteLine(class System.String) ret } The above code declares Hello as an assembly(unit of code) and defines an il managed function called run. This function is marked as an entrypoint (the function which the CLR must invoke when this code is executed). Next it stores a string on the stack and invokes WriteLine method of Console class (this method displays a line on screen). For resolving name collisions all classes in .NET library are packaged in namespaces. The Console class for instance belongs to namespace called System (the root of all the namespaces in the .NET framework class library). The code for System.Console class is stored in mscorlib.dll. To execute this code, it must be first assembled to PE code using ilasm.exe utility available with .NET Framework SDK. ilasm ilhello.il The above command will create ilhello.exe which when executed will display Hello World.NET on the screen. Needless to say that coding in raw IL is pretty difficult. For actual programming any .NET compliant high level language can be used. Given below (mcpphello.cpp) is the Hello World program coded ni Managed C++ #using <mscorlib.dll> using namespace System; void main() { Console::WriteLine(S"Hello World.NET"); } Compile it using command cl /clr mcpphello.cpp The listing below shows same program coded in VisualBasic.Net(vbhello.vb). Imports System Module Hello Sub Main() Console.WriteLine(Hello World.NET) End Sub End Module Compile vbhello.vb using command:vbc vbhello.vb
Copyright 2001 K.M.Hussain <km@hussain.com> -3-

Programming .NET With Visual Basic.NET


The code below is hello program in JScript (jshello.js) import System; Console.WriteLine(Hello World.NET); Compile jshello.js using command jsc jshello.js Finally the Hello World program in C# would be (cshello.cs) using System; class Hello{ public static void Main(){ Console.WriteLine(Hello World.NET); } } And it can be compiled using CSharp compiler as: csc cshello.cs 1.3 Mixed Language Programming: The .NET framework not only allows you to write your program in any language but also allows you to use components coded in one language in a program written in another. Listed below is a component implemented in VB.NET (bizcalc.vb) for calculating the price of a depreciating asset at an end of a given period: Namespace BizCalc Public Class Asset Public OriginalCost As Double Public AnnualDepreciationRate As Single Public Function GetPriceAfter(years As Integer) As Double Dim price As Double = OriginalCost Dim n As Integer For n = 1 To years price = price * (1 - AnnualDepreciationRate/100) Next GetPriceAfter = price End Function End Class End Namespace Compile bizcalc.vb to create a dll using command: vbc /t:library bizcalc.vb A C# program below (assettest.cs) uses the above component: using BizCalc; using System; class AssetTest{ public static void Main(){ Asset ast = new Asset(); ast.OriginalCost = 10000;
Copyright 2001 K.M.Hussain <km@hussain.com> -4-

Programming .NET With Visual Basic.NET


ast.AnnualDepreciationRate = 9; Console.WriteLine("Price of asset worth 10000 after 5 years: {0}", ast.GetPriceAfter(5));

Compile the above program using: csc /r:bizcalc.dll assettest.cs

Copyright 2001 K.M.Hussain <km@hussain.com>

-5-

Programming .NET With Visual Basic.NET


Chapter 2: VB.NET Language Fundamentals
1.1 Branching and Looping: Basically a VB.Net program consists of a Module with a Sub Main (or a Class with a Shared Sub Main). When VB.Net compiler (vbc.exe) compiles a VB.Net program it marks this Main sub as the entrypoint in the generated IL code. System.Environment.GetCommandLineArgs() returns an array which contains the command line arguments passed to the program by its user (first element of this array is the program name). The program below (hello1.vb) says hello to the first name on its command line. Imports System Module Hello1() Sub Main Dim args as String() = Environment.GetCommandLineArgs() Console.WriteLine("Hello {0}", args(1)) args(0) is name of the program Console.WriteLine("Goodbye.") End Sub End Module After compiling hello1.vb execute it as : hello1 John The screen will Display Hello John Goodbye. The statement Console.WriteLine(Hello {0}, args(1)) prints a string on the console with {0} replaced by the second argument ie args(1) of WriteLine. Since args(1) is the first argument on the command line the output appears as Hello John. Now execute this program without any command line argument ie just: hello1 The output would be: Exception occurred: System.IndexOutOfRangeException: An exception of type System.IndexOutOfRangeException was thrown. at Test.Main(String[] args) The program crashes with an exception (error). This is obvious because our program blindly references args(1) without checking for its existence. Also note the next statement (the one which displays Goodbye) does not execute at all. The runtime terminates an executing program as soon as an unhandled exception occurs. If you prefer the remaining code to execute, you must handle or catch the expected exception. Our next program (hello2.vb) demonstrates how to.

Copyright 2001 K.M.Hussain <km@hussain.com>

-6-

Programming .NET With Visual Basic.NET


Imports System Module Hello2 Sub Main Dim args as String() = Environment.GetCommandLineArgs() Try Console.WriteLine("Hello {0}", args(1)) Catch e As Exception Console.WriteLine(e) End Try Console.WriteLine("Goodbye.") End Sub End Module Compile and execute hello2.vb. If you execute hello2 without a command line argument, you will see the exception message as before but now the code will continue to execute and Goodbye message will be displayed. If any exception occurs in any statement enclosed within a try block the control moves into the catch block, the code in there is executed and the code below is resumed normally. The program(hello3.vb) below displays hello to the first argument on the command line, if the argument is missing it prompts the user for a name and then displays hello to the entered name. Imports System Module Hello3 Sub Main Dim args as String() = Environment.GetCommandLineArgs() If args.Length > 1 Then Console.WriteLine("Hello {0}", args(1)) Else Console.Write("Enter your name: ") Dim name As String = Console.ReadLine() Console.WriteLine("Hello {0}", name) End If Console.WriteLine("Goodbye.") End Sub End Module The Length property of an array returns the number of elements in the array. We check the Length of the args array and make our decision using the if-else construct. Note Console.ReadLine() used to input a line of text at runtime. The program below (hello4.vb) uses a for loop and displays hello to each name on the command line. Note here the Main accepts an array as String which contains the command line arguments (args(0) contains the first argument)

Copyright 2001 K.M.Hussain <km@hussain.com>

-7-

Programming .NET With Visual Basic.NET


Imports System Module Hello4 Sub Main(args As String()) Dim n As Integer For n = 0 To args.Length - 1 Console.WriteLine("Hello {0}", args(n)) Next Console.WriteLine("Goodbye.") End Sub End Module Lastly hello5.vb uses while loop to say hello to each name on command line but does it in reverse order. Imports System Module Hello5 Sub Main Dim args as String() = Environment.GetCommandLineArgs() Dim n As Integer = args.Length - 1 Do While n > 0 Console.WriteLine("Hello {0}", args(n)) n=n- 1 Loop Console.WriteLine("Goodbye.") End Sub End Module 1.2 Basic and User Defined Types: VB.Net is a strongly typed language thus VB.Net variables must be declared with an available type and must be initialized with a value (or reference) of same type. The table below lists all the built-in types available in VB.Net VB.Net Type .NET Type Description Boolean System.Boolean Ttrue / False Byte System.Byte unsigned byte value Char System.Char a single character Short System.Int16 16 bit signed integer Integer System.Int32 32 bit signed integer Long System.Int64 64 bit signed integer Single System.Single 32 bit floating-point Double System.Double 64 bit floating-point Decimal System.Decimal a high precision double Date System.DateTime January 1, 0001 to December 31, 9999 String System.String string of characters Object System.Object a generic type
Copyright 2001 K.M.Hussain <km@hussain.com> -8-

Programming .NET With Visual Basic.NET


At compile-time the VB.Net compiler converts the VB.Net types into their corresponding .NET types mentioned in the above table. Apart from above basic types user may define his own types using Enum, Structure and Class. The program below (club1.vb) shows how and to create user-defined type Imports System Enum MemberType Lions Pythons Jackals Eagles End Enum Structure ClubMember Public Name As String Public Age As Integer Public Group As MemberType End Structure Module Test Sub Main() Dim a As ClubMember() Value types are automatically initialized a.Name = "John" a.Age = 13 a.Group = MemberType.Eagles Dim b As ClubMember = a ' new copy of a is assigned to b b.Age = 17 ' a.Age remains 13 Console.WriteLine("Member {0} is {1} years old and belongs to the group of {2}", _ a.Name, a.Age, a.Group) End Sub End Module The Enum keyword is used to declare an enumeration, a distinct type consisting of a set of named constants called the enumerator list. Every enumeration type has an underlying type, which can be any integral type except Char. The default type of the enumeration elements is Integer. By default, the first enumerator has the value 0, and the value of each successive enumerator is increased by one. For example: Enum MemberType Lions Pythons Jackals Eagles End Enum

Copyright 2001 K.M.Hussain <km@hussain.com>

-9-

Programming .NET With Visual Basic.NET


In this enumeration, Lions is 0, Pythons is 1, Jackals is 2 and Eagles is 3. Enumerators can have initializers to override the default values. For example: Enum MemberType Lions = 1 Pythons Jackals Eagles End Enum
Now Lions

is 1, Pythons is 2, Jackals is 3 and Eagles is 4.

The Structure keyword is used to create a composite type called structure . In above example ClubMember is a structure with three fields Name, Age and Group. Note the Group field is of type MemberType and as such it can take only one of the four values defined in MemberType enum. The fields are declared Public so that they are accessible outside the scope of structure block. A structure is automatically instantiated when a variable of its type is declared. In above example variable a is declared to be of type ClubMember. Next the public fields of ClubMember is set for this instance. Note how Group field of a is set: a.Group = MemberType.Eagles A structure is a value type i.e when a variable holding an instance of a structure is assigned to another variable of same type, a new copy of the instance is created and assigned. Consider statement Dim b As ClubMember = a Here b contains a copy of a. Thus change in one of the fields of b does not affect fields of a. Thus output of program prints age of John as 13. In contrast the Class is a reference type. club2.vb is similar to club1.vb except now ClubMember is a class instead of structure Imports System Enum MemberType Lions Pythons Jackals Eagles End Enum Class ClubMember Public Name As String Public Age As Integer Public Group As MemberType End Class Module Test Sub Main() Unlike value type a reference type must be instantiated with new operator Dim a As New ClubMember() a.Name = "John"
Copyright 2001 K.M.Hussain <km@hussain.com> - 10 -

Programming .NET With Visual Basic.NET


a.Age = 13 a.Group = MemberType.Eagles Dim b As ClubMember = a b refers to the instance referred by a b.Age = 17 a.Age becomes 17 Console.WriteLine("Member {0} is {1} years old and belongs to the group of {2}", _ a.Name, a.Age, a.Group) End Sub End Module Here since ClubMember is a class it must be initialized with New operator (or it will reference Nothing). Secondly The assignment Dim b As ClubMember = a assigns variable b a reference to instance referenced by variable a. i.e now a and b are both holding a reference to same instance, any change in field of b will change the corresponding field of a . Hence the output of program prints age of John as 17. An array of values can be created in VB.Net using one of the following syntaxes Dim arr As type() = {val_0, val_1, val_2, val_3, , val_n} arr.Length = n + 1 Dim arr As type() = New arr(n){val_0, val_1, val_2,,val_n} arr.Length = n + 1 Dim arr(n) As type arr.Length = n + 1 For example: Dim k As Integer() = {12, 15, 13, 19} Dim p As Double(2) p(0) = 9.3: p(1) = 12.8: p(2) = 7.7 1.3 User Defined Methods: In modular programming it is desirable to divide a program into multiple procedures The program below (maximizer.vb) implements user-defined methods to obtain maximum of double values. These methods are then called from Main. Imports System Public Class Maximizer Public Shared Overloads Function Max(p As Double, q As Double) As Double If p > q Then Max = p Else Max = q End Function Public Shared Overloads Function Max(p As Double, q As Double, r As Double) As Double Max = Max(Max(p,q),r) End Function End Class Module Test Sub Main() Console.WriteLine("Maximum of 12.8 and 19.2 is {0}", Maximizer.Max(12.8,19.2)) Console.WriteLine("Maximum of 21.7, 12.8 ,19.2 is {0}" ,Maximizer.Max(21.7,12.8,19.2)) End Sub End Module
Copyright 2001 K.M.Hussain <km@hussain.com> - 11 -

Programming .NET With Visual Basic.NET


Note the above programs defines to Max methods with different set of arguments. This is called method overloading. Both Max methods are declared Public and Shared and as such these methods can be invoked from any class using fully qualified name Maximizer.Max. (A shared method can be invoked on the class itself, no instance of the class is required) The invocation Maximizer.Max(13.5,21.7,12.8,19.2) will not compile since class Maximizer does not define any Max method which takes 4 double values as its arguments. Well, we could create one more overload of Max but then what about Max with more than 4 doubles. VB.Net solves this problem using ParamArray key word. Given below is a version of Max (you can incooperate this in maximizer.vb) which takes any number of double values as its argument Public Shared Overloads Function Max(ParamArray list() As Double) As Double Dim m As Double = list(0) Dim val As Double For Each val In list If val > m Then m = val Next Max = m End Function When user invokes this method as Max(31.3, 9.1,13.5,21.7,12.8,19.2) the arguments are passed to Max in an array of double (referred in Max as list) VB.Net also supports default values for optional parameters. The optional parameters must be at the end of the argument list. The example below (interest.vb) demonstrates this feature Imports System Public Class Investment Public Shared Function GetInterest(principal As Double, period As Integer, rate As Single, _ Optional compound As Boolean = False) As Double If compound Then GetInterest = principal * (1 + rate/100) ^ period - principal Else GetInterest = principal * period * rate / 100 End If End Function End Class Module Test Sub Main() Console.WriteLine("Simple interest on 10000 for 5 years at rate of 9% is {0:#.00}", _ Investment.GetInterest(10000,5,9)) value of fourth parameter is False Console.WriteLine("Compound interest on 10000 for 5 years at rate of 9% is {0:#.00}", _ Investment.GetInterest(10000,5,9,True)) End Sub End Module
Copyright 2001 K.M.Hussain <km@hussain.com> - 12 -

Programming .NET With Visual Basic.NET


In VB.Net the arguments are passed to a method by value i.e a copy of variable is passed instead of the original variable. Thus when a caller of a method passes some variable to the method as an argument and the method alters the value of this argument, the original value of the variable remains unchanged. VB.Net provides ByRef keyword for passing arguments by reference. swapper.vb uses this approach to swap values of variables. Imports System Public Class Swapper Swap receives two integers by reference Public Shared Sub Swap(ByRef p As Integer, ByRef q As Integer) Dim t As Integer = p p=q q=t End Sub End Class Module Test Sub Main() Dim m As Integer = 109, n As Integer = 73 Console.WriteLine("m = {0} and n = {1}",m,n) Console.WriteLine("Invoking Swap") Swapper.Swap(m,n) passing m and n by reference to Swap Console.WriteLine("m = {0} and n = {1}",m,n) End Sub End Module

Copyright 2001 K.M.Hussain <km@hussain.com>

- 13 -

Programming .NET With Visual Basic.NET


Chapter 3: Object Oriented Programming in VB.NET
1.1 Classes and Namespaces: Object Oriented Programming (OOP) is essentially programming in terms of smaller units called objects. An object oriented program is composed of one or more objects. Each object holds some data (fields or attributes) as defined by its class. The class also defines a set of functions (methods or operations) which can be invoked on its objects. Generally the data is hidden within the object and can be accessed only through functions defined by its class (encapsulation). One or more objects (instances) can be created from a class by a process called instantiation. The process of deciding which attributes and operations will be supported by an object (i.e defining the class) is called abstraction. We say the state of the object is defined by the attributes it supports and its behaviour is defined by the operations it implements. The term passing a message to an object means invoking its operation. Sometimes the set of operations supported by an object is also referred to as the interface exposed by this object. Given below is a simple class (item.vb) which defines an Item. Each item supports two attributes, its cost and the percent profit charged on it and provides some operations. Public Class Item Private _cost As Double Private _profit As Single Private Shared _count As Integer Public Sub New(Optional cost As Double = 0, Optional profit As Single = 0) If cost > 0 Then _cost = cost _profit = profit _count = _count + 1 End Sub Public Property Cost As Double Get Cost = _cost End Get Set If value > 0 Then _cost = Value End Set End Property Public Property Profit As Single Get Profit = _profit End Get Set _profit = Value End Set
Copyright 2001 K.M.Hussain <km@hussain.com> - 14 -

Programming .NET With Visual Basic.NET


End Property Property which only supports Get operation must be marked ReadOnly Public Shared ReadOnly Property Count As Integer Get Count = _count End Get End Property Overridable modifier will be explained later Public Overridable Function SellingPrice() As Double SellingPrice = _cost * (1 + _profit / 100) End Function Public Function EMI() As Double Dim amount As Double = 1.12 * SellingPrice() ' 12% interest EMI = amount / 12 End Function End Class Class Item defines two Private member variables, _cost and _profit. These variables are visible only inside the body of class Item. An outsider can only access these variables through special property methods(Cost and Profit). For example a user may create an instance of the class as follows. Dim p As Item = New Item() p._cost = 1200 compile-time error, _cost is not accessible p.Cost = 1200 works, invokes set method of Cost with value = 1200 A property implemented in a class is automatically translated by the VB.Net compiler to a pair of methods. For example the Cost property in Item class is translated by the compiler to: Public Function get_Cost() As Double get_Cost = _cost End Function Public Sub set_Cost(Value As Double) If Value > 0 Then _cost = Value End Sub Which also explains the use of undeclared variable Value in the set part of a property. A non-shared (instance) variable is stored within object of the class, thus each object has its own copy of a non-shared variable. A shared (class) variables on other hand is stored within the class and is shared by all of its objects. In Item class _count is a shared variable used for determining number of times the class has been instantiated. The value of this variable can be obtained using the Count property of the class. Note the Count
Copyright 2001 K.M.Hussain <km@hussain.com> - 15 -

Programming .NET With Visual Basic.NET


property is marked shared which means a user may access it as Item.Count. As nonshared member variables are not stored in a class, a shared method cannot directly reference a non-shared member of the class. A user instantiates a class by applying New operator to the class constructor (a sub whose name is New). A class may define multiple constructors. If no constructor is defined by the class a default (no argument) constructor is automatically made available to the class. A user may instantiate and initialize Item as follows: Dim p As Item = New Item() using default values for cost and profit equivalently Dim p As New Item() p.Cost = 1200; p.Profit = 9; Dim q As Item = New Item(1500,8) passing values for cost and profit equivalently Dim p As New Item(1500,8) Item class also provides two utility methods for calculating selling price and the equal monthly instalment (1 year scheme) for the item. The program below(itemtest1.vb) uses Item class Imports System Module ItemTest1 Sub Main() Dim pc As Item = New Item(32000, 12) Dim ac As New Item(25000, 10) Console.WriteLine("Selling price of PC is {0} and its EMI is {1}", _ pc.SellingPrice(), pc.EMI()) Console.WriteLine("Selling price of AC is {0} and its EMI is {1}", _ ac.SellingPrice(), ac.EMI()) Console.WriteLine("Number of Items created: {0}", Item.Count) End Sub End Module To compile itemtest1.vb either first compile item.vb to create a dll and then compile itemtest1.vb referencing item.dll (Note: only public classes from one assembly can be accessed from other assembly, hence class Item is declared public in item.vb) vbc /t:library item.vb creates item.dll vbc /r:item.dll itemtest1.vb creates itemtest1.exe You can also compile itemtest.vb by using command vbc itemtest1.vb item.vb creates only itemtest1.exe One of the most important feature of OOP is subclassing. It involves creation of a new (sub or derived) class based on an existing(super or base) class. The members of base
Copyright 2001 K.M.Hussain <km@hussain.com> - 16 -

Programming .NET With Visual Basic.NET


class are automatically made available to the derived class (inheritance). Subclassing enforces an is-a relationship between two classes i.e an instance of the base class can be replaced by an instance of the derived class in other words an instance of the derived class is also an instance of the base class. For example let DClass be a subclass of BClass (In VB.Net, DClass : Inherits BClass), the above rule makes it possible to write: Dim obj As BClass = New DClass( ) Thus for compiler obj is an object of BClass however at runtime obj is assigned to an instance of DClass. The opposite will obviously not compile. Though the derived class inherits all the operations supported by the base class but for some reason the derived class may want to change the behaviour (implementation) of an operation it inherits. The derived class may do so either by overriding or by hiding the operation of the base class. The difference in the two approach can be described as follows. Let DClass be a subclass of BClass and let both of these classes have their own implementations for a method called SomeOperation(). Now consider statements: Dim obj As BClass = New DClass( ) obj.SomeOperation() If DClass overrides SomeOperation() of BClass, obj.SomeOperation() will be invoked from DClass despite of the fact that obj is declared to be an object of BClass. This behaviour is called polymorphism (capability of an operation to exhibit multiple behaviours) or late-binding (determining address of operation to be invoked at run-time and not at compile-time). However if DClass hides SomeOperation() of BClass then obj.SomeOperation() will be invoked from BClass despite of the fact that obj is assigned to an instance of DClass. To override a method of base class the derived class must use Overrides modifier while redeclaring this method and this is only allowed for those methods which have been declared with Overridable modifier in the base class. To hide a method of base class the derived class must use Shadow modifier while redeclaring this method, the same method in base class may or may not be declared Overridable. Given below is an implementation of a subclass of Item called SpecialItem (specialitem.vb). This class adds a Discount property to Item and overrides its SellingPrice (Overridable) method . Public Class SpecialItem Inherits Item Private _discount As Single Public Sub New(c As Double, p As Single, d As Single) MyBase.New(c,p) If d > 0 Then _discount = d End Sub Public Property Discount As Single
Copyright 2001 K.M.Hussain <km@hussain.com> - 17 -

Programming .NET With Visual Basic.NET


return _discount End Get Set If Value > 0 Then _discount = Value End Set End Property Public Overrides Function SellingPrice() As Double dim price As Double = MyBase.SellingPrice() SellingPrice = price * (1 - _discount/100) End Function End Class Note a subclass constructor must first invoke the base class constructor, this is done by invoking MyBase.New( ) from the constructor of the subclass. If no such arrangement is made by the subclasser then the default (no argument) constructor of base class will be called (absence of which will result in a compile time error). Also note how SellingPrice method of SpecialItem invokes the same method from its base class using MyBase keyword. SpecialItem class does not reimplement the EMI method, when EMI method is invoked on an instance of SpecialItem the implementation from Item will be invoked however EMI method will properly invoke the SellingPrice method of SpecialItem. If instead of overriding SellingPrice method, SpecialItem would have hidden it (replacing overrides modifier by shadows modifier) then EMI method would call the SellingPrice of Item which would not take in account the Discount. The program below (itemtest2.vb) uses both Item and SpecialItem classes: Imports System Module ItemTest2 Sub Main() Dim pc As Item = New Item(32000, 12) Dim ac As SpecialItem = New SpecialItem(32000, 12,7) Console.WriteLine("Selling price of PC is {0} and its EMI is {1}", _ pc.SellingPrice(), pc.EMI()) Console.WriteLine("Selling price of AC is {0} and its EMI is {1}", _ ac.SellingPrice(), ac.EMI()) Console.WriteLine("Number of Items created: {0}", Item.Count) End Sub End Module Compile this program as: vbc itemtest2.vb item.vb specialitem.vb Get

Copyright 2001 K.M.Hussain <km@hussain.com>

- 18 -

Programming .NET With Visual Basic.NET


The output of the program will show that both Selling price and EMI of AC are lesser than the Selling price and EMI of PC even though the cost and profit of both the items are same. The rule of inheritance (an instance of base class can be replaced by that of derived class) allows us to place objects of SpecialItem in an array of Item objects. For example the code below is legal: Dim store As Item()= {New Item(1500, 7), New SpecialItem(22000, 10, 5), _ New SpecialItem(3500,12,7), New Item(15000,0), New Item(750, 5)} Now consider a routine to calculate total SellingPrice of all the items in the store. Dim total As Double = 0 Dim itm As Item For Each itm In store total = total + itm.SellingPrice() Next Since SellingPrice() is overridable in Item and has been overriden in SpecialItem, for store(1) and store(2) the SellingPrice() method is invoked from SpecialItem (for compiler itm is an object of Item). What if we have to calculate average discount on all the items in the store. In code similar to above itm.Discount will not compile since compiler will look for Discount property in Item class (itm is declared to be Item). The code below identifies which of the items in store are of type SpecialItem then converts these items to objects of SpecialItem and sums their discount: Dim total As Double = 0 Dim itm As Item For Each itm In store If TypeOf itm Is SpecialItem Dim sitm As SpecialItem = CType(itm, SpecialItem) total = total + sitm.SellingPrice() End If Next The TypeOf - Is construct is used to check whether a given object is an instance of a given class and the CType function is used to narrow object of base class to an object of its derived class Next consider a similar situation. A bank currently supports two types of accounts, SavingsAccount and CurrentAccount (more types are expected in future). Both of these account types support a Withdraw operation with obviously different behaviours. You dont want one of these account type to be a subclass of other since the statement SavingsAccount is a CurrentAccount or vice versa does not hold good and yet you want to place the instances of these account type in a single array. A simple solution would be to inherit both of these account types from a common base class say Account and then create an array of Account. Dim bank As Account() = {New SavingsAccount(), New CurrentAccount(), }

Copyright 2001 K.M.Hussain <km@hussain.com>

- 19 -

Programming .NET With Visual Basic.NET


Both SavingsAccount and CurrentAccount are derived from Account and support Withdraw(double amount) operation. Now lets write a routine which withdraws 500 from all the accounts in bank array. Dim acc As Account For Each acc In bank acc.Withdraw(500) Next Naturally the compiler looks for Withdraw method in Account class and runtime invokes the overriden method from the appropriate subclass. This clearly means Account class must provide an overridable Withdraw method. But it cannot provide implementation for this method because the implementation depends on the actual type of account. Such an operation (method) without behaviour (implementation) is called an abstract method. Account class will declare Withdraw to be an abstract method and the implementation to this method will be provided by SavingsAccount and CurrentAccount by overriding it. On any grounds user must not be allowed to instantiate the Account class otherwise he might invoke the codeless Withdraw method directly from the Account. Such a class which cannot be instantiated is called an abstract class. Only abstract classes can have abstract methods. However its possible to create an abstract class without any abstract method. A subclass of an abstract class must provide implementation for all the abstract method it inherits from its superclass or the subclass must itself be declared abstract. VB.Net uses MustOverride keyword to declare abstract methods and MustInherit keyword to declare abstract classes. Note a MustOverride method is automatically Overridable and must be defined in the derived class with Overrides modifier (Shadow modifer will cause a compile time error since inherited abstract methods cannot be hidden) Code below(bank.vb) contains an abstract Account class and its subclasses among other classes (explanation follows the code) Namespace Project.Bank Public Class InsufficientFundsException Inherits System.Exception End Class Public Class IllegalTransferException Inherits System.Exception End Class Public MustInherit Class Account Public MustOverride Sub Deposit(amount As Double) Public MustOverride Sub Withdraw(amount As Double) Public MustOverride ReadOnly Property Balance As Double

Copyright 2001 K.M.Hussain <km@hussain.com>

- 20 -

Programming .NET With Visual Basic.NET


Public Sub Transfer(amount As Double, other As Account) Is operator must be used to check whether two references are equal If other Is Me Then Throw New IllegalTransferException() Me.Withdraw(amount) ' same as Withdraw(amount) other.Deposit(amount) End Sub End Class Public MustInherit Class BankAccount Inherits Account Protected _balance As Double ' also accessible to any subclass Friend _id As String ' also accessible to any other class in this dll Public ReadOnly Property ID As String Get ID = _id End Get End Property Public Overrides ReadOnly Property Balance As Double Get Balance = _balance End Get End Property Public Overrides Sub Deposit(amount As Double) _balance = _balance + amount End Sub End Class Public NotInheritable Class SavingsAccount Inherits BankAccount Public Const MIN_BALANCE As Double = 500.0 Public Sub New() _balance = MIN_BALANCE _id = "S/A" End Sub Public Overrides Sub Withdraw(amount As Double) If _balance - amount < MIN_BALANCE Then Throw New _ InsufficientFundsException() _balance = _balance - amount End Sub End Class Public Class CurrentAccount
Copyright 2001 K.M.Hussain <km@hussain.com> - 21 -

Programming .NET With Visual Basic.NET


Inherits BankAccount Public const MAX_CREDIT As Double = 50000.0 Public CurrentAccount() Public Sub New() _id = "S/A" End Sub Public Overrides Sub Withdraw(amount As Double) If _balance - amount < -MAX_CREDIT Then Throw New _ InsufficientFundsException() _balance = _balance - amount End Sub End Class Public NotInheritable Class Banker Private Shared self As Banker Private nextid As Integer Private Sub New() nextid = 1001 End Sub Public Shared Function GetBanker() As Banker If self Is Nothing Then self = New Banker() GetBanker = self End Function Public Function OpenAccount(acctype As String) As BankAccount Dim acc As BankAccount If acctype = "SavingsAccount" Then acc = New SavingsAccount() Else If acctype = "CurrentAccount" Then acc = New CurrentAccount() Else OpenAccount = Nothing Exit Function End If End If acc._id = acc._id & nextid nextid = nextid +1 OpenAccount = acc End Function End Class End Namespace Here are important points to note about bank.vb
Copyright 2001 K.M.Hussain <km@hussain.com> - 22 -

Programming .NET With Visual Basic.NET


1. All the classes are placed in a namespace Project.Bank. Namespaces are used to organize related classes into a group and also to protect classes from name conflicts. A user may refer to Account class in namespace Project.Bank by its fully qualified name, i.e Project.Bank.Account or he might first mention Imports Project.Bank and then refer to Account class as Account. Classes InsufficientFundsException and IllegalTransferException represent exceptions since they are derived from System.Exception. Instances of subclasses of System.Exception can be used along with Throw keyword within a method body to trigger an error situation. Throwing of an exception immediately aborts the method and the control moves into the callers Catch block for that exception. The Account class is an abstract class. It defines two abstract methods and an abstract get property for Balance. It also provides the Transfer method for transferring an amount from an Account on which Transfer was invoked (which is referred in body of Transfer using the Me keyword) to another Account passed as the other argument. If the source of transfer is exactly same object as its destination, the Transfer method throws IllegalTransferException. The BankAccount class is derived from Account class but does not implement its Withdraw method and as such has been declared abstract. This class defines two member variables _balance and _id. _balance is declared as Protected this means apart from the currrent class this member is accessible to the subclasses also. The _id is declared Friend which means apart from the current class this member is accessible to any other class in the current assembly (Protected Friend is also allowed and it means apart from the current class the member will be accessible only to the subclasses or other classes in the current assembly) SavingsAccount and CurrentAccount are two concrete implementations of Account. Both of these classes are derived from BankAccount and are declared NotInheritable which means that these classes cannot be further subclassed. SavingsAccount defines a double constant called MIN_BALANCE and as it is public any user may access its value as SavingsAccount.MIN_BALANCE (constants in VB.Net are shared) though the value cannot be modified. Member _balance declared in BankAccount is accessible in SavingsAccount because SavingsAccount is subclass of BankAccount and member _id is accessible because SavingsAccount and BankAccount belong to same assembly. Finally Banker class is an example of a singleton (only one instance) factory (creates instances of other classes) class. Its main purpose is to create instances of SavingsAccount and CurrentAccount and update their ID. The only constructor that Banker has is declared private and as such it can only be instantiated from Banker class. The shared method GetBanker allows an outsider to obtain a reference to the one and only one instance (self) of Banker which is initialized when Banker.GetBanker is invoked for the first time. The OpenAccount method returns an Account of the specified type, in case of a bad type it returns Nothing.

2.

3.

4.

5.

6.

The program below (banktest1.vb) uses the above classes.

Copyright 2001 K.M.Hussain <km@hussain.com>

- 23 -

Programming .NET With Visual Basic.NET


Imports Project.Bank Imports System Module BankTest1 Sub Main() Dim b As Banker = Banker.GetBanker() Dim cust As BankAccount = b.OpenAccount("SavingsAccount") Dim vend As BankAccount = b.OpenAccount("CurrentAccount") cust.Deposit(12000) Console.Write("Enter amount to transfer: ") Dim amt As Double = Double.Parse(Console.ReadLine()) try cust.Transfer(amt,vend) catch e As InsufficientFundsException Console.WriteLine("Transfer Failed") End Try Console.WriteLine("Customer: Account ID: {0} and Balance: {1}",cust.ID, _ cust.Balance) Console.WriteLine("Vendor: Account ID: {0 } and Balance: {1}",vend.ID, _ vend.Balance) End Sub End Module 1.2 Interfaces and Delegates: Under VB.Net a class can be derived from one and only base class. If a class does not specify its superclass then the class is automatically derived from System.Object. But what if a class need to inherit multiple characteristics. Consider an abstract class called Profitable given below Public MustInherit Class Profitable Public MustOverride Sub AddInterest(rate As Single, period As Integer) End Class A bankable entity (not necessarily an account) which provides interest must be derived from above class and provide an implementation for AddInterest. What if SavingsAccount wants to support interest? It cannot subclass both BankAccount and Profitable. To resolve this issue, VB.Net introduces a concept of Interface. An interface can be regarded to be a pure abstract class as it can only contain abstract operations (abstract classes may contain non-abstract operations also). A class may inherit from one and only one other class but can inherit from multiple interfaces. When a class inherits from an interface it must provide implementations for all the methods in that interface or the class must be declared abstract. The above Profitable may now be defined as an interface (add this to bank.vb). Public Interface IProfitable Sub AddInterest(rate As Single, period As Integer) End Interface
Copyright 2001 K.M.Hussain <km@hussain.com> - 24 -

Programming .NET With Visual Basic.NET


Methods declared in an interface are Public and MustOverride by default and when a class implements a method which it inherits from an interface the override modifier is automatically understood. The SavingsAccount class (update bank.vb) can inherit from both BankAccount and IProfitable as follows: Public NotInheritable Class SavingsAccount Inherits BankAccount Implements IProfitable Public Const MIN_BALANCE As Double = 500.0 Public Sub New() _balance = MIN_BALANCE _id = "S/A" End Sub Public Overrides Sub Withdraw(amount As Double) If _balance - amount < MIN_BALANCE Then Throw New _ InsufficientFundsException() _balance = _balance - amount End Sub Public Sub AddInterest(rate As Single, period As Integer) Implements _ Profitable.AddInterest _balance = _balance * (1 + rate * period /100) End Sub End Class The statement Dim acc As IProfitable = New SavingsAccount() compiles since SavingsAccount implements IProfitable however Dim acc As IProfitable = New CurrentAccount() will cause a compile-time error. Next consider floowing two interfaces Public Interface ITaxable Sub Deduct() End Interface Public Interface IFinable Sub Deduct() End Interface Now suppose we would like our CurrentAccount class to implement both of these interfaces. As both of these interfaces contain members with same signature they must be implemented as follows

Copyright 2001 K.M.Hussain <km@hussain.com>

- 25 -

Programming .NET With Visual Basic.NET


Public Class CurrentAccount Inherits BankAccount Implements ITaxable, IFinable Public const MAX_CREDIT As Double = 50000.0 Public CurrentAccount() Public Sub New() _id = "S/A" End Sub Public Overrides Sub Withdraw(amount As Double) If _balance - amount < -MAX_CREDIT Then Throw New _ InsufficientFundsException() _balance = _balance - amount End Sub Private Sub ITaxable_Deduct() Implements ITaxable.Deduct If _balance > 50000 Then _balance = _balance * 0.95 End Sub Private Sub IFinable_Deduct() Implements IFinable.Deduct If _balance < -0.5*MAX_CREDIT Then _balance = _balance * 0.98 End Sub End Class The member methods are implemented with names other than the names used in interface declaration (as a convention we have used name InterfaceName_MethodName for implementation though this is not necessary). Note the members are implemented with Private access-modifier which means they are oly accessible through an interface instance. For example the right way to invoke ITaxable_Deduct() is Dim acc As ITaxable = New CurrentAccount() declare acc to be an instance of ITaxable acc.Deduct() this will invoke ITaxable_Deduct() implemented in CurrentAccount VB.Net defines Delegate keyword, which can be used to receive a reference to a method. It is very useful in situations where a class needs to invoke a method implemented by its user (such methods are also called callback methods). Our next class (fixeddeposit.vb) FixedDeposit uses a delegate to invoke a method passed by its user for determining the rate of interest. Imports System Namespace Project.Bank Scheme represents a method which takes a Double and an Integer as its arguments returns a Single Public Delegate Function Scheme(principal As Double, period As Integer) As Single
Copyright 2001 K.M.Hussain <km@hussain.com> - 26 -

Programming .NET With Visual Basic.NET


Public Class FixedDeposit Private _deposit As Double Private _duration As Integer Private InterestRate As Scheme InterestRate is a method of type Scheme Shared Sub New() Class initializer Console.WriteLine(<FixedDeposit class initialized>) End Sub Public Sub New(p As Double, n As Integer, s As Scheme) _deposit = p _duration = n InterestRate = s Console.WriteLine(<FixedDeposit instance initialized>) End Sub Public ReadOnly Property Deposit As Double Get Deposit = _deposit End Get End Property Public ReadOnly Property Duration As Integer Get Duration = _duration End Get End Property Public ReadOnly Property Amount As Double Get Dim rate As Single = InterestRate.Invoke(deposit,duration) ' callback users ' method Amount = _deposit * (1 + rate/100) ^ _duration End Get End Property Protected Overrides Sub Finalize() Destructor Console.WriteLine(<FixedDeposit instance destroyed>) End Sub End Class End Namespace The above class contains a shared constructor or a class initializer. This constructor is invoked only once when the class is loaded by CLR. The static constructor is public by default and as it is automatically invoked by CLR it cannot accept arguments. This class
Copyright 2001 K.M.Hussain <km@hussain.com> - 27 -

Programming .NET With Visual Basic.NET


also overrides the Finalize() method inherited from System.Object This metod is automatically invoked whenever the memory allocated to an instance of the class is released back to the system by CLR (through garbage collection mechanism). The program below (fdtest.vb) makes use of above class: Imports Project.Bank Imports System Class FDTest Public Shared Function MyRate(p As Double, n As Integer) As Single If n < 5 Then MyRate = 9 Else MyRate = 11 End Function Public Function HisRate(p As Double, n As Integer) As Single If p < 20000 Then HisRate = 8 Else HisRate = 10 End Function Shared Sub Main() Dim myfd As New FixedDeposit(25000,7,AddressOf MyRate) Dim hisfd As New FixedDeposit(25000,7, AddressOf New FDTest().HisRate) Console.WriteLine("I get {0:#.00}",myfd.Amount) Console.WriteLine("He gets {0:#.00}",hisfd.Amount) End Sub End Class In the Main method an instance of Scheme delegate is created using method MyRate and is passed to FixedDeposit constructor using AddressOf operator which saves the reference in its InterestRate member variable. When Amount get property of myfd is called MyRate is invoked by FixedDeposit. Note how a Scheme delegate is created using a non-shared method HisRate. 1.3 Rasing and Handling Events: An event is a notification sent by a sender object to a listener object. The listener shows an interest in receiving an event by registering an event handler method with the sender. When event occurs the sender invokes event handler methods of all of its registered listeners. In our bank.vb the Account class provides a Transfer method for transferring funds from one account to another. We would like target account to raise an event whenever a transfer is successful. Replace the Account class in bank.vb by following code: Public MustInherit Class Account Public Event FundsTransferred(source As Account, amount As Double) declares the event Public MustOverride Sub Deposit(amount As Double) Public MustOverride Sub Withdraw(amount As Double) Public MustOverride ReadOnly Property Balance As Double
Copyright 2001 K.M.Hussain <km@hussain.com> - 28 -

Programming .NET With Visual Basic.NET


Private Sub OnFundsTransferred(source As Account, amount As Double) RaiseEvent FundsTransferred(source, amount) fires the event End Sub Public Sub Transfer(amount As Double, other As Account) If other Is Me Then Throw New IllegalTransferException() Me.Withdraw(amount) ' same as Withdraw(amount) other.Deposit(amount) other.OnFundsTransferred(Me, amount) End Sub End Class First an called TransferHandler is declared. In the Transfer method the event is fired on the other account. The program below (banktest2.vb) shows how to register an event handler and handle the event. Imports Project.Bank Imports System Module BankTest2 The Event handling method Sub vend_FundsTransferred(source As Account, amount As Double) Console.WriteLine("Vendor received amount {0}", amount) End Sub Sub Main() Dim b As Banker = Banker.GetBanker() Dim cust As BankAccount = b.OpenAccount("SavingsAccount") Dim vend As BankAccount = b.OpenAccount("CurrentAccount") cust.Deposit(12000) Register the event handler with FundsTransferred event of vend AddHandler vend.FundsTransferred, AddressOf vend_FundsTransferred Console.Write("Enter amount to transfer: ") Dim amt As Double = Double.Parse(Console.ReadLine()) Try cust.Transfer(amt,vend) Catch e As InsufficientFundsException Console.WriteLine("Transfer Failed") End Try Console.WriteLine("Customer: Account ID: {0} and Balance: {1}",cust.ID, _ cust.Balance) Console.WriteLine("Vendor: Account ID: {0 } and Balance: {1}",vend.ID, _ vend.Balance) End Sub End Module

Copyright 2001 K.M.Hussain <km@hussain.com>

- 29 -

Programming .NET With Visual Basic.NET


The event handler is registered by using AddHandler. This allows registeration of multiple event handler methods. When event occurs all the handlers will be invoked. VB.Net also provides an alternative approach for handling events raised by objects. The next program (banktest3.vb) demonstrates this approach Imports Project.Bank Imports System Module BankTest3 Dim cust As BankAccount Dim WithEvents vend As BankAccount we are interested in handling events raised by vend This method handles FundsTransferred event raised by vend Sub vend_FundsTransferred(source As Account, amount As Double) Handles _ vend.FundsTransferred Console.WriteLine("Vendor received amount {0}", amount) End Sub Sub Main() Dim b As Banker = Banker.GetBanker() cust = b.OpenAccount("SavingsAccount") vend = b.OpenAccount("CurrentAccount") cust.Deposit(12000) Console.Write("Enter amount to transfer: ") Dim amt As Double = Double.Parse(Console.ReadLine()) Try cust.Transfer(amt,vend) Catch e As InsufficientFundsException Console.WriteLine("Transfer Failed") End Try Console.WriteLine("Customer: Account ID: {0} and Balance: {1}",cust.ID, _ cust.Balance) Console.WriteLine("Vendor: Account ID: {0 } and Balance: {1}",vend.ID, _ vend.Balance) End Sub End Module One last important feature of VB.Net language is the indexer. Indexer allows an instance to behave like an array i.e it allows notation of type instance(index) to be used to access certain information. The class RandomList mentioned below (randomlist.vb) generates a list of first n consecative integers and provides an operation to shuffel this list. It provides an indexer for retrieving the elements of the list. It also supports features which would allow a user to iterate through the list using For-Each statement.

Copyright 2001 K.M.Hussain <km@hussain.com>

- 30 -

Programming .NET With Visual Basic.NET


Imports System Public Class RandomList Private list As Integer() Private _current As Integer Public Sub New(size As Integer) list = New Integer(size){} Dim i As Integer For i = 0 To size list(i) = i+1 Next End Sub Public Sub Shuffle() Dim gen As New Random() Dim i As integer For i = list.Length To 2 Step -1 Dim j As Integer = gen.Next(0,i) random integer between 0 and i Dim t As Integer = list(i-1) list(i-1)=list(j) list(j) = t Next End Sub Public ReadOnly Property Count As Integer Get Count = list.Length End Get End Property support for indexed default property Public ReadOnly Default Property Element(index As Integer) As Integer Get Element = list(index) End Get End Property support for For-Each iteration statement Public Function MoveNext() As Boolean _current = _current + 1 MoveNext = _current < list.Length End Function Public ReadOnly Property Current As Integer Get Current = list(_current)
Copyright 2001 K.M.Hussain <km@hussain.com> - 31 -

Programming .NET With Visual Basic.NET


End Get End Property Public Function GetEnumerator() As RandomList _current = -1 GetEnumerator = Me End Function Shared Sub Main() Dim list As New RandomList(9) Dim k As Integer For k = 0 To list.Count-1 Console.WriteLine(list(k)) Next list.Shuffle() Console.WriteLine() For Each k In list Console.WriteLine(k) Next End Sub End Class Class RandomList accepts a size through its constructor and populates an internal array with consecative integers. The Shuffle method shuffles the internal array by swapping elements at random positions. Next it provides the indexer which allows reading of elements using () notation i.e the third element of a RandomList instance rl can be accessed by rl(2). Any class which supports iteration through foreach statement must provide a method called GetEnumerator() which returns a reference to an instance of a class which provides a get property called Current and a method called MoveNext() which returns a boolean value. Since RandomList itself provides Current property and MoveNext() method its GetEnumerator returns its own reference (Me). The For Each statement first obtains a reference from GerEnumerator() and continuously returns the value returned by the Current property of the reference as long as its MoveNext() method returns true. The Main subroutine shows how to iterate through the RandomList instance using the indexer and For-Each statement.

Copyright 2001 K.M.Hussain <km@hussain.com>

- 32 -

Programming .NET With Visual Basic.NET


Chapter 4: Reflection
1.1 Exposing Assemblies and Types: Exposing and utilizing types at runtime is called Reflection. The type of an object is stored as an instance of System.Type class the reference to which can be obtained using one of the following methods. 1. From the declaration type: If declaration Dim var As AType is legal then System.Type representing AType can be obtained using GetType operator as: Dim t As Type = GetType(AType) 2. From an instance: Type of an instance obj can be obtained using GetType method defined in System.Object as: Dim t As Type = obj.GetType() 3. From the type name within current assembly: System.Type offers a shared method called GetType to obtain a Type from a fully qualified name of the type. The name will be searched in the current assembly. Dim t As Type = Type.GetType( FullyQualifiedTypeName ) 4. From the type name within any assembly: First load the assembly and obtain a reference to it. This reference can be used to obtain the Type with given name: Dim asm As System.Reflection.Assembly = _ System.Reflection.Assembly.LoadFrom( AssemblyName ) Dim t As Type = asm.GetType( FullyQualifiedTypeName ) Alternatively one could also use: Dim t As Type = _ Type.GetType( FullyQualifiedTypeName,AssemblyName ) The program below(showtypes.vb) displays all the Types defined in an assembly whose name is passed in first command line argument: Imports System Imports System.Reflection Module ShowTypes Sub Main(args() As String) Assembly is also a keyword in VB.Net so [Assembly] must be used Dim asm As [Assembly] = [Assembly].LoadFrom(args(0)) Dim types As Type() = asm.GetTypes() Dim t As Type For Each t in types Console.WriteLine(t) Next End Sub End Module

Copyright 2001 K.M.Hussain <km@hussain.com>

- 33 -

Programming .NET With Visual Basic.NET


Execute this program using command line: showmethods anyassembly.dll Pass complete path to any .NET exe or dll to see the types declared in it. The second program (showmembers.vb) takes assembly name and type name as its command line arguments and displays all the members defined in that type of that assembly. Imports System Imports System.Reflection Module ShowTypes Sub Main(args() As String) Dim asm As [Assembly] = [Assembly].LoadFrom(args(0)) Dim t As Type = asm.GetType(args(1)) Dim members As MemberInfo() = t.GetMembers() Dim m As MemberInfo For Each m in members Console.WriteLine(m) Next End Sub End Module 1.2 Dynamic Instantiation and Invocations: Using reflection it is also possible to create an instance of a class and invoke its member methods at runtime. This feature can be used to write more generic (dynamically extensible) programs which receive names of classes at runtime. Consider one such program which is supposed to calculate the amount that a user will receive if he invests a certain amount for a certain period of time in a certain investment policy. The rate of interest will depend on the policy in which he invests. Our program must take the name of the class which implements policy at runtime and perform the calculations. The code below(invpol.vb) provides implementations for various policies: Public Interface Policy Function Rate(principal As Double, period As Integer) As Single End Interface Public Class BronzePolicy Implements Policy Public Function Rate(p As Double, n As Integer) As Single Implements Policy.Rate Rate = 7 End Function End Class Public Class SilverPolicy Implements Policy Public Function Rate(p As Double, n As Integer) As Single Implements Policy.Rate If p < 25000 Then Rate = 8 Else Rate = 10
Copyright 2001 K.M.Hussain <km@hussain.com> - 34 -

Programming .NET With Visual Basic.NET


End Function End Class Public Class GoldPolicy Public Function Rate(p As Double, n As Integer) As Single If n < 5 Then Rate = 9 Else Rate = 11 End Function End Class Public Class PlatinumPolicy Implements Policy Public Function Rate(p As Double, n As Integer) As Single Implements Policy.Rate If p < 50000 Then Rate = 10 Else Rate = 12 If n >= 3 Then Rate = Rate + 1 End Function End Class Each policy provides a Rate method which returns the rate of interest for a given principal and for a given period. All policy classes implement Policy interface except GoldPolicy which defines Rate without implementing policy. Compile invpol.vb to create invpol.dll: vbc /t:library invpol.vb Here is our investment program (investment.vb) which accepts principal, period and policy name as its arguments and displays the amount the investor will receive: Imports System Imports System.Reflection Module Investment Sub Main(args() As String) Dim p As Double = Double.Parse(args(0)) Dim n As Integer = Int32.Parse(args(1)) Dim t As Type = Type.GetType(args(2)) Dim pol As Policy = CType(Activator.CreateInstance(t), Policy) Dim r As Single = pol.Rate(p,n) Dim amount As Double = p*(1+r/100) ^ n Console.WriteLine("You will get {0:#.00}",amount) End Sub End Module After obtaining the reference to Policy Type (t) we instantiate the type dynamically using Activator.CreateInstance(t). For this to work the Type t must not be an abstract class and must support a public default constructor. Activator.CreateInstance method declares to return the generic System.Object which we cast to Policy in order to invoke the Rate method. This cast will work only if the type t implements Policy otherwise it will cause an InvalidCastException. Compile investment.vb: vbc /r:invpol.dll investment.vb Execute: investment 65000 6 SilverPolicy,invpol
Copyright 2001 K.M.Hussain <km@hussain.com> - 35 -

Programming .NET With Visual Basic.NET


The amount will be displayed. If GoldPolicy is used an exception will occur since GoldPolicy does not implement policy. GoldPolicy does provide Rate method but to invoke it we must cast the object returned by Activator.CreateInstance to Policy which fails. Our next program (investmentdmi.vb) calls the Rate method using the so called Dynamic Method Invocation. As this approach calls the method by its name the conversion is not required, this version of investment program will work with all the policy classes in invpol.dll. Imports System Imports System.Reflection Module InvestmentDMI Sub Main(args As String()) Dim p As Double = Double.Parse(args(0)) Dim n As Integer = Int32.Parse(args(1)) Dim t As Type = Type.GetType(args(2)) Dim pol As object = Activator.CreateInstance(t) no conversion Dim ptypes As Type() = {GetType(Double),GetType(Integer)} Look for a method called Rate (Double, Integer) Dim m As MethodInfo = t.GetMethod("Rate",ptypes) Store the values of the argument in an array of object which can hold values of any type Dim pvalues As Object() = {p, n} Invoke the Rate method on pol instance with arguments stored in pvalues. Convert the return value of type object to Single Dim r As Single = CType(m.Invoke(pol, pvalues), Single) Dim amount As Double = p*(1+r/100) ^ n Console.WriteLine("You will get {0:#.00}",amount) End Sub End Module Compile investmentdmi.vb using command vbc investmentdmi.vb It will work with all four policies in invpol.dll 1.3 Creating and using Attributes: VB.Net provides a mechanism for defining declarative tags, called attributes, which you can place on certain entities in your source code to specify additional information. The information that attributes contain can be retrieved at run time through reflection You can use predefined attributes or you can define your own custom attributes. Attributes can be placed on most any declaration (though a specific attribute might restrict the types of declarations on which it is valid). Syntactically, an attribute is specified by placing the name of the attribute, enclosed in arrow brackets, in front of the declaration of the entity (class, member method etc) to which it applies, For example: <Marker1(arglist), Marker2(argslist)> entity declaration
Copyright 2001 K.M.Hussain <km@hussain.com> - 36 -

Programming .NET With Visual Basic.NET


Where Marker1 and Marker2 are attributes which can be applied to the entity that follows. You can create your own custom attributes by defining an attribute class, a class that derives directly or indirectly from System.Attribute. The Attribute class must be always be named as SomethingAttribute so that <Something( )> can be used to apply this attribute and this class must be marked with the predefined attribute AttributeUsage which specifies the type of the declaration to which this attribute can be applied. The listing below (execattr.vb) defines two attributes Executable and Authorize. Imports System applies only to a class <AttributeUsage(AttributeTargets.Class)> Public Class ExecutableAttribute Inherits Attribute End Class applies to a method of a class <AttributeUsage(AttributeTargets.Method)> Public Class AuthorizeAttribute Inherits Attribute Private _pwd As String Public Sub New(pwd As String) _pwd = pwd End Sub Public ReadOnly Property Password As String Get Password = _pwd End Get End Property End Class Next sample (executer.vb) is a program which takes a name of class at runtime and invokes its Execute() method it does so only if the specified class has been marked with Executable attribute. It also checks if the Execute method of the class has been marked with Authorize attribute and if so it retrieves the password passed through the attribute and forces the user to authorize before invoking Execute() method of the class. Imports System Imports System.Reflection Module Executer Sub Main() Dim args As String() = Environment.GetCommandLineArgs() Dim t As Type = Type.GetType(args(1)) args(1) = ClassName,AssemblyName check if the class supports Executable attribute (False -> dont search in ' inheritance chain) If Not t.IsDefined(GetType(ExecutableAttribute), False)
Copyright 2001 K.M.Hussain <km@hussain.com> - 37 -

Programming .NET With Visual Basic.NET


Console.WriteLine("{0} is not executable", args(1)) Exit Sub

End If lookup for a Execute method which takes no arguments Dim m As MethodInfo = t.GetMethod("Execute") obtain AuthorizeAttribute if supported by this method Dim auth As AuthorizeAttribute = CType(Attribute.GetCustomAttribute(m, _ GetType(AuthorizeAttribute)),AuthorizeAttribute) If Not auth Is Nothing Console.Write("Enter Password: ") If Not Console.ReadLine() = auth.Password Console.WriteLine("Password is incorrect") Exit Sub End If End If m.Invoke(Nothing, Nothing) Execute is a static method and takes no arg End Sub End Module Compile above program using command: vbc executer.vb execattr.vb Apps.vb provides few classes for testing executer program. Imports System <Executable> Public Class AClock Public Shared Sub Execute() Console.WriteLine("Its {0}", DateTime.Now) End Sub End Class <Executable> Public Class AGame <Authorize("tiger")> Public Shared Sub Execute() Dim r As Random = New Random() Dim p As Integer = r.Next(1,10) Console.Write("Guess a number between 1 and 10: ") Dim q As Integer = Int32.Parse(Console.ReadLine()) If p = q Then Console.WriteLine("You won!!!") Else Console.WriteLine("You lost, correct number was {0}",p) End If End Sub End Class Public Class AMessage Public Shared Sub Execute() Console.WriteLine("Hello World!") End Sub End Class
Copyright 2001 K.M.Hussain <km@hussain.com> - 38 -

Programming .NET With Visual Basic.NET


Classes AClock and AGame are both marked with Executable attribute and so executer will execute them. Since Execute method of AGame is marked with Authorize attribute, executer will prompt the user for password if his password matches with tiger AGame.Execute will be invoked. AMessage class will not be executed as it is not marked with the Executable attribute. Compile apps.vb to create a dll using command vbc /t:library apps.vb execattr.vb Test executer: executer AClock,apps executer AGame,apps executer AMessage,apps will show the current time will prompt for password will not execute

Copyright 2001 K.M.Hussain <km@hussain.com>

- 39 -

Programming .NET With Visual Basic.NET


Chapter 5: Multithreading
1.1 Processes and Threads: In this chapter we discuss concurrent programming ie executing multiple blocks of code simultaneously. Lets first see how to launch a new process from a .NET executable.The program below (processtest.vb) launches notepad.exe and asynchronously counts up from 1 to 10000 and then destroys the notepad process. Imports System Imports System.Diagnostics for Process class Module ProcessTest Sub Main() Dim p As Process = Process.Start("notepad.exe") Dim i As Integer For i = 1 To 10000 Console.WriteLine(i) Next p.Kill() End Sub End Module Compile: vbc /r:system.dll processtest.vb When you execute processtest you will notice how two processes (processtest.exe and notepad.exe) can execute concurrently. Threads are parts of same process which execute concurrently. In .NET Base Class Library(BCL) the System.Threading namespace provides various classes for executing and controlling threads. The program below (threadtest1.vb) executes two loops simultaneously using threads. Imports System Imports System.Threading for Thread class Module ThreadTest1 Sub SayHello() Dim i As Integer = 1 Do Console.WriteLine("Hello {0}",i) i =i +1 Loop End Sub Sub Main() Dim t As Thread = New Thread(AddressOf SayHello) t.Start() Dim i As Integer For i = 1 To 10000
Copyright 2001 K.M.Hussain <km@hussain.com> - 40 -

Programming .NET With Visual Basic.NET


Next t.Abort() End Sub End Module Console.WriteLine("Bye {0}",i)

We create a Thread instance by passing it an object of ThreadStart delegate which contains a reference to our SayHello method. When the thread is started (using its Start method) SayHello will be executed in an asynchronous manner. Compile and run the above program, you will notice series of Hello and Bye messages appearing on the screen. When the program is executed the CLR launches the main thread (the one which executes Main method) within the body of main we launch thread t (which executes SayHello method). While thread t goes in an infinite loop printing Hello on the console the main thread concurrently prints Bye on the console 10000 times, after which the main thread kills thread t invoking its Abort() method before it itself terminates. If thread t would not be aborted in this fashion it would continue to execute even after the main thread terminates. We could also mark thread t as Back Ground thread, in which case t would automatically abort soon after the last foreground thread (main thread in this case) would terminate. The program below (threadtest2.vb) shows how: Imports System Imports System.Threading Module ThreadTest2 Sub SayHello() Dim i As Integer = 1 Do Console.WriteLine("Hello {0}",i) i =i +1 Loop End Sub Sub Main() Dim t As Thread = New Thread(AddressOf SayHello) t.IsBackground = True t.Start() Dim i As Integer For i = 1 To 10000 Console.WriteLine("Bye {0}",i) Next End Sub End Module Just running threads simultaneously is not the whole thing, we sometimes need to control them In program below (sleepingthread.vb) the main thread prints Hello 15 times while at the same time another thread t prints Welcome 5 times followed by a Goodbye . The programs sees to it that the Goodbye message printed by thread t is always the last message of the program. To achieve this control thread t puts itself to
Copyright 2001 K.M.Hussain <km@hussain.com> - 41 -

Programming .NET With Visual Basic.NET


sleep for 5 second after printing its Welcome messages, this time is big enough for the main thread to print its Hello messages. After doing so the main threads interrupts the sleeping thread t, such an interruption causes the Sleep method to throw ThreadInterruptedException. Thread t handles this exception and finally prints Goodbye message. Imports System Imports System.Threading Module SleepingThread Sub Run() Dim i As Integer For i = 1 To 5 Console.WriteLine("Welcome {0}",i) Next Try Thread.Sleep(5000) Catch e As ThreadInterruptedException Console.WriteLine("Sleep Interrupted") End Try Console.WriteLine("Goodbye") End Sub Sub Main() Dim t As Thread = New Thread(AddressOf Run) t.Start() Dim i As Integer For i = 1 To 15 Console.WriteLine("Hello {0}",i) Next t.Interrupt() End Sub End Module Our next program (joiningthread.vb) reverses the roles of main thread and thread t. Now the main thread prints Welcome messages followed by Goodbye message and thread t prints Hello messages. After printing all the Welcome messages and before printing Goodbye, the main thread joins itself to thread t (i.e blocks itself until t terminates) . Imports System Imports System.Threading Module JoiningThread Sub Run() Dim i As Integer For i = 1 To 15 Console.WriteLine("Hello {0}",i) Next
Copyright 2001 K.M.Hussain <km@hussain.com> - 42 -

Programming .NET With Visual Basic.NET


End Sub Sub Main() Dim t As Thread = New Thread(AddressOf Run) t.Start() Dim i As Integer For i = 1 To 5 Console.WriteLine("Welcome {0}",i) Next t.Join() Console.WriteLine("Goodbye") End Sub End Module 1.2 Thread Sysnchronization and Coordination: In program below (syncthreads.vb) two threads simultaneously execute Withdraw method on the same instance of Account class. Though this method checks for overwithdrawl but as both the threads are simultaneously checking whether there is enough balance in the account before deducting, there is quite a possiblity of over withdrawl Imports System Imports System.Threading Class Account Private balance As Double = 5000 Sub Withdraw(amount As Double) Console.WriteLine("Withdrawing {0}",amount) If amount > balance Then Throw New Exception("Insufficient Funds") Thread.Sleep(10) some other stuff balance -= amount Console.WriteLine("Balance {0}",balance) End Sub End Class Module SyncThreads Dim acc As New Account Sub Run() acc.Withdraw(3000) End Sub Sub Main() Dim t As Thread = New Thread(AddressOf Run) t.Start() acc.Withdraw(3000) End Sub End Module
Copyright 2001 K.M.Hussain <km@hussain.com> - 43 -

Programming .NET With Visual Basic.NET


We say Withdraw method is not thread safe (as it may fail to behave as required when multiple threads execute it simultaneously). Some time it is necessary to guarantee that a certain critical section of code is executed only by one thread at a time. VB.Net offers SyncLock statement to mark a statement block as a critical section. Consider following code block (obj is some object): SyncLock obj critical section End SyncLock Any thread which needs to execute the code enclosed in SyncLock obj statement must acquire the (one and only one) Monitor of obj. If one thread acquires monitor of an object then any other thread which attempts to do the same will be blocked until the first thread releases it( i.e moves out of the locked block). The same effect can also be obtained using following method Monitor.Enter(obj) critical section Monitor.Exit(obj) A thread safe implementation of above Account.Withdraw (modify syncthreads.vb) is given below Sub Withdraw(amount As Double) Console.WriteLine("Withdrawing {0}",amount) SyncLock Me If amount > balance Then Throw New Exception("Insufficient Funds") Thread.Sleep(10) balance -= amount End SyncLock Console.WriteLine("Balance {0}",balance) End Sub Now any thread interested in executing Withdraw method on an instance of Account must first acquire the monitor of that instance. Thus only one thread will be allowed to execute Withdraw on a given instance of Account at a time (obviously one threads execution of Withdraw method on one instance will not block another threads execution of Withdraw method on another instance because each instance will have its own monitor). A thread which acquires the monitor of an object obj may release its monitor by invoking Monitor.Wait(obj), but doing so will block the thread (as it is still in the locked block) until any other thread which acquires the monitor of obj passes a notification by invoking Monitor.Pulse(obj) before releasing it. This feature of thread and monitor can be used to obtain a complete coordination between two threads. The example below (waitingthread.vb) shows how two threads can be made to coordinate using the Monitor.

Copyright 2001 K.M.Hussain <km@hussain.com>

- 44 -

Programming .NET With Visual Basic.NET


Imports System Imports System.Threading Module WaitingThread Dim obj As New Object Sub Run() Dim i As Integer For i = 1 To 15 Console.WriteLine("Hello {0}",i) Next Monitor.Enter(obj) Monitor.Pulse(obj) Monitor.Exit(obj) End Sub Sub Main() Dim t As Thread = New Thread(AddressOf Run) t.Start() Dim i As Integer For i = 1 To 5 Console.WriteLine("Welcome {0}",i) Next Monitor.Enter(obj) Monitor.Wait(obj) Monitor.Exit(obj) Console.WriteLine("Goodbye") End Sub End Module 1.3 Asunchronous Programming Model: Normally when a caller invokes a method, the call is synchronous that is the caller has to wait for the method to return before the remaing code can be executed. .NET has an inbuilt support for asynchronous method invocation. Using this facility the caller can issue a request for invocation of a method and concurrently execute the remaing code. For every delegate declared in an assembly the compiler emits a class (subclass of System.MulticastDelegate) with Invoke, BeginInvoke and EndInvoke methods. For example condider a delegate declared as: Delegate Function MyWorker(ch As Char, max As Integer) As Integer The compiler will emit MyWorker class: Class MyWorker : Inherits System.MulticastDelegate Public Function Invoke(ch As Char, max As Integer) As Integer Public Function BeginInvoke(ch As Char, max As Integer, cb As _ System.AsyncCallback , asyncState as Object) As System.IAsyncResult Public Function EndInvoke(ch As Char, max As Integer, result As _ System.IAsyncResult) As Integer End Class
Copyright 2001 K.M.Hussain <km@hussain.com> - 45 -

Programming .NET With Visual Basic.NET


The BeginInvoke and EndInvoke methods can be used for asynchronous invocation of a method pointed by MyWorker delegate. In the example below (asynctest1.vb) DoWork method (which displays character ch max number of times) is invoked asynchronously with character + , in the next statement this method is directly invoked with character * . You will notice both + and * are displayed (1000 times each) on the console simultaneously. As an asynchronous call executes within its own background thread, we have used Console.Read() to pause the main thread. Imports System Delegate Function MyWorker(ch As Char, max As Integer) As Integer Module AsyncTest1 Dim worker As MyWorker Function DoWork(ch As Char, max As Integer) As Integer Dim t As Integer = Environment.TickCount returns the number of milliseconds elapsed since the system started Dim i As Integer For i = 1 To max Console.Write(ch) Next DoWork = Environment.TickCount - t End Function Sub Main() Console.WriteLine("Start") worker = AddressOf DoWork worker.BeginInvoke("+", 1000,Nothing,Nothing) asynchronous call DoWork("*",1000) synchronous call Console.Read() pause until user enters a key End Sub End Module But how do we retrieve the value returned by DoWork if it is invoked asynchronously? The return value can be obtained by invoking EndInvoke method of delegate passing it the IAsyncResult instance which is returned by BeginInvoke method. The EndInvoke method blocks the current thread until asynchronous call is completed. Our next example (asynctest2.vb) demonstrates this approach. Imports System Delegate Function MyWorker(ch As Char, max As Integer) As Integer Module AsyncTest2 Dim worker As MyWorker Function DoWork(ch As Char, max As Integer) As Integer
Copyright 2001 K.M.Hussain <km@hussain.com> - 46 -

Programming .NET With Visual Basic.NET


Dim t As Integer = Environment.TickCount Dim i As Integer For i = 1 To max Console.Write(ch) Next DoWork = Environment.TickCount - t End Function Sub Main() Console.WriteLine("Start") worker = AddressOf DoWork Dim result As IAsyncResult = worker.BeginInvoke("+", 1500,Nothing,Nothing) DoWork("*",1000) Dim time As Integer = worker.EndInvoke(result) obtain the value returned by DoWork, wait if it is still executing Console.WriteLine(" Work Completed in {0} milliseconds",time) Console.Read() End Sub End Module The third argument of BeginInvoke accepts an instance of AsyncCallback delegate. The method within this delegate is invoked as soon as asynchronous call compeltes. The example below (asynctest3.vb) used AsyncCallback to retrieve the value returned by DoWork method. Imports System Delegate Function MyWorker(ch As Char, max As Integer) As Integer Module AsyncTest3 Dim worker As MyWorker Function DoWork(ch As Char, max As Integer) As Integer Dim t As Integer = Environment.TickCount Dim i As Integer For i = 1 To max Console.Write(ch) Next DoWork = Environment.TickCount - t End Function Sub CallMeBack(result As IAsyncResult) Dim time As Integer = worker.EndInvoke(result) Console.WriteLine(" Work Completed in {0} milliseconds",time) End Sub

Copyright 2001 K.M.Hussain <km@hussain.com>

- 47 -

Programming .NET With Visual Basic.NET


Sub Main() Console.WriteLine("Start") worker = AddressOf DoWork Dim result As IAsyncResult = worker.BeginInvoke("+", 1000, _ AddressOf CallMeBack,Nothing) DoWork("*",1500) Console.Read() End Sub End Module

Copyright 2001 K.M.Hussain <km@hussain.com>

- 48 -

Programming .NET With Visual Basic.NET


Chapter 6: Streams
1.1 Input and Output Streams: The Stream class and its subclasses provide a generic view of data sources and repositories, isolating the programmer from the specific details of the operating system and underlying devices. Streams involve these three fundamental operations: 1. Streams can be read from. Reading is the transfer of data from a stream into a data structure, such as an array of bytes. 2. Streams can be written to. Writing is the transfer of data from a data structure into a stream. 3. Streams can support seeking. Seeking is the querying and modifying of the current position within a stream. All classes that represent streams inherit from the System.IO.Stream class. An application can query a stream for its capabilities by using the CanRead, CanWrite, and CanSeek properties. The Read and Write methods read and write data in a variety of formats. For streams that support seeking, the Seek and Position can be used to query and modify the current position a stream. Some stream implementations perform local buffering of the underlying data to improve performance. For such streams, the Flush method can be used to clear any internal buffers and ensure that all data has been written to the underlying data source or repository. Calling Close on a stream flushes any buffered data, essentially calling Flush for you. Close also releases operating system resources such as file handles, network connections, or memory used for any internal buffering . The System.IO.FileStream class is for reading from and writing to files. This class can be used to read and write bytes to a file. The System.IO.File class, used in conjunction with FileStream, is a utility class with static methods primarily for the creation of FileStream objects based on file paths. The program below (readfile.vb) displays content of a file whose name is passed through the command line. Imports System Imports System.IO Imports System.Text For Encoding Module ReadFile Sub Main() Dim args As String() = Environment.GetCommandLineArgs() Dim s As Stream = new FileStream(args(1), FileMode.Open) Dim size As Integer = CInt(s.Length) Dim buffer As Byte() = New Byte(size){} s.Read(buffer,0,buffer.Length) s.Close() Dim text As String = Encoding.ASCII.GetString(buffer) Console.WriteLine(text) End Sub End Module
Copyright 2001 K.M.Hussain <km@hussain.com> - 49 -

Programming .NET With Visual Basic.NET


First we create a stream to the mentioned file for reading (FileMode.Open) then we calculate the number of bytes that can be read from the stream and create a buffer of that size. Next we read the data from the stream into our buffer. Finally we convert this array of bytes into an ASCII text for displaying it on console. We can also obtain a read-only stream to a file using File class: Dim s As Stream = File.OpenRead(file_name); where file_name is a string containing the name of the file. The next program (writefile.vb) writes text in first command line argument into a file in second command line argument. If the file does not exists, it will be created else the text will be appended to it. Imports System Imports System.IO Imports System.Text For Encoding Module WriteFile Sub Main() Dim args As String() = Environment.GetCommandLineArgs() Dim s As Stream = new FileStream(args(2), FileMode.Append, FileAccess.Write) Dim text As String = args(1) + Environment..NewLine Dim buffer As Byte() = Encoding.ASCII.GetBytes(text) s.Write(buffer,0,buffer.Length) s.Close() End Sub End Module We create a stream to the mentioned file in Append mode. As the file will be modified we also mention FileAccess.Write (default FileAccess is Read). Next we convert the text that is to be appended to the stream into an array of bytes. Finally we write the data to the file. We can also obtain a read-write stream to a file using File class: Dim s As Stream = File.OpenWrite(file_name); where file_name is a string containing the name of the file. Classes System.IO.StreamReader and System.IO.StreamWriter can also be used for reading/writing text from/to a stream. The program below (placeorder.vb) taked customer name, item name and quantity from the command line and saves a line of text in a file called orders.txt containing these informations in a pipe (|) delimited format. This program also demonstrates how to obtain and format the current system date. Imports System Imports System.IO Module PlaceOrder Sub Main() Dim args() As String = Environment.GetCommandLineArgs() Dim dt As Date = DateTime.Now
Copyright 2001 K.M.Hussain <km@hussain.com> - 50 -

Programming .NET With Visual Basic.NET


Dim today As String = dt.ToString("dd-MMM-yyyy" ) Dim record As String = today+"|"+String.Join("|", args, 1, args.Length-1) Dim sw As New StreamWriter("orders.txt",true) sw.WriteLine(record) sw.Close() End Sub End Module We use the static Now property of System.DateTime structure to obtain the currrent date time of the system. Then we convert this DateTime instance to a string containing current date in dd-MMM-yyyy format (Ex: 13-Jun-2001). We build a pipe delimited record by appending this date to the remaining fields in args (note the use of String.Join). Next we create a StreamWriter to orders.txt for appending (indicated by second boolean argument of StreamWriter constructor) and we save the record as a line of text. We can also obtain a StreamWriter to a file using: Dim sw As StreamWriter = File.AppendText(file_name); Out next program (vieworders.vb) parses orders.txt file and displays all the orders which were placed before the date provided in the first command line argument. Imports System Imports System.IO Module ViewOrders Sub Main() Dim args() As String = Environment.GetCommandLineArgs() Dim adate As Date= DateTime.ParseExact(args(1),"dd-MMM-yyyy", Nothing) Dim sr As New StreamReader("orders.txt") Do Dim record As String = sr.ReadLine() If record Is Nothing Then Exit Do Dim delim As Char() = {"|"} Dim fields As String() = record.Split(delim) Dim odate As Date = DateTime.ParseExact(fields(0), _ "dd-MMM-yyyy",Nothing) If odate < adate Then Console.WriteLine(record.Replace("|", " ")) End If Loop sr.Close() End Sub End Module First the string in args(1) is converted into a DateTime instance. Then we create a StreamReader to orders.txt. The ReadLine() method of StreamReader returns the next line in the stream or null if it encounters an end of file. We read lines from orders.txt one at a time, split each line on | character, obtain the date from the first token and we display the record with each field separated by a space only if the date in record is before
Copyright 2001 K.M.Hussain <km@hussain.com> - 51 -

Programming .NET With Visual Basic.NET


the date provided by the user. We can also obtain a StreamReader to a file using: Dim sw As StreamWriter = File.OpenText(file_name); System.IO.BinaryWriter and System.IO.BinaryReader can be used for writing and reading primitive data types to a stream. The example below (binaryio.vb) writes some data to a file called info.dat and then rereads it and prints it. Imports System Imports System.IO Module BinaryIO Sub WriteData(i As Integer, d As Double, t As String) Dim s As Stream = File.OpenWrite("info.dat") Dim bw As BinaryWriter = New BinaryWriter(s) bw.Write(i) bw.Write(d) write length-prefixed string bw.Write(t) bw.Close() End Sub Sub ReadData() Dim s As Stream = File.OpenRead("info.dat") Dim br As BinaryReader = New BinaryReader(s) Dim i As Integer = br.ReadInt32() Dim d As double = br.ReadDouble() Dim t As string = br.ReadString() br.Close() Console.WriteLine("{0}, {1}, {2}", i, d, t) End Sub Sub Main() WriteData(12345, 3.1415467, "Hello World") ReadData() End Sub End Module 1.2 Object Serialization: Sometimes it is desirable to store a complete object in a stream. This is achieved by a process called Object Serialization. By Serialization we mean converting an object, or a connected graph of objects (a set of objects with some set of references to each other), stored within computer memory into a linear sequence of bytes. That string of bytes contains all of the important information that was held in the objects we started with. We can go on to use that sequence of bytes in several ways. For example:

Copyright 2001 K.M.Hussain <km@hussain.com>

- 52 -

Programming .NET With Visual Basic.NET


Send it to another process (on the same machine) and use it to construct arguments to a method that is run in that other process. In this case, the sequence of bytes is copied from one location in the machines physical memory to another it never leaves the machine Send it to the clipboard, to be browsed or included in another application. As above, the sequence of bytes is transferred to another location in memory on the current machine. Send it ' down the wire' to another machine and use it to create a clone on that machine of the original object graph. As above, we might then use this object graph as an argument to a method call. In this case, however, the sequence of bytes is actually transferred outside of the originating machine. Send it to a file on-disk, so that it can be reused later.

Class authors need to be aware of serialization. The serialization services assume that a type is NOT Serializable unless type is specifically marked as Serializable (using System.IO.Serializable attribute). In the most simple case marking a class as Serializable is the all the class author will have to do. Given below (employee.vb) is the Employee class whose instance we will serialize. Imports System Imports System.Runtime.Serialization <Serializable> Public Class Employee Public Name As String Public Job As String Public Salary As Double Public Sub New(_name As String, _job As String, _salary As Double) Name = _name Job = _job Salary = _salary End Sub Public Sub New() End Sub We override ToString() method of System.Object. This method is invoked whenever an Employee object is to be converted to a string. Public Overrides Function ToString() As String ToString = String.Format("{0} is a {1} and earns {2}",Name,Job,Salary) End Function End Class The program below (binsertest1.vb) creates an instance of Employee class and stores it in file emp.bin. It also retreives and displays the object.
Copyright 2001 K.M.Hussain <km@hussain.com> - 53 -

Programming .NET With Visual Basic.NET


Imports System Imports System.IO Imports System.Runtime.Serialization.Formatters.Binary Module BinarySerializationTest Dim bf As New BinaryFormatter Sub WriteEmployee(emp As Employee) Dim s As Stream = File.OpenWrite("emp.bin") bf.Serialize(s,emp) s.Close() End Sub Sub ReadEmployee() Dim s As Stream = File.OpenRead("emp.bin") Dim emp As Employee = CType(bf.Deserialize(s),Employee) s.Close() Console.WriteLine(emp) displays emp.ToString() End Sub Sub Main() Dim emp As Employee = new Employee("Jack", "Clerk", 44000) WriteEmployee(emp) ReadEmployee() End Sub End Module Compile: vbc binsertest1.vb employee.vb Sometimes we need to control the serialization process. For example consider a subclass of Employee class called Manager. When we store a Manager instance we don t want its Job field to be stored (because Job of Manager is Manager ). A class which needs to participate in its own serialization process must satisfy following two requirements. The class must implement System.IO.ISerializable interface and provide implementation for its Sub GetObjectData(info As SerializationInfo, context As StreamingContext) method. During serialization, the Formatter will invoke this method and store only that information in the stream which has been added to the info argument. 2. The class must provide a constructor which takes the same two arguments as above GetObjectData method. During deserialization the Formatter invokes this constructor passing it the info object containing the information from the stream. Below is the Manager class (add it to employee.vb) which exhibits custom serialization. 1.

Copyright 2001 K.M.Hussain <km@hussain.com>

- 54 -

Programming .NET With Visual Basic.NET


<Serializable> Public Class Manager Inherits Employee Implements ISerializable Public Secretary As Employee Public Sub New(_name As String, _salary As Double, _secretary As Employee ) MyBase.New(_name, "Manager",_salary) Secretary = _secretary End Sub Public Sub New() End Sub This constructor will be called during deserialization Retreive the field values from the info object passed as first argument Public Sub New (info As SerializationInfo, context As StreamingContext) Name = info.GetString("name") Salary = info.GetDouble("salary") Secretary = CType(info.GetValue("secretary", GetType(Employee)), Employee) Job = "Manager" End Sub This method will be called during serialization Add the field values to be stored to the info object object Public Sub GetObjectData(info As SerializationInfo, context As StreamingContext) _ Implements ISerializable.GetObjectData info.AddValue("name",Name) info.AddValue("salary",Salary) info.AddValue("secretary",Secretary) End Sub Public Overrides Function ToString() As String ToString = MyBase.ToString()+" - His secretary "+Secretary.ToString() End Function End Class The program below (binsertest2.vb) creates an instance of Manager class and stores it in file boss.bin. It also retreives and displays the object.. Imports System Imports System.IO Imports System.Runtime.Serialization.Formatters.Binary Module BinarySerializationTest Dim bf As New BinaryFormatter Sub WriteManager(mgr As Manager) Dim s As Stream = File.OpenWrite("boss.bin")
Copyright 2001 K.M.Hussain <km@hussain.com> - 55 -

Programming .NET With Visual Basic.NET


bf.Serialize(s,mgr) s.Close() End Sub Sub ReadManager() Dim s As Stream = File.OpenRead("boss.bin") Dim mgr As Manager = CType(bf.Deserialize(s),Manager) s.Close() Console.WriteLine(mgr) End Sub Sub Main() Dim sec As New Employee("Jane", "Steno", 24000) Dim mgr As New Manager("John", 72000, sec) WriteManager(mgr) ReadManager() End Sub End Module Above two examples use BinaryFormatter to store the image of object into the stream in a binary format. Obviously this information is not human readable. It is also possible to store object in an xml form using System.Xml.Serialization.XmlSerializer, The next program (xmlsertest.vb) stores a Manager instance in file boss.xml and then reads it back and displays it. Imports System Imports System.IO Imports System.Xml.Serialization Module XmlSerializationTest Dim xs As New XmlSerializer(GetType(Manager)) Sub WriteManager(mgr As Manager) Dim s As Stream = File.OpenWrite("boss.xml") xs.Serialize(s,mgr) s.Close() End Sub Sub ReadManager() Dim s As Stream = File.OpenRead("boss.xml") Dim mgr As Manager = CType(xs.Deserialize(s),Manager) s.Close() Console.WriteLine(mgr) End Sub Sub Main() Dim sec As New Employee("Jane", "Steno", 24000)
Copyright 2001 K.M.Hussain <km@hussain.com> - 56 -

Programming .NET With Visual Basic.NET


Dim mgr As New Manager("John", 72000, sec) WriteManager(mgr) ReadManager() End Sub End Module Compile: vbc /r:system.xml.dll xmlsertest.vb employee.vb Execution of this program will create boss.xml in the current directory with following content <?xml version="1.0"?> <Manager xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org/1999/XMLSchema"> <Secretary> <Name>Jane</Name> <Job>Steno</Job> <Salary>24000</Salary> </Secretary> <Name>John</Name> <Job>Manager</Job> <Salary>72000</Salary> </Manager> Note: XmlSerializer does not support custom serialization with ISerializable (The Job field of Manager instance is stored in boss.xml). Also for XmlSerializer to work the class whose object is being serialized must support a public default constructor. 1.3 Working with XML Document: The .NET framework provides XML parser for reading and modifying xml documents. Given below is emp.xml containing informations about employees. <?xml version="1.0"?> <employees> <employee id="101"> <name>John</name> <job>manager</job> <salary>72000</salary> </employee> <employee id="102"> <name>Jane</name> <job>steno</job> <salary>23000</salary> </employee> <employee id="103"> <name>Jim</name> <job>salesman</job>
Copyright 2001 K.M.Hussain <km@hussain.com> - 57 -

Programming .NET With Visual Basic.NET


<salary>36000</salary> </employee> <employee id="104"> <name>Jill</name> <job>clerk</job> <salary>45000</salary> </employee> </employees> The program below (listemp.vb) displays id and salary of each employee in emp.xml Imports System Imports System.Xml Module ListEmployees2 Sub Main() Dim doc As New XmlDocument() load employee.xml in XmlDocument doc.Load("emp.xml") obtain a list of all employee element Dim list As XmlNodeList = doc.GetElementsByTagName("employee") Dim emp As XmlNode For Each emp In list obtain value of id attribute of the employee tag Dim id As String = emp.Attributes("id").Value obtain value of salary subtag of the employee tag Dim sal As String = emp("salary").FirstChild.Value Console.WriteLine("{0} {1}",id,sal) Next End Sub End Module Compile: vbc /r:system.xml.dll listemp.vb The next program (addemp.vb) takes informations from command line and makes a new entry in employee.xml Imports System Imports System.Xml Module ListEmployees2 Sub Main() Dim args As String() = Environment.GetCommandLineArgs() Dim doc As New XmlDocument() load employee.xml in XmlDocument doc.Load("emp.xml") create a new employee element and value of its id attribute Dim emp As XmlElement = doc.CreateElement("employee")
Copyright 2001 K.M.Hussain <km@hussain.com> - 58 -

Programming .NET With Visual Basic.NET


emp.SetAttribute("id",args(1)) create a name tag and set its value Dim name As XmlNode = doc.CreateNode("element","name","") name.InnerText = args(2) make name tag a subtag of newly created employee tag emp.AppendChild(name) Dim job As XmlNode = doc.CreateNode("element","job","") job.InnerText = args(3) emp.AppendChild(job) Dim sal As XmlNode = doc.CreateNode("element","salary","") sal.InnerText = args(4) emp.AppendChild(sal) add the newly created employee tag to the root (employees) tag doc.DocumentElement.AppendChild(emp) save the modified document doc.Save("emp.xml") End Sub End Module Compile: vbc /r:system.xml.dll addemp.vb Execute: addemp 105 Jeff president 105000 Open emp.xml you will see the following fragment has been appended. <employee id="105"> <name>Jeffl</name> <job>president</job> <salary>105000</salary> </employee>

Copyright 2001 K.M.Hussain <km@hussain.com>

- 59 -

Programming .NET With Visual Basic.NET


Chapter 7: Sockets
1.1 Networking Basics: Inter-Process Communication i.e the capability of two or more physically connected machines to exchange data, plays a very important role in the enterprise software development. TCP/IP is the most common standard adopted for such communication. Under TCP/IP each machine is identified by a unique 4 byte integer referred to as its IPAddress (usually formated as 192.168.0.101). For easy remembrance this IPAddress is mostly bound to a user-friendly host name. The program below (showip.vb) uses System.Net.Dns class to display IPAddress of the machine whose name is passed in the first command-line argument. In absence of command-line argument it displays the name and IPAddress of the local machine. Imports System Imports System.Net Module ShowIP Sub Main() Dim args As String() = Environment.GetCommandLineArgs() Dim name As String If args.Length < 2 Then name = Dns.GetHostName() Else name = args(1) Try Dim addrs As IPAddress() = Dns.Resolve(name).AddressList Dim addr As IPAddress For Each addr In addrs Console.WriteLine("{0}/{1}",name,addr) Next Catch e As Exception Console.WriteLine(e.Message) End Try End Sub End Module Dns.GetHostName() returns name of the local machine and Dns.Resolve() returns IPHostEntry for a machine with a given name, the AddressList property of which returns the IPAdresses of the machine. The Resolve method will cause an exception if the mentioned host is not found. Though IPAddress allows to identify machines on network, each machine may host multiple applications which use network for data exchange. Under TCP/IP each network oriented application binds itself to a unique 2 byte integer referred to as its port-number which identifies this application on the machine it is executing. The data transfer takes place in form of byte bundles called IP Packets or Datagrams. The size of each datagram is 64KByte and it contains the data to be transferred, the actual size of the data, IPAddresses and port-numbers of sender and the prospective receiver. Once a datagram is placed on a network by a machine it will be received physically by all the other machines but will be accepted only by that machine whose IPAddress matches with the receiver s
Copyright 2001 K.M.Hussain <km@hussain.com> - 60 -

Programming .NET With Visual Basic.NET


IPAddress in the packet, later on this machine will transfer the packet to an application running on it which is bound to the receiver s port-number present in the packet. TCP/IP suite actually offers two different protocols for data exchange. The Transmission Control Protocol (TCP) is a reliable connection oriented protocol while the User Datagram Protocol (UDP) is not very reliable (but fast) connectionless protocol. Under TCP there is a clear distinction between the server process and the client process. The server process starts on a well known port (which clients are aware of) and listens for incoming connection requests. The client process starts on any port and issues a connection request. 1.2 Client-Server Programming with TCP/IP: The basic steps to create a TCP/IP server are as follows: 1. Create a System.Net.Sockets.TcpListener with a given local port and start it. Dim listener As New TcpListener(local_port) listener.Start() 2. Wait for incoming connection request and accept a System.Net.Sockets.Socket object from the listener whenever the request appears. Dim soc As Socket = listener.AcceptSocket() blocks 3. Create a System.Net.Sockets.NetworkStream from the above socket. Dim s As Stream = new NetworkStream(soc) 4. Communicate with the client using the predefined protocol (well established rules for data exchange) 5. Close the Stream. s.Close() 6. Close the Socket. s.Close() 7. Go to Step 2. Note when one request is accepted through step 2 no other request will be accepted until the code reaches step 7 (requests will be placed in a queue or backlog). In order to accept and service more than one client concurrently steps 1 7 must be executed in multiple threads. Program below (emptcpserver.vb) is a multithreaded TCP/IP server which accepts employee name from its client and sends back the job of the employee. The client terminates the session by sending a blank line for the employee s name. The employee data is retrieved from application s configuration file (an XML file in the directory of the application and whose name is the name of the application with a .config extension) Imports System Imports System.Threading Imports System.IO Imports System.Net Imports System.Net.Sockets Imports System.Configuration Module EmployeeTCPServer Dim listener As TcpListener
Copyright 2001 K.M.Hussain <km@hussain.com> - 61 -

Programming .NET With Visual Basic.NET


Const LIMIT As Integer = 5 5 concurrent clients Sub Main() listener = New TcpListener(2055) listener.Start() #If LOG THEN Console.WriteLine("Server mounted, listening to port 2055") #END IF Dim i As Integer For i = 1 To LIMIT Dim t As New Thread(AddressOf Service) t.Start() Next End Sub Sub Service() Do Dim soc As Socket = listener.AcceptSocket() #If LOG THEN Console.WriteLine("Connected: {0}", soc.RemoteEndPoint) #END IF Try Dim s As Stream = new NetworkStream(soc) Dim sr As New StreamReader(s) Dim sw As New StreamWriter(s) sw.AutoFlush = True enable automatic flushing sw.WriteLine("{0} Employees available", _ ConfigurationSettings.AppSettings.Count) Do Dim name As String = sr.ReadLine() If name = "" Or name Is Nothing Then Exit Do Dim job As String = _ ConfigurationSettings.AppSettings(name) If job Is Nothing Then job = "No such employee" sw.WriteLine(job) Loop s.Close() Catch e As Exception #If LOG THEN Console.WriteLine(e.Message) #END IF End Try #IF LOG THEN Console.WriteLine("Disconnected: {0}", soc.RemoteEndPoint) #END IF soc.Close() Loop End Sub End Module
Copyright 2001 K.M.Hussain <km@hussain.com> - 62 -

Programming .NET With Visual Basic.NET


Here is the content of the configuration file (emptcpserver.exe.config) for above application <configuration> <appSettings> <add key = "john" value="manager"/> <add key = "jane" value="steno"/> <add key = "jim" value="clerk"/> <add key = "jack" value="salesman"/> </appSettings> </configuration> The code between #IF LOG and #ENDIF will be added by the compiler only if symbol LOG is defined during compilation (conditional compilation). You can compile above program either by defining LOG symbol (informations are logged on the screen) vbc /r:system.dll /D:LOG=True emptcpserver.vb or without the LOG symbol (silent mode) vbc /r:system.dll emptcpserver.vb Mount the server using command start emptcpserver To test the server you can use : telnet localhost 2055 Or we can create a client program. Basic steps for creating a TCP/IP client are as follows: 1. Create a System.Net.Sockets.TcpClient using server s host name and port. Dim client As New TcpClient(host, port) 2. Obtain the stream from above TCPClient. Dim s As Stream = client.GetStream() 3. Communicate with the server using the predefined protocol. 4. Close the Stream. s.Close() 5. Close the Connection client.Close() The program below (emptcpclient.vb) communicates with emptcpserver. Imports System Imports System.IO Imports System.Net.Sockets Module EmployeeTCPClient Sub Main() Dim args As String() = Environment.GetCommandLineArgs() Dim client As New TcpClient(args(1),2055) Try Dim s As Stream = client.GetStream() Dim sr As New StreamReader(s) Dim sw As New StreamWriter(s) sw.AutoFlush = True Console.WriteLine(sr.ReadLine())
Copyright 2001 K.M.Hussain <km@hussain.com> - 63 -

Programming .NET With Visual Basic.NET


Do Console.Write("Name: ") Dim name As String = Console.ReadLine() sw.WriteLine(name) If name = "" Then Exit Do Console.WriteLine(sr.ReadLine())

Finally

Loop s.Close()

End Try End Sub End Module

code in finally block is guranteed to execute irrespective of whether any exception occurs or does not occur in the try block client.Close()

1.3 Multicasting with UDP: Unlike TCP, UDP is connectionless i.e data can be send to multiple receivers using a single socket. Basic UDP operations are as follows: 1. Create a System.Net.Sockets.UdpClient either using a local port or remote host and remote port Dim client As New UdpClient(local_ port) or Dim client As New UdpClient(remote_host, remote_port) 2. Receive data using above UDPClient. Dim ep As System.Net.IPEndPoint Dim data As Byte() = client.Receive(ep) byte array data will contain the data that was received and ep will contain the address of the sender 3. Send data using above UdpClient.. If remote host name and port number has already been passed to UdpClient through its constructor then send byte array data using client.Send(data, data.Length) Otherwise send byte array data using IPEndPoint ep of the receiver client.Send(data, data.Length, ep) The program below (empudpserver.vb) receives name of an employee from a remote client and sends it back the job of that employee using UDP. Imports System Imports System.Text Imports System.IO Imports System.Net Imports System.Net.Sockets Imports System.Configuration Module EmployeeUDPServer
Copyright 2001 K.M.Hussain <km@hussain.com> - 64 -

Programming .NET With Visual Basic.NET


Sub Main() Dim udpc As New UdpClient(2055) Console.WriteLine("Server started, servicing on port 2055") Dim ep As IPEndPoint Do Dim rdata As byte() = udpc.Receive(ep) Dim name As String = Encoding.ASCII.GetString(rdata) Dim job As String = ConfigurationSettings.AppSettings(name) If job Is Nothing Then job = "No such employee" Dim sdata As byte()= Encoding.ASCII.GetBytes(job) udpc.Send(sdata,sdata.Length,ep) Loop End Sub End Module Here is the content of the configuration file (empudpserver.exe.config) for above application <configuration> <appSettings> <add key = "john" value="manager"/> <add key = "jane" value="steno"/> <add key = "jim" value="clerk"/> <add key = "jack" value="salesman"/> </appSettings> </configuration> The next program (empudpclient.vb) is a UDP client to above server program. Imports System Imports System.Net Imports System.Net.Sockets Imports System.Text Module EmployeeUDPClient Sub Main() Dim args As String() = Environment.GetCommandLineArgs() Dim udpc As New UdpClient(args(1),2055) Dim ep As IPEndPoint Do Console.Write("Name: ") Dim name As String= Console.ReadLine() If name = "" Then Exit Do Dim sdata As byte() = Encoding.ASCII.GetBytes(name) udpc.Send(sdata,sdata.Length) Dim rdata As byte() = udpc.Receive(ep)
Copyright 2001 K.M.Hussain <km@hussain.com> - 65 -

Programming .NET With Visual Basic.NET


Dim job As String = Encoding.ASCII.GetString(rdata) Console.WriteLine(job)

Loop End Sub End Module

UDP also supports multicasting i.e sending a single datagram to multiple receivers. To do so the sender send a packet to an IPAddress in range 224.0.0.1 239.255.255.255 (Class D address group). Multiple receivers can join the group of this address and receive the packet. Program below (stockpricemulticaster.vb) sends a datagram every 5 seconds containing share price (a randomly calculated value) of an imaginary company to address 230.0.0.1. Imports System Imports System.Net Imports System.Net.Sockets Imports System.Text Module StockPriceMulticaster Dim symbols As String()= {"ABCD","EFGH", "IJKL", "MNOP"} Sub Main() Dim publisher As New UdpClient("230.0.0.1",8899) Console.WriteLine("Publishing stock prices to 230.0.0.1:8899") Dim gen As New Random() Do Dim i As Integer = gen.Next(0,symbols.Length) Dim price As Double = 400*gen.NextDouble()+100 Dim msg As String = String.Format("{0} {1:#.00}",symbols(i),price) Dim sdata As byte() = Encoding.ASCII.GetBytes(msg) publisher.Send(sdata,sdata.Length) System.Threading.Thread.Sleep(5000) Loop End Sub End Module Compile and start stockpricemulticaster The next program (stockpricereceiver.vb) joins the group of address 230.0.0.1, receives 10 stock prices and then leaves the group. Imports System Imports System.Net Imports System.Net.Sockets Imports System.Text Module StockPriceReceiver Sub Main()
Copyright 2001 K.M.Hussain <km@hussain.com> - 66 -

Programming .NET With Visual Basic.NET


Dim subscriber As New UdpClient(8899) Dim addr As IPAddress = IPAddress.Parse("230.0.0.1") subscriber.JoinMulticastGroup(addr) Dim ep As IPEndPoint Dim i As Integer For i = 1 To 10 Dim pdata As byte() = subscriber.Receive(ep) Dim price As String = Encoding.ASCII.GetString(pdata) Console.WriteLine(price) Next subscriber.DropMulticastGroup(addr) End Sub End Module Compile and run stockpricereceiver.

Copyright 2001 K.M.Hussain <km@hussain.com>

- 67 -

Programming .NET With Visual Basic.NET


Chapter 8: Distributed Programming with Remoting
1.1 Remoting Architecture: You can use .NET Remoting to enable different applications to communicate with one another, whether those applications reside on the same computer, on different computers in the same local area network, or across the world in very different networks -- even if the computers run different operating systems. The .NET Framework provides a number of services such as activation and lifetime control, as well as communication channels responsible for transporting messages to and from remote applications. Formatters are used to encode and decode the messages before they are sent along a channel. Applications can use binary encoding where performance is critical or XML encoding where interoperability with other remoting frameworks is essential. Remoting was designed with security in mind, so you can use a number of hooks to access the call messages and serialized streams before they are transported over the channel. The remoting infrastructure is an abstract approach to interprocess communication. Much of the system functions without drawing attention to itself. For example, objects that can be passed by value, or copied, are automatically passed between applications in different application domains or on different computers. You need only mark your custom classes as serializable to make this work. The real strength of the remoting system, however, resides in its ability to enable communication between objects in different application domains or processes using different transportation protocols, serialization formats, object lifetime schemes, and modes of object creation. In addition, if you need to intervene in almost any stage of the communication process, for any reason, remoting makes this possible. Cross-process communication requires a server object whose functionality is provided to callers outside its process, a client that makes calls on the server object, and a transportation mechanism to ferry the calls from one end to the other. The addresses of server methods are logical and function properly in one process, but do not in a different client process. To alleviate this problem, one way for the client to call a server object is to make a copy of the object in its entirety and move it to the client process, where the copys methods can be invoked directly. Many objects, however, cannot or should not be copied and moved to some other process for execution. Extremely large objects with many methods can be poor choices for copying, or passing by value, to other processes. Usually, a client needs only the information returned by one or a few methods on the server object. Copying the entire server object, including what could be vast amounts of internal information or executable structures unrelated to the clients needs, would be a waste of bandwidth as well as of client memory and processing time. In addition, many objects expose public functionality, but require private data for internal execution. Copying these objects could enable malicious clients to examine internal data, creating the potential for security problems. Finally, some objects use data that simply cannot be copied in any
Copyright 2001 K.M.Hussain <km@hussain.com> - 68 -

Programming .NET With Visual Basic.NET


understandable way. A FileInfo object, for example, contains a reference to an operating system file, which has a unique address in the server processs memory. You can copy this address, but it will never make sense in another process. In these situations, the server process should pass to the client process a reference to the server object, not a copy of the object. Clients can use this reference to call the server object. These calls do not execute in the client process. Instead, the remoting system collects all information about the call and sends it to the server process, where it is interpreted, the correct server object is located, and the call made to the server object on the client objects behalf. The result of the call is then sent back to the client process to be returned to the client. Bandwidth is used for only the critical information -- the call, call arguments, and any return values or exceptions. Using object references to communicate between server objects and clients is the heart of remoting. The remoting architecture, however, presents to the programmer an even simpler procedure. If you configure the client properly, you need only create a new instance of the remote object using new. Your client receives a reference to the server object, and you can then call its methods as though the object were in your process rather than running on a separate computer. The remoting system uses proxy objects to create the impression that the server object is in the clients process. Proxies are stand-in objects that present themselves as some other object. When your client creates an instance of the remote type, the remoting infrastructure creates a proxy object that looks to your client exactly like the remote type. Your client calls a method on that proxy, and the remoting system receives the call, routes it to the server process, invokes the server object, and returns the return value to the client proxy, which returns the result to the client. Remote calls must be conveyed in some way between the client and the server process. If you were building a remoting system yourself, you might start by learning network programming and a wide array of protocols and serialization format specifications. In the .NET Remoting system, the combination of underlying technologies required to open a network connection and use a particular protocol to send the bytes to the receiving application are represented as a transport channel. A channel is a type that takes a stream of data, creates a package according to a particular network protocol, and sends the package to another computer. Some channels can only receive information, others can only send information, and still others, such as the default TcpChannel and HttpChannel classes, can be used in either direction. Although the server process knows everything about each unique type, the client knows only that it wants a reference to an object in another application domain, perhaps on another computer. From the world outside the server application domain, the object is located by a uniform resource locator (URL). The URLs that represent unique types to the outside world are activation URLs, which ensure that your remote call is made to the proper type. Suppose you have an application running on one computer, and you want to use the functionality exposed by a type that is stored on another computer. The following illustration shows the general remoting process.

Copyright 2001 K.M.Hussain <km@hussain.com>

- 69 -

Programming .NET With Visual Basic.NET


Remoting System ---------- Channel ----------- Remoting System | | | | | Proxy | | | | Server Object Client Object <Computer : 1>------------- <Network> -----------<Computer : 2> If both sides of the relationship are configured properly, then a client merely creates a new instance of the server class. The remoting system creates a proxy object that represents the class and returns to the client object a reference to the proxy. When a client calls a method, the remoting infrastructure fields the call, checks the type information, and sends the call over the channel to the server process. A listening channel picks up the request and forwards it to the server remoting system, which locates (or creates, if necessary) and calls the requested object. The process is then reversed, as the server remoting system bundles the response into a message that the server channel sends to the client channel. Finally, the client remoting system returns the result of the call to the client object through the proxy. Remoting objects can be classified into three groups depending on their activation model. Server-activated SingleCall Object: for this type of object a new instance will be created for every client method invocation. Server-activated Singleton Object: for this type of object there will always be only one instance, regardless of how many clients there are for that object. Client-activated Object: for this type of object a new instance will be created when the client calls new. 1.2 Server Activated Remoting Objects: Our first program (orderserver.vb) provides a remoting object which exposes a PlaceOrder method for inserting new orders in orders.txt. It uses Server-activated SingleCall model for activation. Imports System.IO Imports System.Runtime.Remoting Imports System.Runtime.Remoting.Channels Imports System.Runtime.Remoting.Channels.Tcp Public Class OrderService : Inherits MarshalByRefObject Public Sub PlaceOrder(cust As String, prod As Integer, quant As Integer) Dim dt As Date = DateTime.Now Dim today As String = dt.ToString("dd-MMM-yyyy" ) Dim record As String = today & "|" & cust & "|" & prod & "|" & quant Dim sw As New StreamWriter("\orders.txt",true) sw.WriteLine(record)
Copyright 2001 K.M.Hussain <km@hussain.com> - 70 -

Programming .NET With Visual Basic.NET


sw.Close() End Sub End Class Public Module OrderServer Public Sub Start() ChannelServices.RegisterChannel(new TcpChannel(2255)) RemotingConfiguration.RegisterWellKnownServiceType( _ GetType(OrderService), _ "order.rem", _ WellKnownObjectMode.SingleCall) End Sub Sub Main() Start() Console.WriteLine("order.rem service ready, enter any key to terminate") Console.ReadLine() End Sub End Module Compile: vbc /r:system.runtime.remoting.dll orderserver.vb Above command will create cartserver.exe. Also create cartserver.dll which will be referenced by the client program. vbc /t:library /r:system.runtime.remoting.dll orderserver.vb Note the OrderService class is derived from System.MarshalByRef object. Any remoting object whose reference is to be made available to the client (by means of its proxy) must derive from this class. In the main method we create a TCP channel on port 2255 and register OrderService type. The name order.rem will be used by the client in its URL to locate this object. The above server must be executed as a separate process. Given below (orderclient.vb) is a client program which consumes the OrderService. Imports System Imports System.Runtime.Remoting Imports System.Runtime.Remoting.Channels Imports System.Runtime.Remoting.Channels.Tcp Module OrderClient Sub Main() ChannelServices.RegisterChannel(New TcpChannel()) Dim remote As OrderService = CType(Activator.GetObject( _ GetType(OrderService), _ "tcp://localhost:2255/order.rem"), OrderService) Console.Write("Customer ID: ") Dim cust As String = Console.ReadLine() Console.Write("Product No: ")
Copyright 2001 K.M.Hussain <km@hussain.com> - 71 -

Programming .NET With Visual Basic.NET


Dim prod As Integer= Int32.Parse(Console.ReadLine()) Console.Write("Quantity: ") Dim quant As Integer = Int32.Parse(Console.ReadLine()) remote.PlaceOrder(cust,prod,quant) Console.WriteLine("Order Placed) End Sub End Module Compile client: vbc /r:orderserver.dll,system.runtime.remoting.dll orderclient.vb Note: during compilation and runtime orderclient needs orderserver assembly since it refers to OrderService defined in orderserver.dll. One way to bypass this requirement is to declare the method exposed by the remoting object in an interface placed in a separate assembly and reference this assembly to compile the server and the client. Our next example demonstrates this approach. In this example we simulate an auction system. The remote clients will make a bid for a price of an item and the server will accept the bid if it is legal and change the price of the item. As a single price applies to all the clients, they should all invoke the Bid method on a common remote object i.e we must use Serveractivated Singleton model for the server object. When a bid is accepted by the server it notifies all its clients by firing an event on them. The application is divided into three assemblies compiled from auction.vb (containing the interface exposed by the server), auctionserver.vb (containing the actual remoting class) and auctionclient.vb (containing the client program) File: auction.vb Namespace Auction Public Delegate Sub PriceChangeHandler(price As Double) Public Interface IAuctioneer Event PriceChanged As PriceChangeHandler ReadOnly Property CurrentPrice As Double Function Bid(price As Double) As Boolean End Interface End Namespace Compile: vbc /t:library auction.vb File: auctionserver.vb Imports System Imports System.Runtime.Remoting Imports System.Runtime.Remoting.Channels Imports System.Runtime.Remoting.Channels.Tcp Namespace Auction Public Class Auctioneer Inherits MarshalByRefObject Implements Iauctioneer
Copyright 2001 K.M.Hussain <km@hussain.com> - 72 -

Programming .NET With Visual Basic.NET


Public Event PriceChanged As PriceChangeHandler Implements _ IAuctioneer.PriceChanged Private current As Double = 500 Public ReadOnly Property CurrentPrice As Double Implements _ IAuctioneer.CurrentPrice Get CurrentPrice = current End Get End Property Public Function Bid(price As Double) As Boolean Implements IAuctioneer.Bid If price - current < 100 Or price - current > 1000 Then Bid = False Exit Function End If current = price RaiseEvent PriceChanged(price) Bid = True End Function End Class End Namespace Module AuctionServer Sub Main() ChannelServices.RegisterChannel(new TcpChannel(3355)) RemotingConfiguration.RegisterWellKnownServiceType( _ GetType(Auction.Auctioneer), _ "auction.rem", _ WellKnownObjectMode.Singleton) Console.WriteLine("auction.rem service ready, enter any key to terminate") Console.ReadLine() End Sub End Module Compile: vbc /r:auction.dll,system.runtime.remoting.dll auctionserver.vb File: auctionclient.vb Imports Auction Imports System Imports System.Runtime.Remoting Imports System.Runtime.Remoting.Channels Imports System.Runtime.Remoting.Channels.Tcp Class AuctionClient : Inherits MarshalByRefObject Public Sub New()
Copyright 2001 K.M.Hussain <km@hussain.com> - 73 -

Programming .NET With Visual Basic.NET


ChannelServices.RegisterChannel(New TcpChannel(0)) Dim remote As IAuctioneer = CType(Activator.GetObject( _ GetType(Auction.IAuctioneer), _ "tcp://localhost:3355/auction.rem"), IAuctioneer) Console.WriteLine("Current Price: {0}", remote.CurrentPrice) AddHandler remote.PriceChanged, AddressOf Me.AuctionClient_PriceChanged Do Console.Write("Bid: ") Dim text As String = Console.ReadLine() If text = "" Then Exit Do Try Dim price As Double = Double.Parse(text) If Not remote.Bid(price) Then Console.WriteLine("Bid Failed") catch e As Exception Console.WriteLine(e) Exit Do End Try Loop RemoveHandler remote.PriceChanged, _ AddressOf Me.AuctionClient_PriceChanged End Sub Public Sub AuctionClient_PriceChanged(price As Double) Console.WriteLine("Price changed to {0}",price) End Sub Shared Sub Main() Dim client As New AuctionClient() End Sub End Class Compile: vbc /r:auction.dll,system.runtime.remoting.dll auctionclient.vb 1.3 Client Activated Remoting Objects: Next consider a remote shopping cart. Here each client must have his own cart instance on the server hence the remoting Cart object must be a Client-activated Object. The application below (cartserver.vb) implements a remote Cart. This example also demonstrates the use of HttpChannel and shows how to configure the server using a configuration file (cartserver.exe.config) Imports System Imports System.Collections Imports System.Runtime.Remoting public Class Cart : Inherits MarshalByRefObject Private store As New ArrayList() Public Sub New()
Copyright 2001 K.M.Hussain <km@hussain.com> - 74 -

Programming .NET With Visual Basic.NET


Console.WriteLine("Cart instantiated") End Sub Public Sub AddItem(item As String) store.Add(item) End Sub Public Sub RemoveItem(item As String) store.Remove(item) End Sub public Function ListItems() As String() Dim items As String() = New String(store.Count - 1){} store.CopyTo(items) ListItems = items End Function End Class Module CartServer Sub Main() RemotingConfiguration.Configure("cartserver.exe.config") Console.WriteLine(RemotingConfiguration.ApplicationName _ + " started, enter any key to terminate...") Console.ReadLine() End Sub End Module Here is the content of cartserver.exe.config file <configuration> <system.runtime.remoting> <application name="CartServer"> <service> <activated type = "Cart,CartServer"/> </service> <channels> <channel ref=httpport="8080"/> </channels> </application> </system.runtime.remoting> </configuration> The next program (cartclient.vb) is the client program for above server.

Copyright 2001 K.M.Hussain <km@hussain.com>

- 75 -

Programming .NET With Visual Basic.NET


Imports System Imports System.Runtime.Remoting Module CartClient Sub Main() Dim args As String() = Environment.GetCommandLineArgs() RemotingConfiguration.Configure("cartclient.exe.config") Dim cart As New Cart() Dim i For i = 1 To args.Length - 1 cart.AddItem(args(i)) Next Console.WriteLine("Number of items in the cart: {0}", cart.ListItems().Length) Console.WriteLine("Removing orange from the cart") cart.RemoveItem("orange") Console.WriteLine("Number of items in the cart: {0}", cart.ListItems().Length) End Sub End Module The content of cartclient.exe.config is given below: <configuration> <system.runtime.remoting> <application name="CartClient"> <client url = "http://localhost:8080/CartServer"> <activated type = "Cart,CartServer"/> </client> <channels> <channel ref=http"/> </channels> </application> </system.runtime.remoting> </configuration> HttpChannel uses Simple Object Access Protocol for communication. SOAP is an XML based protocol for exchanging structured type information on Web. While TcpChannel uses BinaryFormatter to serialize objects for transferring them across the network.

Copyright 2001 K.M.Hussain <km@hussain.com>

- 76 -

Programming .NET With Visual Basic.NET


Chapter 9: Database Programming with ADO.NET
1.1 Accessing and Updating Data using ADO.NET: Accessing data has become a major programming task for modern software programming, both for standalone applications and for web-based applications. Microsofts ADO.NET technology offers a solution to many of the problems associated with data access. ADO.NET is an evolutionary improvement to Microsoft ActiveX Data Objects (ADO). It is a standards-based programming model for creating data-sharing applications. ADO.NET offers several advantages over previous versions of ADO and over other data access components. These benefits fall into the following categories: Interoperability, Maintainability, Programmability, and Performance. A .NET data provider is used for connecting to a database, executing commands, and retrieving results. Those results are either processed directly using DataReader, or placed in a DataSet in order to be exposed to the user in an ad-hoc manner, combined with data from multiple sources.. The .NET data provider is designed to be lightweight, creating a minimal layer between the data source and your code, increasing performance while not sacrificing functionality. There are four core objects that make up a .NET data provider: Object Connection Command DataReader DataAdapter Description Establishes a connection to a specific data source. Executes a command at a data source. Exposes Parameters and can enlist a Transaction from a Connection. Reads a forward-only, read-only stream of data from a data source. Populates a DataSet and resolves updates with the data source.

The .NET Framework includes the OLE DB .NET Data Provider (System.Data.OleDb) and the SQL Server .NET Data Provider (System.Data.SqlClient) for Microsoft SQL Server 7.0 or later. To use the OLE DB .NET Data Provider, you must also use an OLE DB provider. The following providers are compatible with ADO.NET. Driver SQLOLEDB Provider Microsoft OLE DB Provider for SQL Server MSDAORA Microsoft OLE DB Provider for Oracle Microsoft.Jet.OLEDB.4.0 OLE DB Provider for Microsoft Jet
- 77 -

Copyright 2001 K.M.Hussain <km@hussain.com>

Programming .NET With Visual Basic.NET


In all the examples below we assume existence of a MS Access 2000 database called sales.mdb (in the current folder) and a MS SQL Server 7.0 database called sales (running on the current machine) . The initial data in both of these databases must be generated by using following sql commands. CREATE TABLE CUSTOMER( CUST_ID VARCHAR(8) PRIMARY KEY, PWD VARCHAR(8) CHECK(LEN(PWD) >= 4), EMAIL VARCHAR(24) NOT NULL) CREATE TABLE PRODUCT( PNO INT PRIMARY KEY, PRICE NUMERIC NOT NULL) CREATE TABLE ORD( ORD_NO INT PRIMARY KEY, ORD_DATE DATETIME, CUST_ID VARCHAR(8) REFERENCES CUSTOMER(CUST_ID), PNO INT REFERENCES PRODUCT(PNO), QTY INT) CREATE TABLE ORD_CTL( CUST_ID INT, PNO INT, ORD_NO INT) CREATE TABLE STOCK (PNO INT PRIMARY KEY REFERENCES PRODUCT(PNO), QTY INT NOT NULL CHECK (QTY >= 0)) INSERT INTO CUSTOMER VALUES (CU101, PW101, JOHN@DOE.COM) INSERT INTO CUSTOMER VALUES (CU102, PW102, JILL@SMITH.NET) INSERT INTO CUSTOMER VALUES (CU103, PW103, JACK@SMITH.NET) INSERT INTO CUSTOMER VALUES (CU104, PW104, JANE@DOE.COM) INSERT INTO PRODUCT VALUES (101, 350) INSERT INTO PRODUCT VALUES (102, 975) INSERT INTO PRODUCT VALUES (103, 845) INSERT INTO PRODUCT VALUES (104, 1025) INSERT INTO PRODUCT VALUES (105, 700) INSERT INTO ORD VALUES(1001, 2001-01-12, CU102, 101, 5) INSERT INTO ORD VALUES(1002, 2001-01-25, CU103, 102, 10) INSERT INTO ORD VALUES(1003, 2001-02-08, CU102, 102, 12) INSERT INTO ORD VALUES(1004, 2001-03-21, CU101, 103, 3) INSERT INTO ORD VALUES(1005, 2001-03-19, CU103, 104, 15) INSERT INTO ORD VALUES(1006, 2001-04-11, CU104, 105, 12)
Copyright 2001 K.M.Hussain <km@hussain.com> - 78 -

Programming .NET With Visual Basic.NET


INSERT INTO ORD_CTL VALUES(104, 105, 1006) INSERT INTO STOCK VALUES (101, 20) INSERT INTO STOCK VALUES (102, 5) INSERT INTO STOCK VALUES (103, 45) INSERT INTO STOCK VALUES (104, 12) INSERT INTO STOCK VALUES (105, 24) Our first program (querytest.vb) displays all the rows in ord table of Access database sales.mdb. It uses OLE DB.NET Data Provider. Imports System Imports System.Data Imports System.Data.OleDb Module QueryTest Sub Main() Dim args As String() = Environment.GetCommandLineArgs() Dim cn As New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=sales.mdb") Dim sql As String = "select ord_no,ord_date,cust_id,pno,qty from ord" If args.Length > 1 Then sql += " order by "+args(1) Dim cmd As new OleDbCommand(sql, cn) cn.Open() Try Dim dr As OleDbDataReader = cmd.ExecuteReader() Do While dr.Read() Console.WriteLine("{0} {1} {2} {3} {4}" , _ dr("ord_no"),dr("ord_date"),dr("cust_id"),dr("pno"),dr("qty")) Loop dr.Close() Finally cn.Close() End Try End Sub End Module Compile: vbc /r:system.dll,system.data.dll querytest.vb We first create an OleDbConnection object with a suitable connection string. Then we create an OleDbCommand with the sql and the above connection. As this sql is a query we execute it it to obtain an OleDbDataReader. The Read method of OleDbReader loads itself with the next record and returns true if it is available. We can obtain the value of any field in the current record loaded in OleDbDataReader dr using dr( field_name )or dr.GetT(field_index) where T is an appropriate .NET equivalent of the field type at position specified in field_index (first field is at index 0).
Copyright 2001 K.M.Hussain <km@hussain.com> - 79 -

Programming .NET With Visual Basic.NET


The next program (updatetest.vb) reduces the price of a product with the product number in first command line argument by 10%. If no argument is passed, price of each product is reduced. Here we use the ExecuteNonQuery method of OleDbCommand which updates the database and returns the number of rows updated. Imports System Imports System.Data Imports System.Data.OleDb Module UpdateTest Sub Main() Dim args As String() = Environment.GetCommandLineArgs() Dim cn As New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=sales.mdb") Dim sql As String = "update product set price = 0.9*price" If args.Length > 1 Then sql += " where pno = "+args(1) Dim cmd As new OleDbCommand(sql, cn) cn.Open() Try Dim n As Integer = cmd.ExecuteNonQuery() Console.WriteLine("{0} row/s updated",n) Finally cn.Close() End Try End Sub End Module 1.2 Paramaterized SQL and Stored Procedures: The next program (paramsqltest.vb) inserts a new record in the ord table of sales.mdb. The values for cust_id, pno and qty fields are passed through the command-line arguments. It uses ord_ctl table (which contains the last assigned order number in its ord_no column) to generate the new order number. This program also demonstrates the use of parameters in SQL command and the use of transactions. Imports System Imports System.Data Imports System.Data.OleDb Module ParamSQLTest Public Function PlaceOrder(cust As String, prod As Integer, quant As Integer) As Integer Dim cn As New OleDbConnection( _ "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=sales.mdb") Dim cmd As New OleDbCommand() cmd.Connection = cn cn.Open() cmd.Transaction = cn.BeginTransaction() Dim ordid As Integer
Copyright 2001 K.M.Hussain <km@hussain.com> - 80 -

Programming .NET With Visual Basic.NET


cmd.CommandText = "update ord_ctl set ord_no = ord_no + 1" cmd.ExecuteNonQuery() cmd.CommandText = "select ord_no from ord_ctl" ordid = CInt(cmd.ExecuteScalar()) cmd.CommandText = "insert into ord values(?,?,?,?,?)" cmd.Parameters.Add("ord_no", OleDbType.Integer).Value = ordid cmd.Parameters.Add("ord_date", OleDbType.Date).Value = _ DateTime.Now cmd.Parameters.Add("cust_id", OleDbType.Char,8).Value = cust cmd.Parameters.Add("pno", OleDbType.Integer).Value = prod cmd.Parameters.Add("qty", OleDbType.Integer).Value = quant cmd.ExecuteNonQuery() cmd.Transaction.Commit() Catch e As Exception cmd.Transaction.Rollback() Console.WriteLine(e.Message) ordid = -1 End Try cn.Close() PlaceOrder = ordid End Function Sub Main(args() As String) Dim ordid As Integer = PlaceOrder(args(0), Int32.Parse(args(1)), _ Int32.Parse(args(2))) If ordid >= 0 Then Console.WriteLine("Order placed, Order No: {0}",ordid) End Sub End Module We use BeginTransaction method of OleDbConnection to start the transaction. This method returns an OleDbTransaction instance which is assigned to the Transaction property of the OleDbCommand. The Commit and Rollback methods of OleDbTransaction can be used to permanently update the database or to cancel any change that was made after executing BeginTransaction. We first increment the ord_no field in ord_ctl (as we have started a transaction this will lock the ord_ctl table until we commit or rollback) then we read the incremented ord_no for our new order. The ExecuteScalar method returns the first column of the first row returned by the query. We insert the new order in the ord table using a paramaterized sql (? used for field values). The parameters with suitable name (need not match with column names) and appropriate OleDbType are added to the Parameters collection of the command. The value of each parameter must be set before executing the SQL. Popular databases like MS SQL Server and Oracle support stored procedures. This procedures are coded in proprietary language and are stored in the database. This procedures can be called by remote clients and they execute within the database environment. Consider a T-SQL procedure called place_order stored in sales database of
Copyright 2001 K.M.Hussain <km@hussain.com> - 81 -

Try

Programming .NET With Visual Basic.NET


MS SQL Server. This procedure accepts customer id, product number and quantity as its input arguments, inserts a new order in the ord table and returns the order number through an output argument. The code used for creating this procedure is given below. CREATE PROCEDURE PLACE_ORDER( @CUST VARCHAR(8), @PROD INTEGER, @QUANT INTEGER, @ORDID INTEGER OUTPUT) AS BEGIN TRAN UPDATE ORD_CTL SET ORD_NO=ORD_NO+1 SELECT @ORDID = ORD_NO FROM ORD_CTL INSERT INTO ORD VALUES(@ORDID, GETDATE(), @CUST, @PROD, @QUANT) IF @@ERROR = 0 COMMIT TRAN ELSE ROLLBACK TRAN The program below (stproctest.vb) uses SQL Server.NET Data Provider to connect to sales database and places an order by calling the above stored procedure Imports System Imports System.Data Imports System.Data.SqlClient Module StoredProcedureTest Sub Main() Dim args As String() = Environment.GetCommandLineArgs() Dim cn As New SqlConnection("server=localhost;uid=sa;pwd=;database=sales") Dim cmd As New SqlCommand("place_order",cn) cmd.CommandType = CommandType.StoredProcedure cn.Open() Try cmd.Parameters.Add("@cust",SqlDbType.VarChar,8) cmd.Parameters("@cust").Value = args(1) cmd.Parameters.Add("@prod",SqlDbType.Int) cmd.Parameters("@prod").Value = Int32.Parse(args(2)) cmd.Parameters.Add("@quant",SqlDbType.Int) cmd.Parameters("@quant").Value = Int32.Parse(args(3)) cmd.Parameters.Add("@ordid",SqlDbType.Int) cmd.Parameters("@ordid").Direction = ParameterDirection.Output cmd.ExecuteNonQuery() Console.WriteLine("Order placed, Order No: {0}", _ cmd.Parameters("@ordid").Value) Finally cn.Close() End Try End Sub End Module
Copyright 2001 K.M.Hussain <km@hussain.com> - 82 -

Programming .NET With Visual Basic.NET


The DataSet object is central to supporting disconnected data scenarios with ADO.NET. The DataSet is a memory-resident representation of data that provides a consistent relational programming model regardless of the data source. The DataSet represents a complete set of data including related tables, constraints, and relationships among the tables. The methods and objects in a DataSet are consistent with those in the relational database model. The program below (viewds.vb) fills all the records from the product table into a DataSet and then displays it. Imports System Imports System.Data Imports System.Data.SqlClient Module ViewDataSet Sub Main() Dim cn As New SqlConnection("server=localhost;uid=sa;pwd=;database=sales") Dim cmd As New SqlDataAdapter("select * from product",cn) Dim ds As New DataSet() cmd.Fill(ds,"products") Dim table As DataTable = ds.Tables("products") Dim column As DataColumn For Each column In table.Columns Console.Write("{0,-12}",column.ColumnName) Next Console.WriteLine() Dim row As DataRow For Each row In table.Rows Dim field As Object For Each field In row.ItemArray Console.Write("{0,-12}",field) Next Console.WriteLine() Next End Sub End Module Compile: vbc /r:system.dll,system.data.dll,system.xml.dll viewds.vb 1.3 Working with Disconnected Datasets: The above program uses an SqlDataAdapter which hold commands to view and update a database table. The program below (updateds.vb) shows how to update the data in the DataSet. It increases the price off each product by 10%. The SQL to select records is given to the SqlDataAdapter but SQLs for updating, inserting and deleting records are not provided. The SqlCommandBuilder can be used for automatic generation of these commands.
Copyright 2001 K.M.Hussain <km@hussain.com> - 83 -

Programming .NET With Visual Basic.NET


Imports System.Data Imports System.Data.SqlClient Module UpdateDataSet Sub Main() Dim cn As new SqlConnection("server=localhost;uid=sa;pwd=;database=sales") Dim cmd As New SqlDataAdapter("select * from product",cn) Dim cb As new SqlCommandBuilder(cmd) Dim ds As New DataSet() cmd.Fill(ds,"products") Dim row As DataRow For Each row In ds.Tables("products").Rows row("price") = 1.1 * (CDec(row("price"))) Next cmd.Update(ds,"products") End Sub End Module The next program (filterds1.vb) shows how to filter the records in a DataSet. It fills a DataSet with all the records in ord table and then displays the orders belonging to a particular customer whose id is passed through the first command-line argument. Imports System Imports System.Data Imports System.Data.SqlClient Module FilterDataSet1 Sub Main() Dim args As String() = Environment.GetCommandLineArgs() Dim cn As New SqlConnection("server=localhost;uid=sa;pwd=;database=sales") Dim cmd As New SqlDataAdapter("select * from ord",cn) Dim ds As New DataSet() cmd.Fill(ds,"orders") Dim filter As String = String.Format("cust_id = {0}",args(1)) Dim rows As DataRow() = ds.Tables("orders").Select(filter,"pno") Dim row As DataRow For Each row In rows Console.WriteLine(row("pno") & " " & row("qty")) Next End Sub End Module The next program (filterds2.vb) performs the same task as the above one but used DataView to filter the DataSet. Imports System Imports System.Data Imports System.Data.SqlClient
Copyright 2001 K.M.Hussain <km@hussain.com> - 84 -

Programming .NET With Visual Basic.NET


Module FilterDataSet2 Sub Main() Dim args As String() = Environment.GetCommandLineArgs() Dim cn As new SqlConnection("server=localhost;uid=sa;pwd=;database=sales") Dim cmd As New SqlDataAdapter("select * from ord",cn) Dim ds As New DataSet() cmd.Fill(ds,"orders") Dim dv As New DataView(ds.Tables("orders")) dv.Sort = "pno" dv.RowFilter = String.Format("cust_id = {0}",args(1)) Dim row As DataRowView For Each row In dv Console.WriteLine(row("pno") & " " & row("qty")) Next End Sub End Module It is also possible to associate rows in one DataTable of a DataSet with the rows in another DataTable of that DataSet by creating a DataRelation. Relationships enable navigation from one table to another within a DataSet. The program below (relationalds.vb) uses DataRelation to display each customer s cust_id , email (from customer table) and order_no (from order table) of all his orders. Imports System Imports System.Data Imports System.Data.SqlClient Module RelationalDataSet Sub Main() Dim cn As New SqlConnection("server=localhost;uid=sa;pwd=;database=sales") Dim cmdcust As New SqlDataAdapter("select cust_id,email from customer",cn) Dim cmdord As New SqlDataAdapter("select cust_id,ord_no from ord",cn) Dim ds As new DataSet() cmdcust.Fill(ds,"customers") cmdord.Fill(ds,"orders") ds.Relations.Add("custord", _ ds.Tables("customers").Columns("cust_id"), _ ds.Tables("orders").Columns("cust_id")) Dim customer As DataRow For Each customer In ds.Tables("customers").Rows Console.WriteLine("Customer ID: {0}",customer("cust_id")) Console.WriteLine("Email : {0}",customer("email")) Console.Write("Order No : ") Dim order As DataRow For Each order In customer.GetChildRows(ds.Relations("custord")) Console.Write("{0,-8}",order("ord_no"))
Copyright 2001 K.M.Hussain <km@hussain.com> - 85 -

Programming .NET With Visual Basic.NET


Next Console.WriteLine() Console.WriteLine()

Next End Sub End Module

A copy of data in a DataSet can be stored in the XML format using its WriteXml method. The program below (dstoxml.vb) stores all the records in the product table in prod.xml file. Imports System Imports System.Data Imports System.Data.SqlClient Module DataSetToXml Sub Main() Dim cn As New SqlConnection("server=localhost;uid=sa;pwd=;database=sales") Dim cmd As New SqlDataAdapter("select * from product",cn) Dim ds As New DataSet("products") cmd.Fill(ds,"product") ds.WriteXml("prod.xml") End Sub End Module The content of prod.xml generated by the above program is as follows: <?xml version="1.0" standalone="yes"?> <products> <product> <pno>101</pno> <price>350</price> </product> <product> <pno>102</pno> <price>975</price> </product> . </products> Content of a valid xml file can also be loaded in a DataSet for further processing. The next program (xmltods.vb) loads prod.xml in a DataSet and displays its records. Imports System Imports System.Data Imports System.Xml Module XmlToDataSet
Copyright 2001 K.M.Hussain <km@hussain.com> - 86 -

Programming .NET With Visual Basic.NET


Sub Main() Dim datadoc As New XmlDataDocument() Dim ds As DataSet = datadoc.DataSet ds.ReadXml("prod.xml") Dim table As DataTable = ds.Tables(0) Dim column As DataColumn For Each column In table.Columns Console.Write("{0,-12}",column.ColumnName) Next Console.WriteLine() Dim row As DataRow For Each row In table.Rows Dim field As Object For Each field In row.ItemArray Console.Write("{0,-12}",field) Next Console.WriteLine() Next End Sub End Module

Copyright 2001 K.M.Hussain <km@hussain.com>

- 87 -

Programming .NET With Visual Basic.NET


Chapter 10: GUI Programming with Windows Forms
1.1 Window Forms and Controls: Windows Forms is the new platform for Microsoft Windows application development, based on the .NET Framework. This framework provides a clear, object-oriented, extensible set of classes that enable you to develop rich Windows applications. Additionally, Windows Forms can act as the local user interface in a database application. A form is a bit of screen real estate, usually rectangular, that you can use to present information to the user and to accept input from the user. Forms can be standard windows, multiple document interface (MDI) windows, dialog boxes, or display surfaces for graphical routines. The easiest way to define the user interface for a form is to place controls on its surface. Forms are objects that expose properties which define their appearance, methods which define their behavior, and events which define their interaction with the user. By setting the properties of the form and writing code to respond to its events, you customize the object to meet the requirements of your application. As with all objects in the .NET Framework, forms are instances of classes. The framework also allows you to inherit from existing forms to add functionality or modify existing behavior. When you add a form , you can choose whether it inherits from the Form class provided by the framework, or from a form youve previously created. The form is the primary vehicle for user interaction. By combining different sets of controls and writing code, you can elicit information from the user and respond to it, work with existing stores of data, and query and write back to the file system and registry on the users local computer. The program (winhello.vb) below displays a simple window with one Button on it. The button s Click event and the Form s Closing event are handled to provide user interactions. Imports System Imports System.Windows.Forms Imports System.ComponentModel Imports System.Drawing Class WinHello : Inherits Form Private WithEvents bnclick As Button Public Sub New() Text = "Hello World" Size = New Size(400,400) bnclick = New Button() bnclick.Text = "Click Me" bnclick.Size = New Size(60,24) bnclick.Location = New Point(20,60)
Copyright 2001 K.M.Hussain <km@hussain.com> - 88 -

Programming .NET With Visual Basic.NET


Controls.Add(bnclick) End Sub Private Sub bnclick_Click(sender As Object, ea As EventArgs) Handles bnClick.Click MessageBox.Show("Hello World!!!!!","Button Clicked", MessageBoxButtons.OK, _ MessageBoxIcon.Information) End Sub Private Sub WinHello_Closing(sender As Object, cea As CancelEventArgs) Handles _ MyBase.Closing If MessageBox.Show("Are you sure?","Confirm exit", _ MessageBoxButtons.YesNo, MessageBoxIcon.Question) = DialogResult.No _ Then cea.Cancel = True End Sub Shared Sub Main() Application.Run(New WinHello()) End Sub End Class Compile: vbc /t:winexe /r:system.dll,system.drawing.dll,system.windows.forms.dll winhello.vb Our next example (readpad.vb) demonstrates how to create an MDI (multiple document interface) windows application using WindowForms. It also shows the use of menus and dialog box. Imports System Imports System.Drawing Imports System.ComponentModel Imports System.Windows.Forms Imports System.IO Imports System.Text namespace ReadPad Public Class MainForm : Inherits Form Private miopen As MenuItem Private misep As MenuItem Private miexit As MenuItem Private mifile As MenuItem Private startDir As String = "C:\" Private client As MdiClient Public Sub New() Text = "ReadPad"
Copyright 2001 K.M.Hussain <km@hussain.com> - 89 -

Programming .NET With Visual Basic.NET


WindowState = FormWindowState.Maximized client = new MdiClient() client.Dock = DockStyle.Fill Controls.Add(client) IsMdiContainer = True miopen = New MenuItem() AddHandler miopen.Click, AddressOf miopen_Click miopen.Text = "&Open..." misep = New MenuItem() misep.Text = "-" miexit = New MenuItem() miexit.Text = "E&xit" AddHandler miexit.Click, AddressOf miexit_Click mifile = New MenuItem() mifile.Text = "&File" mifile.MenuItems.AddRange(New MenuItem(){miopen,misep,miexit}) Menu = New MainMenu(New MenuItem(){mifile}) End Sub Private Sub LoadFile(fname As String) If fname <> "" Then Dim s As New FileStream(fname,FileMode.Open) Dim buffer As Byte() = New Byte(CInt(s.Length)){} s.Read(buffer,0,buffer.Length) s.Close() Dim cfrm As New ChildForm() cfrm.MdiParent = Me cfrm.txtout.Text = Encoding.ASCII.GetString(buffer) Dim info As New FileInfo(fname) cfrm.Text = info.Name startDir = info.Directory.Name cfrm.Show() End If End Sub Private Sub miopen_Click(sender As Object, e As EventArgs) Dim dlg As New OpenFileDialog() dlg.InitialDirectory = startDir dlg.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*" If dlg.ShowDialog() = DialogResult.OK Then LoadFile(dlg.FileName) End Sub
Copyright 2001 K.M.Hussain <km@hussain.com> - 90 -

Programming .NET With Visual Basic.NET


Private Sub miexit_Click(sender As Object, e As EventArgs) Application.Exit() End Sub Shared Sub Main() Application.Run(New MainForm()) End Sub End Class Public Class ChildForm : Inherits Form Friend txtout As TextBox Public Sub New() txtout = New TextBox() txtout.BackColor = SystemColors.Window txtout.Dock = DockStyle.Fill txtout.Multiline = true txtout.ReadOnly = true txtout.ScrollBars = ScrollBars.Both txtout.WordWrap = false Controls.Add(txtout) Text = "ChildForm" End Sub End Class End Namespace 1.2 Window Forms and Data Access: Window Form controls can be bound to fields in a DataSet for viewing and updating data in the database. The program below (windbtest1.vb) displays product number and price of the products in product table using two text boxes. Two buttons are provides for user to navigate through the records. User can also update the price of a product. Imports System Imports System.Windows.Forms Imports System.Drawing Imports System.Data Imports System.Data.SqlClient class WinDBTest1 : Inherits Form Private cn As New SqlConnection("server=localhost;uid=sa;pwd=;database=sales1") Private txtpno As TextBox Private txtprice As TextBox Private bnprev As Button, bnnext As Button Private cmdprod As SqlDataAdapter
Copyright 2001 K.M.Hussain <km@hussain.com> - 91 -

Programming .NET With Visual Basic.NET


Private ds As DataSet Private bm As BindingManagerBase Private Dirty As Boolean = False Public Sub New() txtpno = New TextBox() txtpno.Location = New Point(60,40) txtpno.Size = New Size(120,20) txtpno.ReadOnly = true Controls.Add(txtpno) txtprice = New TextBox() txtprice.Location = New Point(60,60) txtprice.Size = New Size(120,20) Controls.Add(txtprice) bnprev = New Button() bnprev.Text = "<--" bnprev.Location = New Point(80,100) bnprev.Size = New Size(40,20) Controls.Add(bnprev) bnnext = New Button() bnnext.Text = "-->" bnnext.Location = New Point(120,100) bnnext.Size = New Size(40,20) Controls.Add(bnnext) cmdprod = New SqlDataAdapter("select pno, price from product",cn) ds = New DataSet() cmdprod.Fill(ds, "products") txtpno.DataBindings.Add("Text",ds,"products.pno") txtprice.DataBindings.Add("Text",ds,"products.price") bm = BindingContext(ds,"products") AddHandler bnprev.Click, AddressOf bnprev_Click AddHandler bnnext.Click, AddressOf bnnext_Click AddHandler bm.PositionChanged, AddressOf bm_PositionChanged AddHandler bm.CurrentChanged, AddressOf bm_CurrentChanged bm_PositionChanged(Nothing, Nothing) End Sub Private Sub bnprev_Click(sender As Object, ev As EventArgs) If bm.Position > 0 Then bm.Position -= 1 End Sub Private Sub bnnext_Click(sender As Object, ev As EventArgs) If bm.Position < bm.Count -1 Then bm.Position += 1 End Sub Private Sub bm_PositionChanged(sender As Object, ev As EventArgs) Text = String.Format("Record: {0}/{1}",bm.Position+1,bm.Count)
Copyright 2001 K.M.Hussain <km@hussain.com> - 92 -

Programming .NET With Visual Basic.NET


End Sub Private Sub bm_CurrentChanged(sender As Object, ev As EventArgs) Dirty = True End Sub Protected Overloads Overrides Sub Dispose(disposing As Boolean) MyBase.Dispose(disposing) If Dirty Then Dim cb As New SqlCommandBuilder(cmdprod) cmdprod.Update(ds,"products") End If End Sub Shared Sub Main() Application.Run(New WinDBTest1()) End Sub End Class Compile: vbc /t:winexe /r:system.dll,system.data.dll,system.xml.dll,system.drawing.dll,system.windows.for ms.dll windbtest1.vb Our next program (windbtest2.vb) demonstrates more complex type of data binding. It consists of a drop-down combo box which displays all the customer ids from the customer table and data grid which displays all the orders of a customer selected in the drop-down list. Imports System Imports System.Windows.Forms Imports System.Drawing Imports System.Data Imports System.Data.SqlClient Class WinDBTest2 : Inherits Form Private cn As New SqlConnection("server=localhost;uid=sa;pwd=;database=sales1") Private cbocust As ComboBox Private dgord As DataGrid Private cmdcust As SqlDataAdapter, cmdord As SqlDataAdapter Private ds As DataSet Public Sub New() Text = "View Orders" cbocust = New ComboBox() cbocust.DropDownStyle = ComboBoxStyle.DropDownList cbocust.Dock = DockStyle.Top Controls.Add(cbocust)
Copyright 2001 K.M.Hussain <km@hussain.com> - 93 -

Programming .NET With Visual Basic.NET


dgord = New DataGrid() dgord.ReadOnly = true dgord.Dock = DockStyle.Fill Controls.Add(dgord) cmdcust = New SqlDataAdapter("select cust_id from customer",cn) ds = New DataSet() cmdcust.Fill(ds, "customers") cbocust.DataSource = ds.Tables("customers").DefaultView cbocust.DisplayMember = "cust_id" cmdord = New SqlDataAdapter("select * from ord",cn) cmdord.Fill(ds,"orders") AddHandler cbocust.SelectedIndexChanged, _ AddressOf cbocust_SelectedIndexChanged End Sub Private Sub cbocust_SelectedIndexChanged(sender As Object, ev As EventArgs) Dim drv As DataRowView = CType(cbocust.SelectedItem, DataRowView) Dim cust As String = CStr(drv("cust_id")) Dim dv As New DataView(ds.Tables("orders")) dv.RowFilter = "cust_id = "+cust+"" dgord.DataSource = dv End Sub Shared Sub Main() Application.Run(New WinDBTest2()) End Sub End Class 1.3 Creating Custom Controls: Creating custom controls has always been the most interesting part of Windows application development. Windows.Forms.Control can be used as a base class for creating custom controls for Window Forms. The class below (threedlbl.vb) is a control which displays a text provided by the user in a 3D style. It exposes ShadowColor property and an event called Activated (fires when user double clicks on the label) Imports System Imports System.Windows.Forms Imports System.Drawing Public Class ThreeDLabel : Inherits Control Public Event Activated As EventHandler Private _shadowColor As Color Public Sub New() Text = "3DLabel" shadowColor = Color.White End Sub
Copyright 2001 K.M.Hussain <km@hussain.com> - 94 -

Programming .NET With Visual Basic.NET


Public Property ShadowColor As Color Get ShadowColor = _shadowColor End Get Set _shadowColor = Value Refresh() End Set End Property Public Overrides Property Text As String Get Text = MyBase.Text End Get Set MyBase.Text = Value Refresh() End Set End Property Protected Overrides Sub OnPaint(e As PaintEventArgs) MyBase.OnPaint(e) Dim x As Single = ClientRectangle.X Dim y As Single = ClientRectangle.Y e.Graphics.DrawString(Text, _ Font, _ new SolidBrush(_shadowColor), _ x, y) e.Graphics.DrawString(Text, _ Font, _ new SolidBrush(ForeColor), _ x+1, y+1) End Sub Protected Overrides Sub OnResize(e As EventArgs) Refresh() End Sub Protected Overrides Sub OnDoubleClick(e As EventArgs) RaiseEvent Activated(Me,e) End Sub End Class Compile: vbc /t:library /r:system.dll,system.drawing.dll,system.windows.forms.dll threedlbl.vb
Copyright 2001 K.M.Hussain <km@hussain.com> - 95 -

Programming .NET With Visual Basic.NET


The program below (thdlbltest.vb) tests the above control Imports System Imports System.Windows.Forms Imports System.ComponentModel Imports System.Drawing Class ThreeDLabelTest : Inherits Form Private WithEvents tdlheader As ThreeDLabel Public Sub New() Text = "3D Label Control Test" Size = New Size(400,400) tdlheader = New ThreeDLabel() tdlheader.Font = new Font("Times New Roman",16,FontStyle.Bold) tdlheader.Text = "Double Click Me" tdlheader.Size = New Size(120,40) tdlheader.Location = New Point(20,60) Controls.Add(tdlheader) End Sub Private Sub tdlheader_Activated(sender As Object, ea As EventArgs) Handles _ tdlheader.Activated MessageBox.Show("Hello World!!!!!","Label Activated", MessageBoxButtons.OK, _ MessageBoxIcon.Information) End Sub Shared Sub Main() Application.Run(New WinHello()) End Sub End Class Compile: vbc /t:winexe /r:threedlbl.dll,system.dll,system.drawing.dll,system.windows.forms.dll threedlbl.vb

Copyright 2001 K.M.Hussain <km@hussain.com>

- 96 -

Programming .NET With Visual Basic.NET


Chapter 11: Web Programming with ASP.NET
1.1 Web Forms and Server Controls: ASP.NET is a unified Web development platform that provides the services necessary for developers to build enterprise-class Web applications. ASP.NET also provides a new programming model and infrastructure that enables a powerful new class of applications. ASP.NET is a compiled .NET-based environment; you can author applications in any .NET compatible language, including Visual Basic, C# and JScript. Additionally, the entire .NET Framework is available to any ASP.NET application. Developers can easily access the benefits of these technologies, which include a managed Common Language Runtime environment, type safety, inheritance, and so on. Web Forms allows you to build powerful forms-based Web pages. When building these pages, you can use ASP.NET server controls to create common UI elements and program them for common tasks. These controls allow you to rapidly build up a Web Form out of reusable built-in or custom components, simplifying the code of a page. The examples given below assume existence of a virtual directory called myweb on IIS5.0 executing on the localhost The pages created in this chapter must be stored in this directory. Our first ASP.NET page (simplehello.aspx) accepts visitors name in a textbox and says hello to him. <%@ Page Language="VB" %> <html> <head> <title>simplehello.aspx</title> </head> <body> <center> <form> <b>Name:</b> <input name="myname"> <input type="submit" value="Submit"> </form> <% Dim name As String = Request.QueryString("myname") If Not name Is Nothing Then If name = "" Then name = "Visitor" %> <h1>Hello <%=name%> </h1> <%End If%> </center> </body> </html> View the page: http://localhost/myweb/simplehello.aspx

Copyright 2001 K.M.Hussain <km@hussain.com>

- 97 -

Programming .NET With Visual Basic.NET


The page begins with a page directive identifying the language used for server-side scripting. The VB.Net code is embedded within <% %>. ASP.NET offers server controls which makes development of web based interface as simple as that of a typical graphical user interface. Our next page (webfrmhello.aspx) functions in the same way as the above page but uses server controls instead. <html> <head> <title>webfrmhello.aspx</title> <script language="VB" runat = "server"> Private Sub SubmitBtn_Click(sender As Object, ev As EventArgs) Dim name As String = NameTxt.Text If name = "" Then name = "Visitor" HelloLbl.Text = "Hello "+name End Sub </script> </head> <body> <center> <form runat="server"> <b>Name:</b> <asp:TextBox id="NameTxt" runat = "server" /> <asp:Button Text="Submit" OnClick="SubmitBtn_Click" runat="server" /> </form> <h1><asp:label id="HelloLbl" runat="server" /></h1> </center> </body> </html> Server controls are inserted using server-side tags: <TagPrefix:ControlName id = SomeCtl RunAt= Server /> Note: the event handling for server controls is somewhat similar to the event handling for Windows Forms controls. Developers can also build their own server controls. A custom control class must inherit from class System.Web.UI.Control. ASP.NET will draw the control by invoking its Render method which can be overridden. Given below is a simple web control (mywebctls.vb) which displays the value of its text property in rainbow colors. Imports System Imports System.Web Imports System.Web.UI Imports System.Drawing Namespace MyWebControls Public Class RainBowLabel : Inherits Control
Copyright 2001 K.M.Hussain <km@hussain.com> - 98 -

Programming .NET With Visual Basic.NET


Public Shared colors As Color() = {Color.Violet, Color.Indigo, Color.Blue, _ Color.Green, Color.Yellow, Color.Orange, Color.Red} Private _text As String Public Property Text As String Get Text = _text End Get Set _text = Value End Set End Property Private Shared Function ToHtml(c As Color) As String ToHtml = "#"+c.R.ToString("X2")+c.G.ToString("X2")+c.B.ToString("X2") End Function Protected Overrides Sub Render(page As HtmlTextWriter) If _text = Nothing Then Exit Sub Dim col As Integer = 0 Dim ch As Char For Each ch In _text If Char.IsWhiteSpace(ch) Then page.Write(ch) Else page.Write( _ "<font color={0}>{1}</font>",ToHtml(colors(col)),ch) col = col + 1 If col = 7 Then col=0 End If Next End Sub End Class End Namespace Compile mywebctls.vb: vbc /r:system.dll,system.drawing.dll,system.web.dll /t:library mywebctls.vb to create a dll and store this dll in the bin directory within the virtual directory. Given below is an ASP.NET page (myctltest.aspx) which demonstrates how to register and use the above control. Note how the Text property of HelloLbl is set to a data-binding expression using <%# %> syntax. These type of expressions are resolved when DataBind() method is invoked. <%@ Register TagPrefix="My" Namespace="MyWebControls" Assembly="mywebctls"%> <html> <head> <title>MyCtlTest.aspx</title> <script language="VB" runat="server">
Copyright 2001 K.M.Hussain <km@hussain.com> - 99 -

Programming .NET With Visual Basic.NET


Private Sub GoBtn_Click(sender As Object,ev As EventArgs) DataBind() resolve all data-binding expressions in the current page End Sub </script> <head> <body><center> <form runat="server"> <b>Name:</b> <asp:TextBox id="NameTxt" runat="server"/> <asp:Button Text="GO!" onclick="GoBtn_Click" runat="server"/> </form> <h1><my:RainbowLabel id="HelloLbl" Text=<%# NameTxt.Text %> runat="server"/></h1> </center> </body> </html> Another method for creating custom server controls is through pagelets. A pagelet is itself an ASP.NET page which can be embedded within other pages as a control. Pagelets are stored in files with extension ascx. Given below is a pagelet (login.ascx) which builds a login form and exposes UserId and Password properties.. <script language="VB" runat="server"> Public ReadOnly Property UserId As String Get UserId = UsrTxt.Text End Get End Property Public ReadOnly Property Password As String Get Password = PwdTxt.Text End Get End Property </script> <table border="0" bgcolor="#c9c9c9"> <tr> <td><b>Customer ID: </b></td> <td> <asp:TextBox id="UsrTxt" runat="server"/> <asp:RequiredFieldValidator ControlToValidate="UsrTxt" Display="Dynamic" ErrorMessage="*" runat="server"/> </td> </tr> <tr> <td><b>Password: </b></td> <td> <asp:TextBox id="PwdTxt" TextMode="Password" runat="server"/>
Copyright 2001 K.M.Hussain <km@hussain.com> - 100 -

Programming .NET With Visual Basic.NET


<asp:RequiredFieldValidator ControlToValidate="PwdTxt" Display="Dynamic" ErrorMessage="*" runat="server"/> </td> </tr> <tr align="center"> <td colspan="2"> <asp:Button id="SubmitBtn" Text="Login" runat="server"/> </td> </tr> </table> The above pagelet also uses Validator controls. If user leaves a field empty, RequiredFieldValidator will display the text in ErrorMessage. 1.2 Web Forms and Data Access: Accessing databases from ASP.NET applications is an often-used technique for displaying data to Web site visitors. ASP.NET makes it easier than ever to access databases for this purpose - and provides for managing the data in the database. The examples in this section require MS SQLServer (or MSDE) containing sales database mentioned in Chapter 9 and an SMTP Server to be running on IIS host. Create a (non-virtual) subdirectory called sales in the current virtual directory (myweb). Also place the following Web.Config file inside the virtual directory containing the sales directory <configuration> <system.web> <authentication mode= "Forms"> <forms name="salesauth" loginUrl="sales/login.aspx" /> </authentication> </system.web> <!-- Following configuration applies only to the sales subdirectory --> <location path="sales"> <system.web> <authorization> <!-- Deny all unauthorized users --> <deny users="?" /> </authorization> </system.web> </location> </configuration> The above configuration file indicates that the contents of sales subdirectory of the current virtual directory are only accessible to authorized users. An unauthorized user must be redirected to sales/login.aspx for authentication. Given below is login.aspx (place
Copyright 2001 K.M.Hussain <km@hussain.com> - 101 -

Programming .NET With Visual Basic.NET


this file in sales subdirectory) which uses login.ascx pagelet created in the previous section and customer table of sales database to authorize a user. <%@ Register TagPrefix="My" TagName="Login" Src="../login.ascx" %> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %> <html> <head> <title>login.aspx</title> <script language="VB" runat="server"> Private Sub Page_Load(sender As Object, ev As EventArgs) If Not Page.IsPostBack Then Exit Sub Dim cn As New SqlConnection("server=localhost;database=sales;uid=sa") Dim sql As String = String.Format( _ "select email from customer where cust_id={0} and pwd={1}", _ CustLogin.UserId, CustLogin.Password) Dim cmd As New SqlCommand(sql, cn) cn.Open() Try Dim dr As SqlDataReader = cmd.ExecuteReader() If dr.Read() Then Session("email") = dr("email") Create authentication ticket with user name = CustLogin.UserId Second argument specifies whether this ticket must be saved across browser sessions FormsAuthentication.SetAuthCookie(CustLogin.UserId, False) Check which URL the user had originally requested for, if none use order.aspx Dim retUrl = Request.QueryString("ReturnUrl") If retUrl Is Nothing Then retUrl = "order.aspx" Response.Redirect("order.aspx") Else ResultLbl.Text = "Login Failed" End If dr.Close() Catch e As Exception ResultLbl.Text = e.Message Finally cn.Close() End Try End Sub </script> </head> <body> <center> <h2>Login Form</h2> <form action="login.aspx" method="post" runat="server">
Copyright 2001 K.M.Hussain <km@hussain.com> - 102 -

Programming .NET With Visual Basic.NET


<My:Login id="CustLogin" runat="server"/> </form> <b><asp:Label id="ResultLbl" runat="server"/></b> <br><br><br> </center> </body> </html> The login page checks the UserId and Password of the user against the values in the customer table, if the values are correct an authenticated user is created with user name same as the UserId (this user name can be accessed in other pages using User.Identity.Name). The email of the user is stored in the Session object (the Session object allows data to be stored on a per client session basis) with a key email . Finally the user is redirected to the originally requested page or to order.aspx (in sales subdirectory) given below <%@ Import Namespace="System.Web.Mail" %> <html> <head> <title>order.aspx</title> <object id="SalesComp" class="MyWeb.SalesComponent" runat="server" /> <script language="VB" runat="server"> Private Sub Page_Load(sender As Object, ev As EventArgs) If Page.IsPostBack Then Exit Sub ProdLst.DataSource = SalesComp.GetProducts() ProdLst.DataTextField = "pno" ProdLst.DataBind() End Sub Private Sub SubmitBtn_Click(sender As Object, ev As EventArgs) Dim cust As String = User.Identity.Name Dim email As String = CStr(Session("email")) Try Dim prod As Integer = CInt(ProdLst.SelectedItem.Text) Dim quant As Integer = CInt(QuantTxt.Text) Dim ordid As Integer = SalesComp.PlaceOrder(cust, prod, quant) Dim msg As String=String.Format("You ordered {0} items of product {1}", _ quant, prod) SmtpMail.Send("my@webstore.net", email, "Order Confirmed", msg) ResultLbl.Text = String.Format("Order Number: {0}",ordid) Catch e As Exception ResultLbl.Text = e.Message End Try End Sub </script> </head> <body>
Copyright 2001 K.M.Hussain <km@hussain.com> - 103 -

Programming .NET With Visual Basic.NET


<center> <h2>Order Entry Form</h2> <table border="0" bgcolor="#c9c9c9"> <form action="order.aspx" method="post" runat="server"> <tr> <td><b>Product No: </b></td> <td> <asp:DropDownList id="ProdLst" runat="server"/> </td> </tr> <tr> <td><b>Quantity: </b></td> <td> <asp:TextBox id="QuantTxt" runat="server"/> <asp:RequiredFieldValidator ControlToValidate="QuantTxt" Display="Dynamic" ErrorMessage="Quantity is required" runat="server"/> <asp:RegularExpressionValidator ControlToValidate="QuantTxt" ValidationExpression="[0-9]{1,}" Display="Dynamic" ErrorMessage="Quantity must be a number" runat="server"/> </td> </tr> <tr align="center"> <td colspan="2"> <asp:Button id="SubmitBtn" Text="Order" OnClick="SubmitBtn_Click" runat="server"/> </td> </tr> </form> </table> <b><asp:Label id="ResultLbl" runat="server"/></b> <p><a href="view.aspx">View Orders</a> <a href="logout.aspx">Logout</a></p> </center> </body> </html> When the user accesses this page, the drop-down list (ProdList) is populated with product numbers (pno field) from the product table. When user submits the form (Page.IsPostBack will be true and Page_Load will return without repopulating ProdList) the customer id is obtained from User.Identity.Name and email id is obtained from the Session object, the order is placed and an email confirming the order is also sent to the customer (SMTP Server must be available on the IIS host). If customer id is not found in the Session (user has not logged in) then the user is redirected to the login page. In above page the QuantTxt field is also validated by RegularExpressionValidator which checks whether this field contains one or more digit characters ([0-9]{1,}). This page also provides links to two other pages, view.aspx which displays all the orders of the logged customer and logout.aspx which destroys the customer s session. To separate the logic from the presentation all the database related code is encapsulated in the SalesComponent
Copyright 2001 K.M.Hussain <km@hussain.com> - 104 -

Programming .NET With Visual Basic.NET


class which is instantiated in order.aspx using the object tag. The source code of this component (salescomp.vb) is as follows Imports System Imports System.Web Imports System.Data Imports System.Data.SqlClient Namespace MyWeb Public Class SalesComponent Private cn As SqlConnection Public Sub New cn = New SqlConnection("server=localhost;database=sales;uid=sa") End Sub Public Function GetProducts() As DataTable Dim cmd As New SqlDataAdapter("select pno from product", cn) Dim ds As New DataSet() cmd.Fill(ds,"products") GetProducts = ds.Tables("products") End Function Public Function PlaceOrder(customer As String, product As Integer, _ quantity As Integer) As Integer Dim cmd As New SqlCommand() cmd.Connection = cn cn.Open() cmd.Transaction = cn.BeginTransaction() Dim ordid As Integer Try cmd.CommandText = "update ord_ctl set ord_no = ord_no + 1" cmd.ExecuteNonQuery() cmd.CommandText = "select ord_no from ord_ctl" ordid = CInt(cmd.ExecuteScalar()) cmd.CommandText = _ "insert into ord values(@ord_no,@ord_date,@cust_id,@pno,@qty)" cmd.Parameters.Add("@ord_no", SqlDbType.Int).Value = ordid cmd.Parameters.Add("@ord_date", SqlDbType.DateTime).Value = _ DateTime.Now cmd.Parameters.Add("@cust_id", SqlDbType.VarChar,8).Value = _ customer cmd.Parameters.Add("@pno", SqlDbType.Int).Value = product cmd.Parameters.Add("@qty", SqlDbType.Int).Value = quantity cmd.ExecuteNonQuery() cmd.Transaction.Commit()
Copyright 2001 K.M.Hussain <km@hussain.com> - 105 -

Programming .NET With Visual Basic.NET


Catch e As Exception cmd.Transaction.Rollback() Throw e Finally cn.Close() End Try PlaceOrder = ordid End Function End Class End Namespace Compile the above code to create a dll in the bin directory of the application. By default every ASP.NET page is directly derived from System.Web.UI.Page class. But it s possible to derive an ASP.NET page from a custom subclass of System.Web.UI.Page class (such classes are referred to as codebehind classes ). For example our next page (view.aspx) which displays all the orders placed by the logged customer in a DataGrid inherits from MyWeb.OrderViewPage class defined in view.aspx.vb. <%@ Page Language="VB" Src="view.aspx.vb" Inherits="MyWeb.OrderViewPage"%> <html> <head> <title>view.aspx</title> </head> <body> <center> <h2>Your Orders</h2> <asp:DataGrid id="OrdGrid" runat="server"/> </center> </body> </html> Here is the source code (view.aspx.vb) for OrderViewPage class (there is no need to compile this file) Imports System Imports System.Web Imports System.Web.UI Imports System.Web.UI.WebControls Imports System.Data Imports System.Data.SqlClient Namespace MyWeb Public Class OrderViewPage : Inherits Page Protected OrdGrid As DataGrid
Copyright 2001 K.M.Hussain <km@hussain.com> - 106 -

Programming .NET With Visual Basic.NET


Private Sub Page_Load(sender As Object, ev As EventArgs) Dim cust As String = User.Identity.Name Dim cn As New SqlConnection("server=localhost;database=sales;uid=sa") Dim sql As String = String.Format( _ "select ord_no,ord_date,pno,qty from ord where cust_id={0}",cust) Dim cmd As New SqlDataAdapter(sql, cn) Dim ds As New DataSet() cmd.Fill(ds,"orders") OrdGrid.DataSource = ds.Tables("orders") OrdGrid.DataBind() End Sub End Class End Namespace Finally the code for logout.aspx which signs out the user and destroys his session is as follows. <script language="VB" runat="server"> Private Sub Page_Load(sender As Object, ev As EventArgs) FormsAuthentication.SignOut() Session.Abandon() Response.Redirect("login.aspx") End Sub </script> 1.3 Creating Web Services: A Web service is a way to access server functionality remotely. Using services, businesses can expose programmatic interfaces to their data or business logic, which in turn can be obtained and manipulated by client and server applications. Web services enable the exchange of data in client-server or server-server scenarios, using standards like HTTP and XML messaging to move data across firewalls. Web services are not tied to a particular component technology or object-calling convention. As a result, programs written in any language, using any component model, and running on any operating system can access Web services. For example consider a business method GetPrice( ) which accepts product number and quantity and returns the total price. The price of the product is obtained from the product table and if the requested quantity is 50 or more a 5% discount is offered. We can expose this method to all our clients using a web service. Under ASP.NET a web-service is deployed in a page with an extension asmx. Given below (priceservice.asmx) shows an implementation of above mentioned GetPrice method as a web-service. <%@ WebService Language="VB" Class="PriceService"%> Imports System Imports System.Data Imports System.Data.SqlClient Imports System.Web.Services
Copyright 2001 K.M.Hussain <km@hussain.com> - 107 -

Programming .NET With Visual Basic.NET


<WebService(Namespace:="http://my.webstore.net/")> Public Class PriceService Inherits WebService <WebMethod> Public Function GetPrice(pno As Integer, qty As Integer) As Decimal Dim cn As New SqlConnection("server=localhost;database=sales;uid=sa") Dim sql As String = String.Format("select price from product where pno={0}",pno) Dim cmd As New SqlCommand(sql, cn) cn.Open() Dim price As Decimal = -1 Try Dim dr As SqlDataReader = cmd.ExecuteReader() If dr.Read() Then price = qty * dr.GetDecimal(0) dr.Close() If qty >= 50 Then price = 0.95 * price Finally cn.Close() End Try GetPrice = price End Function End Class The service can be tested by directly accessing priceservice.asmx in the browser using its proper URL. To consume the service in a client application, a proxy class for it must be created. This can be done using wsdl.exe utility available with the .NET SDK. wsdl /l:VB http://localhost/myweb/priceservice.asmx The above command will create PriceService.vb Compile it: vbc /r:system.dll,system.xml.dll,syetm.web.dll,system.web.services.dll /t:library priceservice.vb and store the dll in the bin directory of the application PriceService class exposes GetPrice ( ) method, the implementation of which passes the call to the original web-service. PriceService can be instantiated in a regular exe client or in an ASP.NET page (in which case priceservice.dll must be stored in the application s bin subdirectory) and GetPrice method can be invoked. Given below (priceservicetest.vb) is a simple program which consumes the above web-service Imports System Module PriceServiceTest Sub Main() Dim args As String() = Environment.GetCommandLineArgs() Dim pno As Integer = Int32.Parse(args(1)) Dim qty As Integer = Int32.Parse(args(2)) Dim proxy As New PriceService() Console.WriteLine("Price : {0}",proxy.GetPrice(pno,qty)) End Sub End Module Compile: vbc /r:priceservice.dll,system.dll,system.web.services.dll priceclient.vb
Copyright 2001 K.M.Hussain <km@hussain.com> - 108 -

Programming .NET With Visual Basic.NET


Given below (prices.aspx) is an ASP.NET client for PriceService <html> <head><title>prices.aspx</title> <script language="VB" runat="server"> Private Sub SubmitBtn_Click(sender As Object, ev As EventArgs) Dim ws As New PriceService() Dim pno As Integer = Int32.Parse(PnoTxt.Text) Dim qty As Integer = Int32.Parse(QtyTxt.Text) Dim price As Double = ws.GetPrice(pno,qty) If price < 0 Then PriceLbl.Text = "No such product" Else PriceLbl.Text = String.Format("{0:#.00}",price) End If End Sub </script> </head> <body><center> <h1>Price Finder</h1> <table border="0" bgcolor="#c9c9c9"> <form action="prices.aspx" method="post" runat="server"> <tr> <td>Product No:</td><td><asp:TextBox id="PnoTxt" runat="server" /></td></tr> <tr><td>Quantity:</td><td><asp:TextBox id="QtyTxt" runat="server" /></td></tr> <tr><td>Total Price:</td><td><asp:Label id="PriceLbl" runat="server"/></td></tr> <tr align="center"><td colspan="2"> <asp:Button Text="Submit" OnClick="SubmitBtn_Click" runat="server"/> </td></tr> </form></table> </center></body> </html> An interesting feature of WebServices is SOAP headers. They offer a method for passing data to and from a Web Service method not directly related to its primary functionality. For instance, a Web Service may contain several Web Service methods all requiring a custom authorization scheme. Instead of adding parameters to each Web Service method for the custom authorization scheme, a SoapHeaderAttribute referring to a class deriving from SoapHeader can be applied to each Web Service method. The implementation for the class deriving from SoapHeader handles the actual custom authentication scheme. In this manner, the Web Service method implements just the functionality specific to it and adds additional functionality through the use of a SOAP header. Here are the basic steps to receiving and processing a SOAP header: 1. Create a class deriving from SoapHeader representing the data passed in the SOAP header.
Copyright 2001 K.M.Hussain <km@hussain.com> - 109 -

Programming .NET With Visual Basic.NET


2. Add a member to the Web Service class or Web Service client proxy class of the Type created in step 1. 3. Apply a SoapHeaderAttribute to the Web Service method or corresponding method in the proxy class, specifying the member created in step 2 in the MemberName property. 4. Within the Web Service method or Web Service client code access the MemberName property to process the data sent in the SOAP header. Given below is WebService (loanservice.asmx) which uses SOAP Headers for authorizing the callers of its methods <%@WebService Language="VB" Class="LoanService"%> Imports System Imports System.Configuration Imports System.Web.Services Imports System.Web.Services.Protocols Public Class AuthorizationHeader : Inherits SoapHeader Public UserId As String Public Password As String End Class <WebService(Namespace:="http://my.bank.net")> Public Class LoanService : Inherits WebService Public Authorization As AuthorizationHeader Public Function GetInstallment(P As Decimal, I As Single, M As Integer) As Decimal GetInstallment = P*I/(1-(1+I)^(-M)) End Function <WebMethod(), SoapHeader("Authorization")> _ Public Function GetRate(principal As Decimal, period As Integer) As Single Authorize() Select Principal Case Is < 100000 GetRate = 8 Case 100000 To 500000 GetRate = 9.5 Case Else GetRate = 11 End Select If period > 5 Then GetRate +=1 End Function <WebMethod(), SoapHeader("Authorization")> _ Public Function EMI(principal As Decimal, period As Integer) As Decimal Authorize()
Copyright 2001 K.M.Hussain <km@hussain.com> - 110 -

Programming .NET With Visual Basic.NET


Dim rate As Single = GetRate(principal,period) EMI = GetInstallment(principal,rate/1200,12*period) End Function Private Sub Authorize() Dim uid As String = ConfigurationSettings.AppSettings("uid") Dim pwd As String = ConfigurationSettings.AppSettings("pwd") If Authorization.UserId <> uid Or Authorization.Password <> pwd Then Throw New UnauthorizedAccessException() End If End Sub End Class The above WebService uses entries in web.config file (in the application directory) to authorize the caller. The content of web.config is as follows <configuration> <appSettings> <add key="uid" value="ken"/> <add key="pwd" value="barbie"/> </appSettings> </configuration> Generate the proxy for above webservice using wsdl.exe and test it using the client program (loantest.vb) given below Imports System Module LoanTest Sub Main() Dim args As String() = Environment.GetCommandLineArgs() Dim service As New LoanService() Dim auth As New AuthorizationHeader() auth.UserId = "ken" auth.Password = "barbie" service.AuthorizationHeaderValue=auth Dim emi As Decimal = service.EMI(CDec(args(1)), CInt(args(2))) Console.WriteLine(emi) End Sub End Module

Copyright 2001 K.M.Hussain <km@hussain.com>

- 111 -

Programming .NET With Visual Basic.NET


Chapter 12: Interop and Enterprise Services
1.1 Using Native DLLs and COM Components: .Net framework allows an easy interoperability with exisisting (unmanaged) windows legacies. A lot of windows code is available in form of native dlls or COM components. Its quite easy to use this code in your new VB.Net applications. Lets first see how to call functions exported from native dll from a VB.Net program. The VB.Net program (callnatdll.vb) below invokes _getch() function exported by msvcrt.dll to input a character from the keyboard without displaying it on the console. Imports System Imports System.Text Public Class CallNativeDll Declare Auto Function _getch Lib "msvcrt.dll"() As Char Public Shared Function ReadPassword() As String Dim sb As StringBuilder = New StringBuilder() Do Dim c As Char = _getch() input a character without echoing If c = Convert.ToChar(13) Then Console.WriteLine() Exit Do End If Console.Write("*") display a * sb.Append(c) Loop ReadPassword = sb.ToString() End Function End Class Module Test Sub Main() Console.Write("Enter Password: ") Dim p As String = CallNativeDll.ReadPassword() if p = "tiger" Then Console.WriteLine("SUCCESS") Else Console.WriteLine("FAILURE") End If End Sub End Module Check how _getch is redeclared in CallNativeDll class with Declare keyword. It is also possible to receive callbacks from native code. Our next example (natcallback.vb) displays handles and names of all open windows. To do so it uses a

Copyright 2001 K.M.Hussain <km@hussain.com>

- 112 -

Programming .NET With Visual Basic.NET


Win32 API function called EnumWindows from user32.dll. Whenever this function encounters a window it calls back a function whose pointer is passed to it. Imports System Imports System.Text Module NativeCallback Delegate Function EnumWindowsProc(hWnd As Integer, lParam As Integer) As Boolean Declare Auto Function EnumWindows Lib "user32" (ewp As EnumWindowsProc, lParam As Integer) As Integer Declare Auto Function GetWindowText Lib "user32" (hWnd As Integer, title As StringBuilder, size As Integer) _ As Integer Function PrintWindow(hWnd As Integer, lParam As Integer) As Boolean Dim title As StringBuilder = New StringBuilder(80) GetWindowText(hWnd, title, 80) Console.WriteLine("{0} {1}",hWnd,title) PrintWindow = True End Function Sub Main() EnumWindows(AddressOf PrintWindow, 0) End Sub End Module To receive the call back a delegate is passed to EnumWindows. Our PrintWindow method is called whenever EnumWindows encounters an open window passing it the handle of the window. We use another Win32 API function called GetWindowText to obtain the title of the window with a given handle. This function requires you to pass a pointer to a character buffer (in which the title of the window will be placed) and the size of this buffer. We have used StringBuilder for the character buffer. Many COM components are available under Windows Platform. These components can be incooperated in a VB.Net application with help of tlbimp.exe utility. This utility generates the necessary .NET wrapper class for a given COM component from its type library. The code below (callcom.vb) plays a media (.wav, .mid, .avi etc) file using a COM component present in quartz.dll (a part of windows installation). First generate the .NET Wrapper for the component: tlbimp c:\winnt\system32\quartz.dll (in case of Windows 98 use path c:\windows\system\quartz.dll).The above command will create QuartzTypeLib.dll which contains the required wrapper placed under QuartzTypeLib namespace.

Copyright 2001 K.M.Hussain <km@hussain.com>

- 113 -

Programming .NET With Visual Basic.NET


Imports System Imports QuartzTypeLib Module CallCom Sub Main() Dim args As String() = Environment.GetCommandLineArgs() Dim fm As New FilgraphManagerClass fm.RenderFile(args(1)) fm.Run() Dim ecode As Integer fm.WaitForCompletion(-1, ecode) 1: wait indefinitely Console.WriteLine("Terminated With Code {0}",ecode) End Sub End Module Compile: vbc /r:quartztypelib.dll callcom.vb Test: callcom c:\winnt\media\canyon.mid It is also possible to expose .NET components as COM components to unmanaged clients. Building components using COM can be messy because you need to track the IDL, type library, registry entries, and a lot of other COM-specific code just to get your components running. The .NET Framework makes deploying and running components far simpler, especially if the components and clients will live in the managed environment. Exposing code built for the .NET world as a COM component requires little modification and far less extra information than youd need in unmanaged code. Given below (bizcalc.vb) is a .NET component called BizCalc.Asset which can be used to calculate a price of a depreciating asset at a given period of time. Note: in order to expose a .NET component as a COM component, the class of the component must provide a public default constructor or no constructor at all. Namespace BizCalc Public Class Asset Public OriginalCost As Double Public AnnualDepreciationRate As Single Public Function GetPriceAfter(years As Integer) As Double Dim price As Double = OriginalCost Dim n As Integer For n = 1 To years price = price * (1 - AnnualDepreciationRate/100) Next GetPriceAfter = price End Function End Class End Namespace Use following steps to build and deploy the above .NET class as a COM component:
Copyright 2001 K.M.Hussain <km@hussain.com> - 114 -

Programming .NET With Visual Basic.NET


1. Create a strong name key file using sn.exe utility sn k mykey.snk This key file is necessary to deploy the component in the global assembly cache so that the clients can access it. You can use one key file for deploying multiple components. 2. Compile bizcalc.vb using the above key file to generate a shared library vbc /keyfile:mykey.snk /t:library bizcalc.vbc 3. Install the above bizcalc.dll in the global assembly cache (assembly folder under your windows/winnt folder) using the gacutil.exe utility gacutil -i bizcalc.dll 4. Register bizcalc.dll as a COM component using regasm.exe utility regasm /tlb:bizcalc.tlb bizcalc.dll This will also generate typelibrary (bizcalc.tlb) needed by some COM clients. 5. Test the above component using any COM containet. For example: Open a new Workbook in MS Excel. Open the Visual Basic Editor (Alt+F11). Open the code window for Sheet1 listed in project browser. From the tools menu select references and add a reference to bizcalc. Next write the following VBA macro in the code window. Sub CalculateDepreciation() Dim ast As New Asset Dim period As Integer ast.OriginalCost = CDbl(Cells(1, 1)) ast.AnnualDepreciationRate = CSng(Cells(1, 2)) period = CInt(Cells(1, 3)) Cells(1, 4) = ast.GetPriceAfter(period) End Sub Close VisualBasic editor and go to Sheet1. Enter values for OriginalCost, AnnualDepreciationRate and period in cells A1, B1 and C1 respectively. Run Sheet1.CalculateDepreciation macro (Alt + F8), Cell D1 will show the new price. 1.2 Messaging with MSMQ: Message Queuing technology allows applications running at different times to communicate across heterogeneous networks and systems, which might be temporarily offline. Applications send, receive, or peek (read without removing) messages from queues. Message Queuing is an optional component of Windows 2000, and must be installed separately. The System.Messaging.MessageQueue class is a wrapper around Message Queuing. The MessageQueue class provides a reference to a Message Queuing queue. You can specify a path in the MessageQueue constructor to connect to an existing resource, or you can create a new queue on the server. Before you can call Send, Peek, or Receive, you must associate the new instance of the MessageQueue class with an existing queue. The program below (sendmsg1.vb) sends a string messages to private queue called myq on the current machine.
Copyright 2001 K.M.Hussain <km@hussain.com> - 115 -

Programming .NET With Visual Basic.NET


Imports System Imports System.Messaging Module SendMessage1 Sub Main() Dim args As String() = Environment.GetCommandLineArgs() Dim qname As String = ".\private$\myq" Create this queue if it does not exist If Not MessageQueue.Exists(qname) Then MessageQueue.Create(qname) Dim mq As New MessageQueue(qname) Try mq.Send(args(1)) Finally mq.Close() End Try End Sub End Module The next program (receivemsg.vb) pops a message from a queue passed as the first command line argument and displays it on the console. While retrieving a message from the message queue the formatter property of the queue must be set to an instance of formatter used while sending the message. The default formatter used while sending the message is XmlMessageFormatter so in above program we have set the formatter to the same. The constructor of XmlMessageFormatter takes an array of string containing fully qualified names of the target types that make up the message. In our case the message is in form of a simple string. The Receive method retrieves and removes the message from the queue, if no message is available it blocks (you can pass a timespan to Receive method as an argument inorder to set the timeout for the blocking). The Peek() method of message queue also retrieves a message but without removing the message from the queue. Imports System Imports System.Messaging Module ReceiveMessages Sub Main() Dim args As String() = Environment.GetCommandLineArgs() Dim mq As New MessageQueue(args(1)) mq.Formatter = New XmlMessageFormatter(New String(){"System.String"}) Try Dim msg As Message = mq.Receive() blocks if no message Console.WriteLine(msg.Body) Finally mq.Close() End Try End Sub End Module
Copyright 2001 K.M.Hussain <km@hussain.com> - 116 -

Programming .NET With Visual Basic.NET


Several related messages can be coupled into a single transaction. Sending a message as part of a transaction ensures that the messages are delivered in order, delivered only once, and are successfully retrieved from their destination queue. An instance of MessageQueueTransaction is used to send multiple messages as a single transaction (cannot be used with non-transactional queues). In the program given below (sendmsg2.vb) we send two messages passed through command line arguments to a private message queue called ordq as a part of a transaction. If only one argument is passed, a runtime exception will occur and the transaction will be aborted as a result of which even the first message won t be sent. Imports System Imports System.Messaging Module SendMessage2 Sub Main() Dim args As String() = Environment.GetCommandLineArgs() Dim qname As String = ".\private$\ordq" Create this transactional(second argument=True) queue if it does not exist If Not MessageQueue.Exists(qname) Then MessageQueue.Create(qname, True) Dim mq As New MessageQueue(qname) Dim mqtx As New MessageQueueTransaction() mqtx.Begin() Try mq.Send(args(1), mqtx) mq.Send(args(2), mqtx) mqtx.Commit() Catch e As Exception mqtx.Abort() Console.WriteLine("Messaging failed: "+e.Message) End Try mq.Close() End Sub End Module 1.3 Creating Serviced Components with COM+: COM+ Component Services offered by Win2000 provides a simple mechanism to manage transactions within a COM component. A serviced component is a class authored in a CLS-compliant language that derives directly or indirectly from the System.EnterpriseServices.ServicedComponent class. Classes configured in this manner are hosted by a COM+ application and can use COM+ services. COM+ services, such as automatic transactions are configured declaratively. You apply service-related attributes at design time and create instances of classes that use services at run time. Some services can flow from one object to another. For example, an object configured to require a transaction can extend that transaction to a second object if the second object also supports or requires transactions.

Copyright 2001 K.M.Hussain <km@hussain.com>

- 117 -

Programming .NET With Visual Basic.NET


Given below is the source code for a COM+component (mysalescomp.vb) which relies on COM+ transaction processing (explanation follows the code). Imports System Imports System.Reflection Imports System.Data Imports System.Data.SqlClient Imports System.EnterpriseServices <assembly:ApplicationName("MySalesComponent")> <assembly:AssemblyKeyFile("my.snk")> Namespace My.SalesComponent <Transaction(TransactionOption.Required)> _ Public Class OrderManager Inherits ServicedComponent <AutoComplete> _ Public Function PlaceOrder(customer As String, product As Integer, _ quantity As Integer) As Integer Dim cn As New SqlConnection( _ "server=(local);database=sales;trusted_connection=yes") Dim cmd As new SqlCommand() cmd.Connection = cn cn.Open() Dim ordid As Integer = -1 Try cmd.CommandText = "update ord_ctl set ord_no = ord_no + 1" cmd.ExecuteNonQuery() cmd.CommandText = "select ord_no from ord_ctl" ordid = CInt(cmd.ExecuteScalar()) cmd.CommandText = "insert into ord values(@ord_no, _ @ord_date, @cust_id, @pno, @qty)" cmd.Parameters.Add("@ord_no", SqlDbType.Int).Value = ordid cmd.Parameters.Add("@ord_date",SqlDbType.DateTime).Value _ = DateTime.Now cmd.Parameters.Add("@cust_id", SqlDbType.Char,8).Value _ = customer cmd.Parameters.Add("@pno", SqlDbType.Int).Value = product cmd.Parameters.Add("@qty", SqlDbType.Int).Value = quantity cmd.ExecuteNonQuery() Finally cn.Close() End Try Return ordid End Function End Class
Copyright 2001 K.M.Hussain <km@hussain.com> - 118 -

Programming .NET With Visual Basic.NET


A COM+ application must be identified with a suitable name. The name is provided using assembly level ApplicationName attribute. The assembly that holds a COM+ application must have a strong name (generated using sn.exe utility). The key-file containing this strong name can be provided from inside of the code using the AssemblyKeyFile attribute or it can be passed during compilation using /keyf option of the assembly linker(al.exe). The component class must be derived from System.EnterpriseServices.ServicedComponent and must be marked with the configuration attributes. We have marked the CustomerManager class with Transaction attribute setting the option to Required which indicates that an object of this component runs in the context of an existing transaction, if no transaction exists, the object starts one. Just In Time Activation (JITA) is automatically enabled for any component which supports automatic transactions which means that the object of such a component remains non-active until a client invokes a method on the object. When the method call returns, COM+ deactivates the object (as such any data stored in its instance variables will be lost). Finally this class exposes CreateCustomer() method which is marked with AutoComplete attribute. This attribute indicates that when the method completes without any exception the transaction will be commited. In case of any exception the transaction will be aborted. Compile the above class: sn k my.snk vbc /r:system.dll,system.data.dll,system.enterpriseservices.dll /t:library mysalescomp.vs Given below is a client program(libcompclient.vb) which uses the above component. Imports My.SalesComponent Imports System Module SalesComponentClient Sub Main() Dim args As String() = Environment.GetCommandLineArgs() Dim cm As New CustomerManager() Dim custid As String = cm.CreateCustomer(args(1), args(2)) Console.WriteLine("Customer ID: {0}",custid) End Sub End Module Compile the program: vbc /r:mysalescomp.dll,system.enterpriseservices.dll libcompclient.vb When this program is executed for the first time, CLR will automatically register the MySalesComponent. Registration occurs only once for a particular version of an assembly.(To execute this example and the subsequent ones you must first create and populate the sales database in SQL Server or MSDE running on localhost as mentioned in Chapter 9)
Copyright 2001 K.M.Hussain <km@hussain.com> - 119 -

Programming .NET With Visual Basic.NET


Given below is an ASP.NET page (complustest.aspx) which uses MySalesComponent application to place an order. It starts a transaction by setting Transaction attribute of Page to Required. It invokes OrderManager.PlaceOrder to place an order from the informations submitted through the web-forrm. Next it sends a message to MessageQueue ordq. Then it updates the stock table. If everything goes well ContextUtil.SetComplete() is invoked which commits the transaction (the database is permanently updated and message is sent to the queue). In case of an exception ContextUtil.SetAbort() is invoked which aborts the transaction (the database is not updated and no message is sent to the queue). <%@ Page Transaction="Required"%> <%@ Assembly Name="System.Messaging, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" %> <%@ Import Namespace="My.SalesComponent"%> <%@ Import Namespace="System.Data"%> <%@ Import Namespace="System.Data.SqlClient"%> <%@ Import Namespace="System.Messaging"%> <%@ Import Namespace="System.EnterpriseServices"%> <html> <title>COM+ Test</title> <head> <script language="VB" runat="server"> Private Sub Submit_Click(sender As Object, ev As EventArgs) Dim custid As String = CustTxt.Text Dim pno As Integer = CInt(ProdTxt.Text) Dim qty As Integer = Cint(QtyTxt.Text) Dim om As New OrderManager() Dim mq As New MessageQueue(".\private$\ordq") Try Dim ordid As Integer = om.PlaceOrder(custid, pno, qty) mq.Send(String.Format( _ "Customer {0} ordered {1} pieces of {2}", custid, pno, qty), _ MessageQueueTransactionType.Automatic) send message using the current transaction Dim cn As New SqlConnection( _ "server=(local);database=sales;trusted_connection=yes") Dim sql As String = String.Format( _ "update stock set qty=qty-{0} where pno={1}", quantity, product) Dim cmd As New SqlCommand(sql, cn) cn.Open() Try cmd.ExecuteNonQuery() Finally cn.Close() End Try
Copyright 2001 K.M.Hussain <km@hussain.com> - 120 -

Programming .NET With Visual Basic.NET


ContextUtil.SetComplete() ResultLbl.Text = String.Format("Order No {0}", ordid) Catch e As Exception ContextUtil.SetAbort() ResultLbl.Text = e.ToString() End Try mq.Close() End Sub </script> </head> <body> <center> <h1>Place Order</h1> <form runat="server"> <table border="1"><tr><td> <table border="0" bgcolor="#d4d0c8"> <tr> <td><b> Cust Id:<b></td> <td><asp:TextBox id="CustTxt" runat="server" /></td> </tr> <tr> <td><b> Prod No:<b></td> <td><asp:TextBox id="ProdTxt" runat="server" /></td> </tr> <tr> <td><b> Quantity:<b></td> <td><asp:TextBox id="QtyTxt" runat="server" /></td> </tr> <tr align="center"><td colspan="2"> <asp:Button Text="Order" runat="server" OnClick="Submit_Click" runat="server"/> </tr> </table> </td></tr></table> <p><b><asp:Label id="ResultLbl" runat="server" /></b></p> </form> </center> </body> </html> To execute this page you must copy mysalescomp.dll the bin directory of the application.

Copyright 2001 K.M.Hussain <km@hussain.com>

- 121 -

Вам также может понравиться