Sei sulla pagina 1di 123

Chapter 1: Introduction to the .NET Framework.................................................................2 Chapter 2: C# Language Fundamentals...............................................................................5 Chapter 3: Object Oriented Programming in C#...............................................................13 Chapter 4: Reflection.........................................................................................................

32 Chapter 5: Multithreading..................................................................................................38 Chapter 6: Streams.............................................................................................................46 Chapter 7: Sockets.............................................................................................................57 Chapter 8: Distributed Programming with Remoting........................................................64 Chapter 9: Database Programming with ADO.NET..........................................................73 Chapter 10: GUI Programming with Windows Forms......................................................86 Chapter 11: Web Programming with ASP.NET................................................................99 Chapter 12: Interop and Enterprise Services...................................................................110

Chapter 1: Introduction to the .NET Framework


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 runtime's 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 cross-language 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 runtime's 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.

Currently Microsoft provides compilers for C# (Pronounced as C Sharp), VisualBasic 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 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 base 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. 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 The code below is hello program in JScript (jshello.js) import System; Console.WriteLine(Hello World.NET); Compile jshello.js using command jsc /EXE 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 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 C#(bizcalc.cs) for calculating the price of a depreciating asset at an end of a given period: namespace BizCalc{ public class Asset{ public double OriginalCost; public float AnnualDepreciationRate; public double GetPriceAfter(int years){ double price = OriginalCost; for(int n = 1; n <= years; n++) price = price * (1 - AnnualDepreciationRate/100); return price; } } } Compile asset.cs to create a dll using command: csc /t:library bizcalc.cs A VB program below (assettest.vb) uses the above component: Imports BizCalc Imports System Module AssetTest Sub Main() Dim ast As Asset = new Asset() ast.OriginalCost = 10000 ast.AnnualDepreciationRate = 9 Console.WriteLine("Price of asset worth 10000 after 5 years: {0}", _ ast.GetPriceAfter(5)) End Sub End Module Compile the above program using: vbc /r:bizcalc.dll assettest.vb

Chapter 2: C# Language Fundamentals


Basically a C# program consists of a class with a static member method (class function) called Main. When C# compiler (csc.exe) compiles a C# program it marks the Main method as the entrypoint in the generated IL code. This Main method may accept an array of string as its argument (though this is optional). This array will always contain the command line arguments passed to the program by its user. The program below (hello1.cs) says hello to the first name on its command line. using System; class Hello1{ public static void Main(string[] args){ Console.WriteLine(Hello{0}, args[0]); Console.WriteLine(Goodbye.); } } After compiling hello1.cs execute it as : hello1 John The screen will Display Hello John Goodbye. The statement Console.WriteLine(Hello {0}, args[0]) prints a string on the console with {0} replaced by the second argument ie args[0] of WriteLine. Since args[0] 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[0] 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.cs) demonstrates how to. using System; class Hello2{

public static void Main(string[] args){ try{ Console.WriteLine(Hello{0}, args[0]); }catch(Exception e){ Console.WriteLine(e); } Console.WriteLine(Goodbye.); } } Compile and execute hello2.cs. 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.cs) 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. using System; class Hello3{ public static void Main(string[] args){ if(args.Length > 0){ Console.WriteLine(Hello{0}, args[0]); }else{ Console.Write(Enter your name: ); string name = Console.ReadLine(); Console.WriteLine(Hello{0}, name); } Console.WriteLine(Goodbye.); } } 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.cs) uses a for loop and displays hello to each name on the command line. using System; class Hello4{ public static void Main(string[] args){

for(int n = 0; n < args.Length ; n++){ Console.WriteLine(Hello{0}, args[n]); } Console.WriteLine(Goodbye.); } } The for statement consists of three statements int n = 0; initializes an integer variable n used for iteration. n < args.Length ; is a boolean expression representing the condition for the loop to continue. Finally ; n++ increments the value of n after every iteration. The above behaviour can also be obtained in a more readable fashion by using foreach statement as shown in program (hello5.cs) below: using System; class Hello5{ public static void Main(string[] args){ foreach(string name in args){ Console.WriteLine(Hello{0}, name); } Console.WriteLine(Goodbye.); } } Lastly hello6.cs uses while loop to say hello to each name on command line but does it in reverse order. using System; class Hello6{ public static void Main(string[] args){ int n = args.Length 1; while(n >= 0){ Console.WriteLine(Hello{0}, args[n--]); //decrement after use } Console.WriteLine(Goodbye.); } } C# is a strongly typed language thus C# 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 C# C# Type .NET Type Description bool System.Boolean true / false

byte sbyte char short ushort int uint long ulong float double decimal string object

System.Byte System.SByte System.Char System.Int16 System.UInt16 System.Int32 System.UInt32 System.Int64 System.UInt64 System.Single System.Double System.Decimal System.String System.Object

unsigned byte value signed byte value a single character 16 bit signed integer 16 bit unsigned integer 32 bit signed integer 32 bit unsigned integer 64 bit signed integer 64 bit unsigned integer 32 bit floating-point 64 bit floating-point a high precision double string of characters a generic type

At compile-time the C# compiler converts the C# types into their corresponding .NET types mentioned in the above table. Apart from above basic types user may define his own types using enum, struct and class. The program below (club1.cs) shows how and to create user-defined type using System; enum MemberType {Lions, Pythons, Jackals, Eagles}; struct ClubMember{ public string Name; public int Age; public MemberType Group; } class Test{ public static void Main(){ ClubMember a = new ClubMember(); a.Name = "John"; a.Age = 13; a.Group = MemberType.Eagles; ClubMember b = 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); // above line } } 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 int. 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}; 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}; Now Lions is 1, Pythons is 2, Jackals is 3 and Eagles is 4. The struct 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 struct block. To use a structure you must first instantiate it i.e allocate necessary memory for its fields. This is done by using new keyword as done in the Main method of class Test: ClubMember a = new ClubMember(); In this statement a variable a is declared to be of type ClubMember and a newly created instance is assigned to it. Next the public fields of ClubMember is set for this instance. Note how Group field of a is set: a.Group = MemberType.Eagles; A struct is a value type i.e when a variable holding an instance of a struct is assigned to another variable of same type, a new copy of the instance is created and assigned. Consider statement ClubMember b = 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.cs is similar to club1.cs except now ClubMember is a class instead of struct. using System; enum MemberType {Lions, Pythons, Jackals, Eagles}; class ClubMember{ public string Name; public int Age; public MemberType Group; } class Test{ public static void Main(){ ClubMember a = new ClubMember(); a.Name = "John"; a.Age = 13; a.Group = MemberType.Eagles; ClubMember b = a; // b and a refer to same instance b.Age = 17; // a.Age also becomes 17 Console.WriteLine("Member {0} is {1} years old and belongs to the group of {2}", a.Name, a.Age, a.Group);//above line } }

Here since ClubMember is a class the assignment ClubMember b = 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 C# using one of the following syntaxes; type[] arr = {val0, val1, val2, val3, }; type[] arr = new arr[size_of_array]; For example: int[] k = {12, 15, 13, 19}; double[] p = new double[3]; p[0] = 9.3; p[1] = 12.8; p[2] = 7.7; In modular programming it is desirable to divide a program into multiple procedures (methods in C#). The program below (maximizer.cs) implements user-defined methods to obtain maximum of double values. These methods are then called from Main. using System; public class Maximizer{ public static double Max(double p, double q){ if(p > q) return p; return q; } public static double Max(double p, double q, double r){ return Max(Max(p,q),r); } public static void Main(){ Console.WriteLine(Maximum of 12.8 and 19.2 is {0}, Max(12.8,19.2)); Console.WriteLine(Maximum of 21.7, 12.8 ,19.2 is {0} ,Max(21.7,12.8,19.2)); } } 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 static and as such these methods can be invoked from any class using fully qualified name Maximizer.Max. (A static 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. C# solves this problem using params keyword. Given below is a version of Max (you can incooperate this in maximizer.cs) which takes any number of double values as its argument

public static double Max(params double[] list){ if(list.Length == 0) return 0; double max = list[0]; foreach(double val in list){ if(val > max) max = val; } return max; } 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) In C# 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. In C a function may accept a pointer to a variable and the make a permanent change to its value. C# allows the use of pointer but only in an unsafe method (the code in this method is not managed by CLR). The listing below (swapper1.cs) implements an unsafe method which receives pointers to variables and swaps their values. using System; public class Swapper1{ // Swap is an unsafe method which accepts pointers to two integers (int*) public static unsafe void Swap(int* p, int* q){ int t = *p; // store the value referenced by p in t *p = *q; *q = t; } public static unsafe void Main(){ int m = 109, n = 73; Console.WriteLine(m = {0} and n = {1},m,n); Console.WriteLine(Invoking Swap); // address-of operator (&) can only be used in an unsafe method Swap(&m, &n); Console.WriteLine(m = {0} and n = {1},m,n); } } Compile the above source as: csc /unsafe swapper1.cs If you execute the program you will notice the variables are swapped successfully. It is not necessary to rely on unsafe code just for sake of passing a variable to a method by

reference. C# provides ref keyword for passing and receiving arguments by reference. swapper2.cs uses this approach to swap values of variables. using System; public class Swapper2{ // Swap receives two integers by reference (ref int) public static void Swap(ref int p, ref int q){ int t = p; p = q; q = t; } public static void Main(){ int m = 109, n = 73; Console.WriteLine(m = {0} and n = {1},m,n); Console.WriteLine(Invoking Swap); Swap(ref m, ref n); // passing m and n by reference to Swap Console.WriteLine(m = {0} and n = {1},m,n); } } A method can return only one value, what if more than one value ie expected from a method. For this purpose C# uses out keyword. An uninitialized out argument can be passed to a method and that method can set the value of this argument. The program below(divider.cs) defines a method called Divide which returns quotient and remainder in division of one integer by another. The quotient is returned in a normal way (using return keyword) and remainder is returned through an out argument. using System; class Divider{ public static int Divide(int dividend, int divisor, out int remainder){ int quotient = dividend/divisor; remainder = dividend - quotient * divisor return quotient; } public static void Main(string[] args){ int r; int q = Divide(157, 23, out r); Console.WriteLine("Quotient = {0} and Remainder = {1}, q,r); } } Qualifier <none> ref out read yes yes no write no yes yes

Chapter 3: Object Oriented Programming in C#


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.cs) 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 double _cost; private float _profit; private static int _count; public Item(){ _count++; } public Item(double cost, float profit){ if(cost > 0) _cost = cost; _profit = profit; _count++; } public double Cost{ get { return _cost; } set{ if(value > 0) _cost = value; } } public float Profit{ get { return _profit; } set{ _profit = value; }

} public static int Count{ get{ return _count; } } // use of virtual modifier will be explained later public virtual double SellingPrice(){ return _cost * (1 + _profit / 100); } public double EMI(){ double amount = 1.12 * SellingPrice(); // 12% interest return amount / 12; } } 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. Item p = 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 C# compiler to a pair of methods. For example the Cost property in Item class is translated by the compiler to: public double get_Cost (){ return cost; } public void set_Cost (double value){ if (value > 0) cost = value; } Which also explains the use of undeclared variable value in the set part of a property. A non-static (instance) variable is stored within object of the class, thus each object has its own copy of a non-static variable. A static (class) variables on the other hand is stored within the class and is shared by all of its objects. In Item class _count is a static 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 property is marked static which means a user may access it as Item.Count. As non-static member variables are not stored in a class, a static method cannot directly reference a non-static member of the class.

A user instantiates a class by applying the new operator to the class constructor (a method whose name matches with the class name and does not define any return type). 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. Item class defines two constructors. A user may instantiate and initialize Item as follows: Item p = new Item(); // using default constructor p.Cost = 1200; p.Profit = 9; Item q = new Item(1500,8); // using second constructor Item class also provides two utility methods for calculating selling price and the equal monthly installment (1 year scheme) for the item. The program below(itemtest1.cs) uses Item class using System; class ItemTest1{ public static void Main(){ Item pc = new Item(32000, 12); Item ac = 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); } } To compile itemtest1.cs either first compile item.cs to create a dll and then compile itemtest1.cs referencing item.dll (Note: only public classes from one assembly can be accessed from other assembly, hence class Item is declared public in item.cs) csc /t:library item.cs creates item.dll csc /r:item.dll itemtest1.cs creates itemtest1.exe You can also compile itemtest.cs by using command csc itemtest1.cs item.cs 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 the base 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 C#, DClass : BClass), the above rule makes it possible to write: BClass obj =

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: BClass obj = 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 override modifier while redeclaring this method and this is only allowed for those methods which have been declared with virtual modifier in the base class. To hide a method of base class the derived class must use new modifier while redeclaring this method, the same method in base class may or may not be declared virtual. Given below is an implementation of a subclass of Item called SpecialItem (specialitem.cs). This class adds a Discount property to Item and overrides its SellingPrice (virtual) method . using System; public class SpecialItem : Item{ private float _discount; public SpecialItem(double c, float p, float d) : base(c,p){ if(d > 0) _discount = d; } public float Discount{ get { return _discount; } set{ if(value > 0) _discount = value; } }

public override double SellingPrice(){ double price = base.SellingPrice(); return price * (1 - _discount/100); } } Note a subclass constructor must first invoke the base class constructor, this is done by attaching :base() to 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 base 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 override modifier by new modifier) then EMI method would call the SellingPrice of Item which would not take in account the Discount. The program below (itemtest2.cs) uses both Item and SpecialItem classes: using System; class ItemTest2{ public static void Main(){ Item pc = new Item(25000, 12); SpecialItem ac = new SpecialItem(25000, 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); } } Compile this program as: csc itemtest2.cs item.cs specialitem.cs 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: Item[] store = {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. double total = 0; foreach(Item itm in store){ total += itm.SellingPrice(); } Since SellingPrice() is virtual 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 in foreach). Also statement foreach(SpecialItem itm in store) will not compile because store is declared to be an array of 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: double total = 0; foreach(Item itm in store){ if(itm is SpecialItem){ SpecialItem sitm = itm as SpecialItem; total += sitm.SellingPrice(); } } The is operator is used to check whether a given object is an instance of a given class and the as operator is used to narrow object of base class to an object of its derived class. The conversion SpecialItem sitm = itm as SpecialItem; can also be achieved by means of casting SpecialItem sitm = (SpecialItem) itm; the difference between these two methods of conversion is that the first approach (using as operator) will return null if the conversion fails while the second approach (using the cast operator) will throw System.InvalidCastException if the conversion fails. 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. Account[] bank = {new SavingsAccount(), new CurrentAccount(), }; 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. foreach(Account acc in bank){

acc.Withdraw(500); } 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 a virtual 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. C# uses abstract keyword to declare abstract methods and abstract classes. Note an abstract method is automatically virtual and must be defined in the derived class with override modifier (new modifer will cause a compile time error since inherited abstract methods cannot be hidden) Code below(bank.cs) contains an abstract Account class and its subclasses among other classes (explanation follows the code) namespace Project.Bank{ public class InsufficientFundsException : System.Exception{ } public class IllegalTransferException : System.Exception{ } public abstract class Account{ public abstract void Deposit(double amount); public abstract void Withdraw(double amount); public abstract double Balance{ get; } public void Transfer(double amount, Account other){ if(other == this) throw new IllegalTransferException(); this.Withdraw(amount); // same as Withdraw(amount); other.Deposit(amount); } } public abstract class BankAccount : Account{ protected double _balance; // also accessible to any subclass

internal

string _id; // also accessible to any other class in this dll

public string ID{ get{ return _id; } } public override double Balance{ get{ return _balance; } } public override void Deposit(double amount){ _balance += amount; } } public sealed class SavingsAccount : BankAccount{ public const double MIN_BALANCE = 500.0; public SavingsAccount(){ _balance = MIN_BALANCE; _id = S/A; } public override void Withdraw(double amount){ if(_balance amount < MIN_BALANCE){ throw new InsufficientFundsException(); } _balance -= amount; } } public sealed class CurrentAccount : BankAccount{ public const double MAX_CREDIT = 50000.0; public CurrentAccount(){ _id = C/A; } public override void Withdraw(double amount){ if(_balance amount < - MAX_CREDIT){ throw new InsufficientFundsException(); } _balance -= amount; } } public sealed class Banker{ private static Banker self; private int nextid;

private Banker(){ nextid = 1001; } public static Banker GetBanker(){ if(self == null) self = new Banker(); return self; } public BankAccount OpenAccount(string type){ BankAccount acc; if(type == SavingsAccount) acc = new SavingsAccount(); else if(type == CurrentAccount) acc = new CurrentAccount(); else return null; acc._id += nextid++; return acc; } } } Here are important points to note about bank.cs 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 using Project.Bank and then refer to Account class as Account. A user can also alias the namespace as using pb = Project.Bank and then refer to Account class as pb.Account. 2. 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. 3. 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 this 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. 4. 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 internal which means apart from the current class this member is accessible to any other class in the current assembly (protected internal is also allowed and it means apart from the current class the member will be accessible only to the subclasses or any other class in the current assembly) 5. SavingsAccount and CurrentAccount are two concrete implementations of Account. Both of these classes are derived from BankAccount and are declared sealed which

6.

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 C# are static) 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 static 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 null.

The program below (banktest1.cs) uses the above classes. using Project.Bank; using System; class BankTest1{ public static void Main(){ Banker banker = Banker.GetBanker(); BankAccount cust = banker.OpenAccount(SavingsAccount); BankAccount vend = banker.OpenAccount(CurrentAccount); cust.Deposit(12000); Console.Write("Enter amount to transfer: "); //read a string from the keyboard and convert it into a double double amt = Double.Parse(Console.ReadLine()); try{ cust.Transfer(amt,vend); }catch(InsufficientFundsException e){ //a compile time warning my appear as we are not using variable e Console.WriteLine("Transfer Failed"); } 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); } } Under C# 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 abstract class Profitable{ public abstract void AddInterest(float rate, int period); } 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, C# 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.cs). public interface IProfitable{ void AddInterest(float rate, int period); } Methods declared in an interface are public and abstract 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.cs) can inherit from both BankAccount and IProfitable as follows: public sealed class SavingsAccount : BankAccount, IProfitable{ public const double MIN_BALANCE = 500.0; public SavingsAccount(){ _balance = MIN_BALANCE; _id = S/A; } public override void Withdraw(double amount){ if(_balance amount < MIN_BALANCE){ throw new InsufficientFundsException(); } _balance -= amount; } //override is understood public void AddInterest(float rate, int period){ _balance = _balance * (1 + rate * period /100); } }

The statement IProfitable acc = new SavingsAccount(); compiles since SavingsAccount implements IProfitable however IProfitable acc = new CurrentAccount(); will cause a compile-time error. Next consider following two interfaces public interface ITaxable{ void Deduct(); } public interface IFinable{ void Deduct(); } Now suppose we would like our CurrentAccount class to implement both of these interfaces. Clearly this is not possible as both of these interfaces contain members with same signature. This problem is solved in C# by using explicit interface member implementations. The code below demonstrates the technique. public sealed class CurrentAccount : BankAccount, ITaxable, IFinable{ public const double MAX_CREDIT = 50000.0; public CurrentAccount(){ _id = C/A; } public override void Withdraw(double amount){ if(_balance amount < - MAX_CREDIT){ throw new InsufficientFundsException(); } _balance -= amount; } // implementation of Deduct() inherited from ITaxable void ITaxable.Deduct(){ if(_balance > 50000) _balance *= 0.95; } // implementation of Deduct() inherited from IFinable void IFinable.Deduct(){ if(_balance < -0.5*MAX_CREDIT) _balance *= 0.98; } } The member methods are implemented with their fully qualified names (InterfaceName.MemberName). The methods implemented in this fashion must be declared without any access modifier (private, protected, public) and as such are only accessible through an interface instance. For example the right way to invoke ITaxable.Deduct() method of CurrentAccount is

ITaxable acc = new CurrentAccount(); // declare acc to be an instance of ITaxable acc.Deduct(); // this will invoke ITaxable.Deduct() implemented in CurrentAccount C# 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.cs) FixedDeposit uses a delegate to invoke a method passed by its user for determining the rate of interest. using System; namespace Project.Bank{ // Scheme represents a method which takes a double and an int as its arguments and // returns a float; public delegate float Scheme(double principal, int period); public class FixedDeposit{ private double deposit; private int duration; private Scheme InterestRate; // InterestRate is a method of type Scheme public FixedDeposit(double p, int n, Scheme s){ deposit = p; duration = n; InterestRate = s; } public double Deposit{ get{ return deposit; } } public int Duration{ get{ return duration; } } public double Amount{ get{ float rate = InterestRate(deposit,duration); //callback users method return deposit * Math.Pow(1 + rate/100, duration); } } } }

The program below (fdtest.cs) makes use of above class: using Project.Bank; using System; class FDTest{ public static float MyRate(double p, int n){ return (n < 5) ? 9 : 11; } public float HisRate(double p, int n){ return (p < 20000) ? 8 : 10; } public static void Main(){ FixedDeposit myfd = new FixedDeposit(25000,7,new Scheme(MyRate)); FixedDeposit hisfd = new FixedDeposit(25000,7, new Scheme(new FDTest().HisRate)); Console.WriteLine("I get {0:#.00}",myfd.Amount); // formatted output Console.WriteLine("He gets {0:#.00}",hisfd.Amount); } } In the Main method an instance of Scheme delegate is created using method MyRate and is passed to FixedDeposit constructor 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-static method HisRate. Delegates are mostly used to implement 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.cs 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.cs by following code: public delegate void TransferHandler(Account source, double amount); public abstract class Account{ public event TransferHandler FundsTransferred; public abstract void Deposit(double amount); public abstract void Withdraw(double amount); public abstract double Balance{ get; }

private void OnFundsTransferred(Account source, double amount){ if(FundsTransferred != null) FundsTransferred(source, amount); } public void Transfer(double amount, Account other){ if(other == this) throw new IllegalTransferException(); this.Withdraw(amount); // same as Withdraw(amount); other.Deposit(amount); // raise FundsTransferred event on other other.OnFundsTransferred(this, amount); } } First a delegate called TransferHandler is declared, next in Account class we declare an event called FundsTransferred whose handler is of type TransferHandler. In the Transfer method the event is fired on the other account. The program below (banktest2.cs) shows how to register an event handler and handle the event. using Project.Bank; using System; class BankTest2{ // The event handler method public static void vend_FundsTransferred(Account source, double amount){ Console.WriteLine(Vendor received amount {0}, amount); } public static void Main(){ Banker banker = Banker.GetBanker(); BankAccount cust = banker.OpenAccount(SavingsAccount); BankAccount vend = banker.OpenAccount(CurrentAccount); cust.Deposit(12000); //register the event handler method with vend vend.FundsTransferred += new TransferHandler(vend_FundsTransferred); Console.Write("Enter amount to transfer: "); double amt = Double.Parse(Console.ReadLine()); try{ cust.Transfer(amt,vend); }catch(InsufficientFundsException e){ Console.WriteLine("Transfer Failed"); } 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); } }

The event handler is registered by adding (+=) the delegate of event handler to the FundsTransferred event of the Account. This allows registeration of multiple event handler methods. When event occurs all the handlers will be invoked. Operator Overloading is yet another feature introduced in C#. It allows a class to redefine standard operators for its instances. Not all operators can be overloaded nor new operators can be created. The class below (money.cs) shows how to overload some of the operators in C# using System; public class Money{ private int dollars; private int cents; public Money(int d, int c){ dollars = d; cents = c; } public static Money operator+(Money lhs, Money rhs){ int d = lhs.dollars+rhs.dollars; int c = lhs.cents+rhs.cents; if(c > 100){ d += 1; c -= 100; } return new Money(d,c); } public static explicit operator Money(double rhs){ int d = (int) rhs; int c = (int)(100*rhs-100*d); return new Money(d,c); } public static implicit operator double(Money rhs){ return rhs.dollars + rhs.cents/100.0; } public static bool operator<(Money lhs, Money rhs){ return (lhs.dollars + lhs.cents/100.0) < (rhs.dollars + rhs.cents/100.0); } public static bool operator>(Money lhs, Money rhs){

return (lhs.dollars + lhs.cents/100.0) > (rhs.dollars + rhs.cents/100.0); } } The above class overloads +, < and > operators. The methods used for operator overloading must be static and must be named as operator op (where op is the operator to be overloaded) C# compiler will translate these method names appropriately before compiling (for example method name operator+ is transalated to op_Addition) Some operators must be overloaded in pairs for example if you overload operator < you must also overload operator >. C# does not allow overloading of cast () operator but allows you to define explicit and implicit operators. If conversion is defined through explicit operator then an appropriate cast will be needed during conversion. In above class conversion of Money instance to double is defined using explicit operator thus statement Money m = 123.45; will cause a compile-time error. The correct approach is Money m = (Money)123.45; On the other hand conversion of a Money instance to a double is defined using implicit operator and this conversion will not require a cast i.e statement double m = new Money(123, 45); will compile. The program below (moneytest.cs) shows how to use Money class. using System; class MoneyTest{ public static void Main(){ Money k = new Money(123, 45); // instantiating with constructor Money l = (Money) 13.75; // explicit conversion Money m = k + l; // using + operator double n = m; // implicit conversion; Console.WriteLine(n); // must display 137.18 } } One last important feature of C# 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.cs) 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 foreach statement. using System; public class RandomList{ private int[] list; private int current; public RandomList(int size){ list = new int[size];

for(int i=0;i<size;i++) list[i] = i+1; } public void Shuffle(){ Random gen = new Random(); for(int i=list.Length; i>1;i--){ int j = gen.Next(0,i); // random integer between 0 and i int t = list[i-1]; list[i-1]=list[j]; list[j] = t; } } public int Count{ get{ return list.Length; } } //support for indexer public int this[int index]{ get{ return list[index]; } } //support for foreach statement public bool MoveNext(){ return (++current < list.Length); } public int Current{ get{ return list[current]; } } public RandomList GetEnumerator(){ current = -1; return this; } public static void Maim(){ RandomList list = new RandomList(10); // using the indexer for(int k = 0; k < list.Count;k++){

Console.WriteLine(list[k]); } list.Shuffle(); // using the iterator foreach(int k in list){ Console.WriteLine(k); } } } 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 bool value. Since RandomList itself provides Current property and MoveNext() method its GetEnumerator returns its own reference (this). The foreach 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 method show two different method to iterate RandomList. The first method uses indexer and second uses iterator.

Chapter 4: Reflection
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 AType var is legal then System.Type representing AType can be obtained using typeof operator as: Type t = typeof(AType); 2. From an instance: Type of an instance obj can be obtained using GetType method defined in System.Object as: Type t = obj.GetType(); 3. From the type name within current/referenced assembly: System.Type offers a static method called GetType to obtain a Type from a fully qualified name of the type. The name will be searched in the current and all the referenced assemblies. Type t = 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: using System.Reflection; Assembly asm = Assembly.LoadFrom(AssemblyName); Type t = asm.GetType(FullyQualifiedTypeName); Alternatively one could also use: Type t = asm.GetType(FullyQualifiedTypeName,AssemblyName); The program below(showtypes.cs) displays all the Types defined in an assembly whose name is passed in first command line argument: using System; using System.Reflection; class ShowTypes{ public static void Main(string[] args){ Assembly asm = Assembly.LoadFrom(args[0]); Type[] types = asm.GetTypes(); foreach(Type t in types) Console.WriteLine(t); } } 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.cs) takes assembly name and type name as its command line arguments and displays all the members defined in that type of that assembly. using System; using System.Reflection; class ShowMembers{ public static void Main(string[] args){ Assembly asm = Assembly.LoadFrom(args[0]); Type t = asm.GetType(args[1]); MemberInfo[] members = t.GetMembers(); foreach(MemberInfo m in members) Console.WriteLine(m); } } 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.cs) provide implementations for various policies: public interface Policy{ float Rate(double principal, int period); } public class BronzePolicy : Policy{ public float Rate(double p, int n){ return 7; } } public class SilverPolicy : Policy{ public float Rate(double p, int n){ return (p < 25000) ? 8 : 10; } } public class GoldPolicy { public float Rate(double p, int n){ return (n < 5) ? 9 : 11; } }

public class PlatinumPolicy : Policy{ public float Rate(double p, int n){ float r = (p < 50000) ? 10 : 12; if(n >= 3) r += 1; return r; } } 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.cs to create invpol.dll: csc /t:library invpol.cs Here is our investment program (investment.cs) which accepts principal, period and policy name as its arguments and displays the amount the investor will receive: using System; using System.Reflection; class Investment{ public static void Main(string[] args){ double p = Double.Parse(args[0]); // string to double int n = Int32.Parse(args[1]); // string to int Type t = Type.GetType(args[2]); Policy pol = (Policy)Activator.CreateInstance(t); float r = pol.Rate(p,n); double amount = p*Math.Pow(1+r/100,n); Console.WriteLine("You will get {0:#.00}",amount); } } 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.cs: csc /r:invpol.dll investment.cs Execute: investment 65000 6 SilverPolicy,invpol 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.cs) calls the Rate method using the so called Dynamic Method Invocation. As this approach calls the method by its name the casting is not required, this version of investment program will work with all the policy classes in invpol.dll.

using System; using System.Reflection; class InvestmentDMI{ public static void Main(string[] args){ double p = Double.Parse(args[0]); int n = Int32.Parse(args[1]); Type t = Type.GetType(args[2]); object pol = Activator.CreateInstance(t); // no casting Type[] ptypes = {typeof(double), typeof(int)}; //look for a method called Rate (double, int) MethodInfo m = t.GetMethod("Rate",ptypes); // store the values of the argument in an array of object // which can hold values of any type (boxing) object[] pvalues = {p,n}; // Invoke the Rate method on pol instance with arguments // stored in pvalues. //Cast the return value of type object to float (unboxing) float r = (float)m.Invoke(pol,pvalues); double amount = p*Math.Pow(1+r/100,n); Console.WriteLine("You will get {0:#.00}",amount); } } Compile investmentdmi.cs using command csc investmentdmi.cs It will work with all four policies in invpol.dll C# 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 square brackets, in front of the declaration of the entity (class, member method etc) to which it applies, For example: [Marker(arglist)] entity declaration Where Marker is an attribute 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.cs) defines two attributes Executable and Authorize. [AttributeUsage(AttributeTargets.Class)] // applies only to a class public class ExecutableAttribute : Attribute{ } [AttributeUsage(AttributeTargets.Method)] // applies to a method of a class public class AuthorizeAttribute : Attribute{ private string _pwd; public AuthorizeAttribute(string pwd){ _pwd = pwd; } public string Password{ get{ return _pwd; } } } Next sample (executer.cs) 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. class Executer{ public static void Main(string[] args){ Type t = Type.GetType(args[0]); //args[0] = ClassName,AssemblyName // check if the class supports Executable attribute if(!t.IsDefined(typeof(ExecutableAttribute))){ Console.WriteLine("{0} is not executable", args[0]); return; } // lookup for a Execute method which takes no arguments MethodInfo m = t.GetMethod("Execute"); // obtain AuthorizeAttribute if supported by this method AuthorizeAttribute auth = (AuthorizeAttribute)Attribute.GetCustomAttribute(m, typeof(AuthorizeAttribute)); if(auth != null){ Console.Write(Enter Password: ); if(Console.ReadLine() != auth.Password ){ Console.WriteLine("Password is incorrect"); return; } } m.Invoke(null, null); //Execute is a static method and takes no arg } }

Compile above program using command: csc executer.cs, execattr.cs Apps.cs provides few classes for testing executer program. using System; [Executable] public class AClock{ public static void Execute(){ Console.WriteLine("Its {0}", DateTime.Now); } } [Executable] public class AGame{ [Authorize("tiger")] public static void Execute(){ Random r = new Random(); int p = r.Next(1,10); Console.Write("Guess a number between 1 and 10: "); int q = Int32.Parse(Console.ReadLine()); if(p == q) Console.WriteLine("You won!!!"); else Console.WriteLine("You lost, correct number was {0}",p); } } public class AMessage{ public static void Execute(){ Console.WriteLine("Hello World!"); } } 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.cs to create a dll using command csc /t:library apps.cs execattr.cs (executer.exe must be referenced because apps.cs uses attributes defined in that assembly). Test executer: executer AClock,apps executer AGame,apps executer AMessage,apps will show the current time will prompt for password will not execute

Chapter 5: Multithreading
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.cs) launches notepad.exe and asynchronously counts up from 1 to 10000 and then destroys the notepad process. using System; using System.Diagnostics; // for Process class class ProcessTest{ public static void Main(){ Process p = Process.Start("notepad.exe"); for(int i = 1;i < 10001;i++) Console.WriteLine(i); p.Kill(); } } Compile: csc processtest.cs 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.cs) executes two loops simultaneously using threads. using System; using System.Threading; // for Thread class class ThreadTest1{ public static void SayHello(){ for(int i = 1;; i++){ Console.WriteLine(Hello {0},i); } } public static void Main(){ Thread t = new Thread(new ThreadStart(SayHello)); t.Start(); for(int i = 1;i < 10001; i++){ Console.WriteLine(Bye {0},i); } t.Abort(); } }

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.cs) shows how: using System; using System.Threading; class ThreadTest2{ public static void SayHello(){ for(int i = 1;; i++){ Console.WriteLine("Hello {0}",i); } } public static void Main(){ Thread t = new Thread(new ThreadStart(SayHello)); t.IsBackground = true; t.Start(); for(int i = 1;i < 10001; i++){ Console.WriteLine("Bye {0}",i); } } } Just running threads simultaneously is not the whole thing, we sometimes need to control them. In program below (sleepingthread.cs) 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 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. using System; using System.Threading;

class SleepingThread{ public static void Run(){ for(int i=1;i<6;i++) Console.WriteLine("Welcome {0}",i); try{ Thread.Sleep(5000); }catch(ThreadInterruptedException e){ Console.WriteLine("Sleep Interrupted"); } Console.WriteLine("Goodbye"); } public static void Main(){ Thread t = new Thread(new ThreadStart(Run)); t.Start(); for(int i=1;i<16;i++) Console.WriteLine("Hello {0}",i); t.Interrupt(); } } Our next program (joiningthread.cs) 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) . using System; using System.Threading; class JoiningThread{ public static void Run(){ for(int i=1;i<16;i++) Console.WriteLine("{Hello {0}",i); } public static void Main(){ Thread t = new Thread(new ThreadStart(Run)); t.Start(); for(int i=1;i<6;i++) Console.WriteLine("Welcome {0}",i); t.Join(); //blocks the current thread until t terminates Console.WriteLine("Goodbye"); } } In program below (syncthreads.cs) 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 using System; using System.Threading; class Account { private double balance=5000; public void Withdraw(double amount) { Console.WriteLine("WITHDRAWING {0}",amount); if(amount>balance) throw new Exception("INSUFFICIENT FUNDS"); Thread.Sleep(10); //do other stuff balance-=amount; Console.WriteLine("BALANCE {0}",balance); } } class SyncThread { static Account acc = new Account(); public static void Run() { acc.Withdraw(3000); } public static void Main() { new Thread(new ThreadStart(Run)).Start(); acc.Withdraw(3000); } } 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. C# offers lock statement to mark a statement block as a critical section. Consider following code block (obj is some object): lock(obj){ // critical section } Any thread which needs to execute the code enclosed in lock(obj){} block 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 method (modify syncthreads.cs) is given below

public void Withdraw(double amount) { Console.WriteLine("WITHDRAWING {0}",amount); lock(this) { if(amount>balance) throw new Exception("INSUFFICIENT FUNDS"); Thread.Sleep(10); //do other stuff balance-=amount; } Console.WriteLine("BALANCE {0}",balance); } 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.cs) shows how two threads can be made to coordinate using the Monitor. using System; using System.Threading; class WaitingThread { static object obj = new object(); public static void Run() { for(int i = 0 ; i<=200 ; i++) Console.WriteLine("WELCOME : {0}",i); Monitor.Enter(obj); Monitor.Pulse(obj); Monitor.Exit(obj); } public static void Main() { new Thread(new ThreadStart(Run)).Start(); for(int i = 0 ; i <=200 ; i++) Console.WriteLine("HELLO : {0}",i); Monitor.Enter(obj); Monitor.Pulse(obj); Monitor.Exit(obj); Console.WriteLine("GOOD BYE"); } }

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 also supports the Asynchronous programming model . Using this model 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 int MyWorker(char ch, int max); The compiler will emit MyWorker class: class MyWorker : System.MulticastDelegate{ public int Invoke(char ch, int max); public System.IAsyncResult BeginInvoke(char ch, int max, System.AsyncCallback cb, object asyncState); public int EndInvoke(System.IAsyncResult result); } The BeginInvoke and EndInvoke methods can be used for asynchronous invocation of a method pointed by MyWorker delegate. In the example below (asynctest1.cs) 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. using System; delegate int MyWorker(char ch, int max); class AsyncTest1{ static MyWorker worker; static int DoWork(char ch, int max){ int t = Environment.TickCount; // returns number of milliseconds elapsed since the system started for(int i=1;i<=max;i++) Console.Write(ch); return Environment.TickCount - t; } static void Main(){ Console.WriteLine("Start"); worker = new MyWorker(DoWork); worker.BeginInvoke('+', 1000,null,null); // asynchronous call

DoWork('*',1000); // synchronous call Console.Read(); // pause until user enters a key } } 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.cs) demonstrates this approach. using System; delegate int MyWorker(char ch, int max); class AsyncTest2{ static MyWorker worker; static int DoWork(char ch, int max){ int t = Environment.TickCount; for(int i=1;i<=max;i++) Console.Write(ch); return Environment.TickCount - t; } static void Main(){ Console.WriteLine("Start"); worker = new MyWorker(DoWork); IAsyncResult result = worker.BeginInvoke('+', 1500,null,null); DoWork('*',1000); int time = 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(); } } 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.cs) used AsyncCallback to retrieve the value returned by DoWork method. using System; delegate int MyWorker(char ch, int max); class AsyncTest3{

static MyWorker worker; static int DoWork(char ch, int max){ int t = Environment.TickCount; for(int i=1;i<=max;i++) Console.Write(ch); return Environment.TickCount - t; } static void CallMeBack(IAsyncResult result){ int time = worker.EndInvoke(result); Console.WriteLine(" Work Completed in {0} milliseconds",time); } static void Main(){ Console.WriteLine("Start"); worker = new MyWorker(DoWork); AsyncCallback cb = new AsyncCallback(CallMeBack); IAsyncResult result = worker.BeginInvoke('+', 1000,cb,null); DoWork('*',1500); Console.Read(); } }

Chapter 6: 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.cs) displays content of a file whose name is passed through the command line. using System; using System.IO; using System.Text; // For Encoding class ReadFile{ public static void Main(string[] args){ Stream s = new FileStream(args[0], FileMode.Open); int size = (int) s.Length; byte[] buffer = new byte[size]; s.Read(buffer,0,buffer.Length); s.Close(); string text = Encoding.ASCII.GetString(buffer); Console.WriteLine(text); }

} 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: Stream s = File.OpenRead(file_name); where file_name is a string containing the name of the file. The next program (writefile.cs) 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. using System; using System.IO; using System.Text; class WriteFile{ public static void Main(string[] args){ Stream s = new FileStream(args[1], FileMode.Append, FileAccess.Write); string text = args[0]+"\r\n"; // append end of line characters byte[] buffer = Encoding.ASCII.GetBytes(text); s.Write(buffer,0,buffer.Length); s.Close(); // also flushes the stream } } 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: Stream s = 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.cs) takes 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. using System; using System.IO; class PlaceOrder{ public static void Main(string[] args){ DateTime dt = DateTime.Now; string today = dt.ToString("dd-MMM-yyyy" );

string record = today+"|"+String.Join(|, args); StreamWriter sw = new StreamWriter("orders.txt",true); sw.WriteLine(record); sw.Close(); } } 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: StreamWriter sw = File.AppendText(file_name); Out next program (vieworders.cs) parses orders.txt file and displays all the orders which were placed before the date provided in the first command line argument. using System; using System.IO; class ViewOrders{ public static void Main(string[] args){ DateTime adate = DateTime.ParseExact(args[0],"dd-MMM-yyyy", null); StreamReader sr = new StreamReader("orders.txt"); while(true){ string record = sr.ReadLine(); if(record == null) break; string[] fields = record.Split(new char[]{'|'}); DateTime odate = DateTime.ParseExact(fields[0], "dd-MMM-yyyy",null); if(odate < adate){ Console.WriteLine(record.Replace(|, )); } } sr.Close(); } } First the string in args[0] 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 the date provided by the user. We can also obtain a StreamReader to a file using: StreamWriter sw = 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.cs) writes some data to a file called info.dat and then rereads it and prints it. using System; using System.IO; class BinaryIO{ private static void WriteData(int i, double d, string t){ Stream s = File.OpenWrite("info.dat"); BinaryWriter bw = new BinaryWriter(s); bw.Write(i); bw.Write(d); // write length-prefixed string bw.Write(t); bw.Close(); } private static void ReadData(){ Stream s = File.OpenRead("info.dat"); BinaryReader br = new BinaryReader(s); int i = br.ReadInt32(); double d = br.ReadDouble(); string t = br.ReadString(); br.Close(); Console.WriteLine("{0}, {1}, {2}", i, d, t); } public static void Main(){ WriteData(12345, 3.1415467, "Hello World"); ReadData(); } } 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: 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 machine's 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.cs) is the Employee class whose instance we will serialize. using System; using System.Runtime.Serialization; [Serializable] public class Employee { public string Name; public string Job; public double Salary; public Employee(string name, string job, double salary){ Name=name; Job=job; Salary=salary; } public Employee(){ } // We override ToString() method of System.Object. This method is invoked // whenever an Employee object is to be converted to a string. public override string ToString(){ return String.Format("{0} is a {1} and earns {2}",Name,Job,Salary); } } The program below (binsertest1.cs) creates an instance of Employee class and stores it in file emp.bin. It also retreives and displays the object. using System; using System.IO;

using System.Runtime.Serialization.Formatters.Binary; class BinarySerializationTest1{ static BinaryFormatter bf = new BinaryFormatter(); private static void WriteEmployee(Employee emp){ Stream s = File.OpenWrite("emp.bin"); bf.Serialize(s,emp); s.Close(); } private static void ReadEmployee(){ Stream s = File.OpenRead("emp.bin"); Employee emp = (Employee)bf.Deserialize(s); s.Close(); Console.WriteLine(emp); //displays emp.ToString() } public static void Main(string[] args){ Employee emp = new Employee("Jack", "Clerk", 44000); WriteEmployee(emp); ReadEmployee(); } } Compile: csc binsertest1.cs employee.cs 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 dont 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. 1. The class must implement System.IO.ISerializable interface and provide implementation for its void GetObjectData(SerializationInfo info, StreamingContext context) 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. 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.

2.

Below is the Manager class (add it to employee.cs) which exhibits custom serialization. [Serializable] public class Manager : Employee, ISerializable { public Employee Secretary;

public Manager(string name, double salary, Employee secretary){ Name = name; Salary = salary; Secretary = secretary; Job = "Manager"; } public Manager(){ } // This constructor will be called upon during deserialization // Retreive the field values from the info object passed as first argument public Manager(SerializationInfo info, StreamingContext context){ Name=info.GetString("name"); Salary=info.GetDouble("salary"); Secretary=(Employee)info.GetValue("secretary", typeof(Employee)); Job = "Manager"; } // This method will be called upon during serialization // Add the field values to be stored to the info object object public void GetObjectData(SerializationInfo info, StreamingContext context){ info.AddValue("name",Name); info.AddValue("salary",Salary); info.AddValue("secretary",Secretary); } public override string ToString(){ return base.ToString()+"\nHis secretary "+Secretary.ToString(); } } The program below (binsertest2.cs) creates an instance of Manager class and stores it in file boss.bin. It also retreives and displays the object. using System; using System.IO; using System.Runtime.Serialization.Formatters.Binary; class BinarySerializationTest{ static BinaryFormatter bf = new BinaryFormatter(); private static void WriteManager(Manager mgr){ Stream s = File.OpenWrite("boss.bin"); bf.Serialize(s,mgr); s.Close();

} private static void ReadManager(){ Stream s = File.OpenRead("boss.bin"); Manager mgr = (Manager)bf.Deserialize(s); s.Close(); Console.WriteLine(mgr); } public static void Main(string[] args){ Employee sec = new Employee("Jane", "Steno", 24000); Manager mgr = new Manager("John", 72000, sec); WriteManager(mgr); ReadManager(); } } 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.cs) stores a Manager instance in file boss.xml and then reads it back and displays it. using System; using System.IO; using System.Xml.Serialization; class XmlSerializationTest{ static XmlSerializer xs = new XmlSerializer(typeof(Manager)); private static void WriteManager(Manager mgr){ Stream s = File.OpenWrite("boss.xml"); xs.Serialize(s,mgr); s.Close(); } private static void ReadManager(){ Stream s = File.OpenRead("boss.xml"); Manager mgr = (Manager)xs.Deserialize(s); s.Close(); Console.WriteLine(mgr); } public static void Main(string[] args){ Employee sec = new Employee("Jane", "Steno", 24000); Manager mgr = new Manager("John", 72000, sec);

WriteManager(mgr); ReadManager(); } } Compile: csc xmlsertest.cs employee.cs 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. 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> <salary>36000</salary> </employee>

<employee id="104"> <name>Jill</name> <job>clerk</job> <salary>45000</salary> </employee> </employees> The program below (listemp.cs) displays id and salary of each employee in emp.xml. using System; using System.Xml; class ListEmployees{ public static void Main(){ XmlDocument doc = new XmlDocument(); // load employee.xml in XmlDocument doc.Load("emp.xml"); // obtain a list of all employee element XmlNodeList list = doc.GetElementsByTagName("employee"); foreach(XmlNode emp in list){ // obtain value of id attribute of the employee tag string id = emp.Attributes["id"].Value; // obtain value of salary subtag of the employee tag string sal = emp["salary"].FirstChild.Value; Console.WriteLine("{0}\t{1}",id,sal); } } } Compile: csc listemp.cs The next program (addemp.cs) takes informations from command line and makes a new entry in employee.xml using System; using System.Xml; class AddEmployee{ public static void Main(string[] args){ XmlDocument doc = new XmlDocument(); doc.Load("emp.xml"); // create a new employee element and value of its id attribute XmlElement emp = doc.CreateElement("employee"); emp.SetAttribute("id",args[0]); // create a name tag and set its value XmlNode name = doc.CreateNode("element","name","");

name.InnerText = args[1]; // make name tag a subtag of newly created employee tag emp.AppendChild(name); XmlNode job = doc.CreateNode("element","job",""); job.InnerText = args[2]; emp.AppendChild(job); XmlNode sal = doc.CreateNode("element","salary",""); sal.InnerText = args[3]; emp.AppendChild(sal); // add the newly created employee tag to the root (employees) tag doc.DocumentElement.AppendChild(emp); // save the modified document doc.Save("employee.xml"); } } Compile: csc addemp.cs Execute: addemp 105 Jeff president 105000 Open employee.cs you will see the following fragment has been appended. <employee id="105"> <name>Jeffl</name> <job>president</job> <salary>105000</salary> </employee>

Chapter 7: Sockets
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.cs) 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. using System; using System.Net; class ShowIP{ public static void Main(string[] args){ string name = (args.Length < 1) ? Dns.GetHostName() : args[0]; try{ IPAddress[] addrs = Dns.Resolve(name).AddressList; foreach(IPAddress addr in addrs) Console.WriteLine("{0}/ {1}",name,addr); }catch(Exception e){ Console.WriteLine(e.Message); } } } 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 receivers IPAddress in the packet, later on this machine will transfer the packet to an application running on it which is bound to the receivers 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. 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. TcpListener listener = 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. Socket soc = listener.AcceptSocket(); // blocks 3. Create a System.Net.Sockets.NetworkStream from the above socket. Stream s = 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 2 7 must be executed in multiple threads. Program below (emptcpserver.cs) 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 employees name. using System; using System.Threading; using System.IO; using System.Net; using System.Net.Sockets; class EmployeeTCPServer{ static TcpListener listener; const int LIMIT = 5; //5 concurrent clients static string[] names = {"John", "Jane", "Jack", "Jill"}; static string[] jobs = {"Manager", "Steno", "Salesman", "Clerk", "No such employee"}; private static string JobOf(string name){ int i = 0; for(;i < names.Length; i++){

if(names[i] == name) break; } return jobs[i]; } public static void Main(){ listener = new TcpListener(2055); listener.Start(); #if LOG Console.WriteLine("Server mounted, listening to port 2055"); #endif for(int i = 0;i < LIMIT;i++){ Thread t = new Thread(new ThreadStart(Service)); t.Start(); } } public static void Service(){ while(true){ Socket soc = listener.AcceptSocket(); #if LOG Console.WriteLine("Connected: {0}", soc.RemoteEndPoint); #endif try{ Stream s = new NetworkStream(soc); StreamReader sr = new StreamReader(s); StreamWriter sw = new StreamWriter(s); sw.AutoFlush = true; // enable automati flushing sw.WriteLine("{0} Employees available", names.Length); while(true){ string name = sr.ReadLine(); if(name == "" || name == null) break; sw.WriteLine(JobOf(name)); } s.Close(); }catch(Exception e){ #if LOG Console.WriteLine(e.Message); #endif } soc.Close(); #if LOG Console.WriteLine("Disconnected: {0}", soc.RemoteEndPoint); #endif }

} } 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) csc /D:LOG emptcpserver.cs or without the LOG symbol (silent mode) csc emptcpserver.cs 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 servers host name and port. TcpClient client = new TcpClient(host, port); 2. Obtain the stream from above TCPClient. Stream s = 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.cs) communicates with emptcpserver. using System; using System.IO; using System.Net.Sockets; class EmployeeTCPClient{ public static void Main(string[] args){ TcpClient client = new TcpClient(args[0],2055); try{ Stream s = client.GetStream(); StreamReader sr = new StreamReader(s); StreamWriter sw = new StreamWriter(s); sw.AutoFlush = true; Console.WriteLine(sr.ReadLine()); while(true){ Console.Write("Name: "); string name = Console.ReadLine(); sw.WriteLine(name); if(name == "") break; Console.WriteLine(sr.ReadLine()); } s.Close(); }finally{ // code in finally block is guranteed to execute irrespective of // whether any exception occurs or does not occur in the try block

client.Close(); } } } 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 UdpClient client = new UdpClient(local_ port); or UdpClient client = new UdpClient(remote_host, remote_port); 2. Receive data using above UDPClient. System.Net.IPEndPoint ep = null; byte[] data = client.Receive(ref 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.cs) receives name of an employee from a remote client and sends it back the job of that employee using UDP. using System; using System.Net; using System.Net.Sockets; using System.Text; class EmployeeUDPServer{ static string[] names = {"John", "Jane", "Jack", "Jill"}; static string[] jobs = {"Manager", "Steno", "Salesman", "Clerk", "No such employee"}; private static string JobOf(string name){ int i = 0; for(;i < names.Length; i++){ if(names[i] == name) break; } return jobs[i]; }

public static void Main(){ UdpClient udpc = new UdpClient(2055); Console.WriteLine("Server started, servicing on port 2055"); IPEndPoint ep = null; while(true){ byte[] rdata = udpc.Receive(ref ep); string name = Encoding.ASCII.GetString(rdata); string job = JobOf(name); byte[] sdata = Encoding.ASCII.GetBytes(job); udpc.Send(sdata,sdata.Length,ep); } } } The next program (empudpclient.cs) is a UDP client to above server program. using System; using System.Net; using System.Net.Sockets; using System.Text; class EmployeeUDPClient{ public static void Main(string[] args){ UdpClient udpc = new UdpClient(args[0],2055); IPEndPoint ep = null; while(true){ Console.Write("Name: "); string name = Console.ReadLine(); if(name == "") break; byte[] sdata = Encoding.ASCII.GetBytes(name); udpc.Send(sdata,sdata.Length); byte[] rdata = udpc.Receive(ref ep); string job = Encoding.ASCII.GetString(rdata); Console.WriteLine(job); } } } UDP also supports multicasting i.e sending a single datagram to multiple receivers. To do so the sender sends 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.cs) sends a datagram every 5 seconds containing share price (a randomly calculated value) of an imaginary company to address 230.0.0.1. using System;

using System.Net; using System.Net.Sockets; using System.Text; class StockPriceMulticaster{ static string[] symbols = {"ABCD","EFGH", "IJKL", "MNOP"}; public static void Main(){ UdpClient publisher = new UdpClient("230.0.0.1",8899); Console.WriteLine("Publishing stock prices to 230.0.0.1:8899"); Random gen = new Random(); while(true){ int i = gen.Next(0,symbols.Length); double price = 400*gen.NextDouble()+100; string msg = String.Format("{0} {1:#.00}",symbols[i],price); byte[] sdata = Encoding.ASCII.GetBytes(msg); publisher.Send(sdata,sdata.Length); System.Threading.Thread.Sleep(5000); } } } Compile and start stockpricemulticaster The next program (stockpricereceiver.cs) joins the group of address 230.0.0.1, receives 10 stock prices and then leaves the group. using System; using System.Net; using System.Net.Sockets; using System.Text; class StockPriceReceiver{ public static void Main(){ UdpClient subscriber = new UdpClient(8899); IPAddress addr = IPAddress.Parse("230.0.0.1"); subscriber.JoinMulticastGroup(addr); IPEndPoint ep = null; for(int i=0; i<10;i++){ byte[] pdata = subscriber.Receive(ref ep); string price = Encoding.ASCII.GetString(pdata); Console.WriteLine(price); } subscriber.DropMulticastGroup(addr); } } Compile and run stockpricereceiver.

Chapter 8: Distributed Programming with Remoting


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 copy's 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 client's 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

understandable way. A FileInfo object, for example, contains a reference to an operating system file, which has a unique address in the server process's 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 object's 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 client's 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.

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. Our first program (orderserver.cs) provides a remoting object which exposes a PlaceOrder method for inserting new orders in orders.txt. It uses Server-activated SingleCall model for activation. using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Tcp; public class OrderService : MarshalByRefObject{ public void PlaceOrder(string cust, int prod, int quant){ DateTime dt = DateTime.Now; string today = dt.ToString("dd-MMM-yyyy" ); string record = today+"|"+cust+|+prod+|+quant; StreamWriter sw = new StreamWriter("orders.txt",true); sw.WriteLine(record);

sw.Close(); } } public class OrderServer{ public static void Main(){ ChannelServices.RegisterChannel(new TcpChannel(2255)); RemotingConfiguration.RegisterWellKnownServiceType( typeof(OrderService), "order.rem", WellKnownObjectMode.SingleCall); Console.WriteLine("order.rem service ready, enter any key to terminate"); Console.ReadLine(); } } 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. Given below (orderclient.cs) is a client program which consumes the OrderService. using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Tcp; public class OrderClient{ public static void Main(){ ChannelServices.RegisterChannel(new TcpChannel()); OrderService remote = (OrderService) Activator.GetObject( typeof(OrderService), //Type of remoting obj "tcp://localhost:2255/order.rem"); //URL of remoting obj Console.Write("Customer ID: "); string cust = Console.ReadLine(); Console.Write("Product No: "); int prod = Int32.Parse(Console.ReadLine()); Console.Write("Quantity: "); int quant = Int32.Parse(Console.ReadLine()); remote.PlaceOrder(cust,prod,quant); Console.WriteLine("Order Placed); } } Compile server: csc orderserver.cs

Compile client: csc /r:orderserver.exe orderclient.cs Note: during compilation and runtime orderclient needs orderserver assembly since it refer to OrderService defined in orderserver.exe. 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.cs (containing the interface exposed by the server), auctionserver.cs (containing the actual remoting class) and auctionclient.cs (containing the client program) // File: auction.cs namespace Auction{ public delegate void PriceChangeHandler(double price); public interface IAuctioneer{ event PriceChangeHandler PriceChanged; double CurrentPrice{ get; } bool Bid(double price); } } Compile: csc /t:library auction.cs // File: auctionserver.cs using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Tcp; namespace Auction{ public class Auctioneer : MarshalByRefObject, IAuctioneer{ public event PriceChangeHandler PriceChanged; private double current = 500; public double CurrentPrice{ get{ return current;

} } public bool Bid(double price){ if(price - current < 100 || price - current > 1000){ return false; } current = price; if(PriceChanged != null) PriceChanged(price); return true; } } } class AuctionServer{ public static void Main(){ ChannelServices.RegisterChannel(new TcpChannel(3355)); RemotingConfiguration.RegisterWellKnownServiceType( typeof(Auction.Auctioneer), "auction.rem", WellKnownObjectMode.Singleton); Console.WriteLine("auction.rem service ready, enter any key to + terminate"); Console.ReadLine(); } } Compile: csc /r:auction.dll auctionserver.cs // File: auctionclient.cs using Auction; using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Tcp; class AuctionClient : MarshalByRefObject{ public AuctionClient(){ ChannelServices.RegisterChannel(new TcpChannel(0)); IAuctioneer remote = (IAuctioneer) Activator.GetObject( typeof(Auction.IAuctioneer), "tcp://localhost:3355/auction.rem"); Console.WriteLine("Current Price: {0}", remote.CurrentPrice); remote.PriceChanged += new PriceChangeHandler(AuctionClient_PriceChanged); while(true){ Console.Write("Bid: ");

string text = Console.ReadLine(); if(text == "") break; try{ double price = Double.Parse(text); if(!remote.Bid(price)) Console.WriteLine("Bid Failed"); }catch(Exception e){ Console.WriteLine(e); break; } } remote.PriceChanged -= new PriceChangeHandler(AuctionClient_PriceChanged); } public void AuctionClient_PriceChanged(double price){ Console.WriteLine("Price changed to {0}",price); } public static void Main(){ new AuctionClient(); } } Compile: csc /r:auction.dll auctionclient.cs 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.cs) 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) using System; using System.Collections; using System.Runtime.Remoting; public class Cart : MarshalByRefObject{ private ArrayList store = new ArrayList(); public Cart(){ Console.WriteLine("Cart instantiated"); } public void AddItem(string item){ store.Add(item); }

public void RemoveItem(string item){ store.Remove(item); } public string[] ListItems(){ string[] items = new string[store.Count]; store.CopyTo(items); return items; } } class CartServer{ public static void Main(){ RemotingConfiguration.Configure("cartserver.exe.config"); Console.WriteLine(RemotingConfiguration.ApplicationName +" started, enter any key to terminate..."); Console.ReadLine(); } } Here is the content of cartserver.exe.config file
<configuration> <system.runtime.remoting> <application name="CartServer"> <service> <activated type = "Cart,CartServer"/> </service> <channels> <channel type = "System.Runtime.Remoting.Channels.Http.HttpChannel,System.Runtime.Remoting" port="8080"/> </channels> </application> </system.runtime.remoting> </configuration>

The next program (cartclient.cs) is the client program for above server. using System; using System.Runtime.Remoting; public class CartClient{ public static void Main(string[] args){ RemotingConfiguration.Configure("cartclient.exe.config");

Cart cart = new Cart(); //instantiates Cart on server foreach(string item in args) cart.AddItem(item); 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); } } 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 type= "System.Runtime.Remoting.Channels.Http.HttpChannel,System.Runtime.Remoting"/> </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, HttpChannel uses SoapFormatter and hence the clients do not need to know anything about the platform, object model, or programming language used to implement the service; they only need to understand how to send and receive SOAP messages (HTTP and XML). NET Framework also offers a special utility called soapsuds.exe which can build a stand-in class for the clients. For example soapsuds can be used to generate cartserver.dll from cartserver.exe which can be used to compile cartclient: soapsuds /types:Cart,cartserver /oa:cartserver.dll csc /r: cartserver.dll cartclient.cs Now cartserver.dll can be included (instead of cartserver.exe) in the distribution of cartclient.exe.

Chapter 9: Database Programming with ADO.NET


Accessing data has become a major programming task for modern software programming, both for standalone applications and for web-based applications. Microsoft's 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 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.

DataReader DataAdapter

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 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), EMAIL VARCHAR(24)) CREATE TABLE PRODUCT( PNO INT PRIMARY KEY, PRICE NUMERIC) 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) 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) INSERT INTO ORD_CTL VALUES(104, 105, 1006) Our first program (querytest.cs) displays all the rows in ord table of Access database sales.mdb. It uses OLE DB.NET Data Provider.

using System; using System.Data; using System.Data.OleDb; class QueryTest{ public static void Main(string[] args){ OleDbConnection cn = new OleDbConnection( "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=sales.mdb"); string sql = "select ord_no,ord_date,cust_id,pno,qty from ord"; if(args.Length > 0) sql += " order by "+args[0]; OleDbCommand cmd = new OleDbCommand(sql, cn); cn.Open(); try{ OleDbDataReader dr = cmd.ExecuteReader(); while(dr.Read()){ Console.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}", dr["ord_no"],dr["ord_date"],dr.GetString(2),dr["pno"],dr["qty"]); } dr.Close(); }finally{ cn.Close(); // always close the connection } } } 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). The next program (updatetest.cs) 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. using System; using System.Data; using System.Data.OleDb; class UpdateTest{

public static void Main(string[] args){ OleDbConnection cn = new OleDbConnection( "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=sales.mdb"); string sql = "update product set price = 0.9*price"; if(args.Length > 0) sql += " where pno = "+args[0]; OleDbCommand cmd = new OleDbCommand(sql, cn); cn.Open(); try{ int n = cmd.ExecuteNonQuery(); Console.WriteLine("{0} row/s updated",n); }finally{ cn.Close(); } } } The next program (paramsqltest.cs) 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. using System; using System.Data; using System.Data.OleDb; class ParamSQLTest{ public static int PlaceOrder(string cust, int prod, int quant){ OleDbConnection cn = new OleDbConnection( "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=sales.mdb"); OleDbCommand cmd = new OleDbCommand(); cmd.Connection = cn; cn.Open(); cmd.Transaction = cn.BeginTransaction(); int ordid; try{ cmd.CommandText = "update ord_ctl set ord_no = ord_no + 1"; cmd.ExecuteNonQuery(); cmd.CommandText = "select ord_no from ord_ctl"; ordid = (int) 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(Exception e){ cmd.Transaction.Rollback(); Console.WriteLine(e.Message); ordid = -1; } cn.Close(); return ordid; } public static void Main(string[] args){ int ordid = PlaceOrder(args[0], Int32.Parse(args[1]), Int32.Parse(args[2])); if(ordid >= 0) Console.WriteLine("Order placed, Order No: {0}",ordid); } } 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 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.cs) uses SQL Server.NET Data Provider to connect to sales database and places an order by calling the above stored procedure using System; using System.Data; using System.Data.SqlClient; class StoredProcedureTest{ public static void Main(string[] args){ SqlConnection cn = new SqlConnection( "server=localhost;uid=sa;pwd=;database=sales"); SqlCommand cmd = new SqlCommand("place_order",cn); cmd.CommandType = CommandType.StoredProcedure; cn.Open(); try{ cmd.Parameters.Add("@cust",SqlDbType.VarChar,8); cmd.Parameters["@cust"].Value = args[0]; cmd.Parameters.Add("@prod",SqlDbType.Int); cmd.Parameters["@prod"].Value = Int32.Parse(args[1]); cmd.Parameters.Add("@quant",SqlDbType.Int); cmd.Parameters["@quant"].Value = Int32.Parse(args[2]); cmd.Parameters.Add("@ordid",SqlDbType.Int); cmd.Parameters["@ordid"].Direction = ParameterDirection.Output; cmd.ExecuteNonQuery(); Console.WriteLine("Order No: {0}", cmd.Parameters["@ordid"].Value); }finally{ cn.Close(); } } } Yet another way of managing transactions is using COM+ Component Service of Win2000. Given below is the source code for Order component (ordcomp.cs) which relies on COM+ transaction processing (explanation follows the code). using System; using System.Reflection;

using System.Data; using System.Data.SqlClient; using System.EnterpriseServices; [assembly: ApplicationName("OrderComponent")] [assembly: AssemblyKeyFile("ordcomp.snk")] namespace OrderComponent{ [Transaction(TransactionOption.Required)][JustInTimeActivation] public class Order : ServicedComponent{ [AutoComplete] public int Place(string customer, int product, int quantity){ SqlConnection cn = new SqlConnection("server=localhost;uid=sa;pwd=;database=sales"); SqlCommand cmd = new SqlCommand(); cmd.Connection = cn; cn.Open(); int ordid = -1; try{ cmd.CommandText = "update ord_ctl set ord_no = ord_no + 1"; cmd.ExecuteNonQuery(); cmd.CommandText = "select ord_no from ord_ctl"; int ordno = (int) cmd.ExecuteScalar(); //Unlike OleDbCommand, under SqlCommand the parameters must be named cmd.CommandText = "insert into ord values(@ord_no,@ord_date,@cust_id,@pno,@qty)"; cmd.Parameters.Add("@ord_no", SqlDbType.Int).Value = ordno; 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(); ordid=ordno; }finally{ cn.Close(); } return ordid; } }

} Here are important points to note about the above component class. 1. A COM+ application must be identified with a suitable name. The name is provided using assembly: ApplicationName attribute. 2. 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 in the code using assembly:AssemblyKeyFile attribute or it can be passed during compilation using /keyf option of the assembly linker(al.exe). 3. The component class must be derived from System.EnterpriseServices.ServicedComponent and must be marked with the configuration attributes. We have marked the Order 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. The JustInTimeActivation attribute indicates that an object of this component must be activated on demand. 4. Finally this class contains Place() method which is marked with AutoComplete attribute. This attribute indicates that when the method completes without any exception the transaction must be commited. In case of any exception the transaction will be rolledback Compile the above class: sn k ordcomp.snk csc /t:library ordcomp.cs Given below is a client program(ordclient.cs) which uses the above component. using OrderComponent; using System; class OrderClient{ public static void Main(string[] args){ Order ord = new Order(); int ordid = ord.Place(args[0], Int32.Parse(args[1]), Int32.Parse(args[2])); if(ordid >= 0) Console.WriteLine("Order placed, Order No: {0}",ordid); } } Compile the program: csc /r:ordcomp.dll ordclient.cs When this program is executed for the first time, CLR will automatically register the OrderComponent. Registration occurs only once for a particular version of an assembly.

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.cs) fills all the records from the product table into a DataSet and then displays it. using System; using System.Data; using System.Data.SqlClient; class ViewDataSet{ public static void Main(string[] args){ SqlConnection cn = new SqlConnection( "server=localhost;uid=sa;pwd=;database=sales"); SqlDataAdapter cmd = new SqlDataAdapter("select * from product",cn); DataSet ds = new DataSet(); cmd.Fill(ds,"products"); DataTable table = ds.Tables["products"]; foreach(DataColumn column in table.Columns){ Console.Write("{0,-12}",column.ColumnName); } Console.WriteLine(); foreach(DataRow row in table.Rows){ foreach(object field in row.ItemArray){ Console.Write("{0,-12}",field); } Console.WriteLine(); } } } The above program uses an SqlDataAdapter which hold commands to view and update a database table. The program below (updateds.cs) 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. using System; using System.Data; using System.Data.SqlClient;

class UpdateDataSet{ public static void Main(string[] args){ SqlConnection cn = new SqlConnection( "server=localhost;uid=sa;pwd=;database=sales"); SqlDataAdapter cmd = new SqlDataAdapter("select * from product",cn); SqlCommandBuilder cb = new SqlCommandBuilder(cmd); DataSet ds = new DataSet(); cmd.Fill(ds,"products"); foreach(DataRow row in ds.Tables["products"].Rows){ row["price"] = 1.1 * ((double)row["price"]); } cmd.Update(ds,"products"); } } The next program (filterds1.cs) 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. using System; using System.Data; using System.Data.SqlClient; class FilterDataSet1{ public static void Main(string[] args){ SqlConnection cn = new SqlConnection( "server=localhost;uid=sa;pwd=;database=sales"); SqlDataAdapter cmd = new SqlDataAdapter("select * from ord",cn); DataSet ds = new DataSet(); cmd.Fill(ds,"orders"); string filter = String.Format("cust_id='{0}'",args[0]); DataRow[] rows = ds.Tables["orders"].Select(filter,"pno"); foreach(DataRow row in rows){ Console.WriteLine(row["pno"] + "\t" + row["qty"]); } } } The next program (filterds2.cs) performs the same task as the above one but used DataView to filter the DataSet. using System; using System.Data; using System.Data.SqlClient;

class FilterDataSet2{ public static void Main(string[] args){ SqlConnection cn = new SqlConnection( "server=localhost;uid=sa;pwd=;database=sales"); SqlDataAdapter cmd = new SqlDataAdapter("select * from ord",cn); DataSet ds = new DataSet(); cmd.Fill(ds,"orders"); DataView dv = new DataView(ds.Tables["orders"]); dv.Sort = "pno"; dv.RowFilter = String.Format("cust_id = '{0}'",args[0]); foreach(DataRowView row in dv){ Console.WriteLine(row["pno"] + "\t" + row["qty"]); } } } 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.cs) uses DataRelation to display each customers cust_id , email (from customer table) and order_no (from order table) of all his orders. using System; using System.Data; using System.Data.SqlClient; class RelationalDataSet{ public static void Main(string[] args){ SqlConnection cn = new SqlConnection( "server=localhost;uid=sa;pwd=;database=sales"); SqlDataAdapter cmdcust = new SqlDataAdapter( "select cust_id,email from customer",cn); SqlDataAdapter cmdord = new SqlDataAdapter( "select cust_id,ord_no from ord",cn); DataSet ds = 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"]); foreach(DataRow customer in ds.Tables["customers"].Rows){ Console.WriteLine("Customer ID: {0}",customer["cust_id"]); Console.WriteLine("Email : {0}",customer["email"]); Console.Write("Order No : ");

foreach(DataRow order in customer.GetChildRows(ds.Relations["custord"])){ Console.Write("{0,-8}",order["ord_no"]); } Console.WriteLine(); Console.WriteLine(); } } } A copy of data in a DataSet can be stored in the XML format using its WriteXml method. The program below (dstoxml.cs) stores all the records in the product table in prod.xml file. using System; using System.Data; using System.Data.SqlClient; class DataSetToXml{ public static void Main(string[] args){ SqlConnection cn = new SqlConnection( "server=localhost;uid=sa;pwd=;database=sales"); SqlDataAdapter cmd = new SqlDataAdapter("select * from product",cn); DataSet ds = new DataSet("products"); cmd.Fill(ds,"product"); ds.WriteXml("prod.xml"); } } 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.cs) loads prod.xml in a DataSet and displays its records. using System;

using System.Data; using System.Xml; class XmlToDataSet{ public static void Main(string[] args){ XmlDataDocument datadoc = new XmlDataDocument(); DataSet ds = datadoc.DataSet; ds.ReadXml("prod.xml");; DataTable table = ds.Tables[0]; foreach(DataColumn column in table.Columns){ Console.Write("{0,-12}",column.ColumnName); } Console.WriteLine(); foreach(DataRow row in table.Rows){ foreach(object field in row.ItemArray){ Console.Write("{0,-12}",field); } Console.WriteLine(); } } }

Chapter 10: GUI Programming with Windows Forms


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 you've 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 user's local computer. The program (winhello.cs) below displays a simple window with one Button on it. The buttons Click event and the Forms Closing event are handled to provide user interactions. using System; using System.Windows.Forms; using System.ComponentModel; using System.Drawing; class WinHello : Form{ private Button bnclick; public WinHello(){ 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); bnclick.Click += new EventHandler(bnclick_Click); Controls.Add(bnclick); Closing += new CancelEventHandler(WinHello_Closing);

} private void bnclick_Click(object sender, EventArgs ev){ MessageBox.Show("Hello World!!!!!", "Button Clicked", MessageBoxButtons.OK ,MessageBoxIcon.Information); } private void WinHello_Closing(object sender, CancelEventArgs ev){ if(MessageBox.Show("Are you sure?", "Confirm exit", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No) ev.Cancel = true; } // Initialize the main thread using Single Threaded Apartment(STA) model [STAThread] public static void Main(){ Application.Run(new WinHello()); } } The next program (emicalc.cs) is a complete Windows Application for calculating equal monthly instalment for a given loan. The available schemes (which determine duration and rate of interest) are displayed in a dropdown box. Employees enjoy a discount in the interest rate and the employee status is displayed through a check box. using System; using System.Drawing; using System.Windows.Forms; public class EMICalculator : Form{ private Label lblloan; private TextBox txtloan; private Label lblscheme; private ComboBox cboscheme; private Label lblemp; private CheckBox chkemp; private Label lblemi; private TextBox txtemi; private MenuItem mifile; private MenuItem miexit; public EMICalculator(){ Text = "EMI Calculator"; ClientSize = new Size (312, 213);

lblloan = new Label (); lblloan.Location = new Point (28, 32); lblloan.Text = "Loan:"; lblloan.Size = new Size (58, 13); Controls.Add (lblloan); txtloan = new TextBox (); txtloan.Location = new Point (110, 29); txtloan.Size = new Size (104, 20); txtloan.KeyPress += new KeyPressEventHandler (txtloan_KeyPress); txtloan.TextChanged += new EventHandler (txtloan_TextChanged); Controls.Add (txtloan); lblscheme = new Label (); lblscheme.Location = new Point (28, 61); lblscheme.Text = "Scheme:"; lblscheme.Size = new Size (58, 13); Controls.Add (lblscheme); cboscheme = new ComboBox (); cboscheme.Location = new Point (110, 59); cboscheme.Size = new Size (106, 21); cboscheme.DropDownStyle = ComboBoxStyle.DropDownList; // Populate cboscheme with instances of scheme. The string returned // by Scheme.ToString() will be displayed in the drop-down list cboscheme.Items.Add(new Scheme("Bronze",6,8)); cboscheme.Items.Add(new Scheme("Silver",12,10)); cboscheme.Items.Add(new Scheme("Gold",18,12)); cboscheme.Items.Add(new Scheme("Platinum",24,13.5F)); cboscheme.SelectedIndex = 1; cboscheme.SelectedIndexChanged += new EventHandler (cboscheme_SelectedIndexChanged); Controls.Add (cboscheme); lblemp = new Label (); lblemp.Location = new Point (28, 89); lblemp.Text = "Employee:"; lblemp.Size = new Size (58, 13); Controls.Add (lblemp); chkemp = new CheckBox (); chkemp.Location = new Point (110, 85); chkemp.Size = new Size (17, 24); chkemp.CheckedChanged += new EventHandler (chkemp_CheckedChanged); Controls.Add (chkemp);

lblemi = new Label (); lblemi.Location = new Point (28, 122); lblemi.Text = "EMI:"; lblemi.Size = new Size (58, 13); Controls.Add (lblemi); txtemi = new TextBox (); txtemi.Location = new Point (110, 116); txtemi.ReadOnly = true; txtemi.Size = new Size (100, 20); Controls.Add (txtemi); Menu = new MainMenu(); mifile = new MenuItem(); mifile.Text = "&File"; miexit = new MenuItem(); miexit.Text = "E&xit"; miexit.Click += new EventHandler(miexit_Click); mifile.MenuItems.Add(miexit); Menu.MenuItems.Add(mifile); } protected void txtloan_KeyPress (object sender, KeyPressEventArgs e){ if(e.KeyChar == '\r'){ DoCalculation(); } } protected void txtloan_TextChanged (object sender, EventArgs e){ txtemi.Text = ""; } protected void cboscheme_SelectedIndexChanged (object sender, EventArgs e){ DoCalculation(); } protected void chkemp_CheckedChanged (object sender, EventArgs e){ DoCalculation(); } protected void miexit_Click (object sender, EventArgs e){ Application.Exit(); } private void DoCalculation (){

if(txtloan.Text == "") return; double loan = Double.Parse(txtloan.Text); Scheme scheme = (Scheme) cboscheme.SelectedItem; if(chkemp.Checked) scheme.Rate = scheme.Rate - 5; txtemi.Text = string.Format("{0:#.00}",scheme.EMI(loan)); } [STAThread] public static void Main(string[] args) { Application.Run(new EMICalculator()); } } struct Scheme{ public string Name; public int Period; public float Rate; public Scheme(string name, int period, float rate){ Name=name; Period=period; Rate=rate; } //For display in ComboBox public override string ToString(){ return Name; } public double EMI(double P){ float I = Rate/1200; int M = Period; return P * (I/(1 - Math.Pow((1 + I), -M))); } } Our next example (readpad.cs) demonstrates how to create an MDI (multiple document interface) windows application using WindowForms. It also shows the use of menus and dialog box. using System; using System.Drawing; using System.ComponentModel; using System.Windows.Forms; using System.IO; using System.Text; namespace ReadPad{

public class MainForm : Form{ private MenuItem miopen; private MenuItem misep; private MenuItem miexit; private MenuItem mifile; private String startDir = "C:\\"; private MdiClient client; public MainForm(){ Text = "ReadPad"; WindowState = FormWindowState.Maximized; // start as a maximized window client = new MdiClient(); client.Dock = DockStyle.Fill; Controls.Add(client); IsMdiContainer = true; miopen = new MenuItem(); miopen.Click += new EventHandler(miopen_Click); miopen.Text = "&Open..."; misep = new MenuItem(); misep.Text = "-"; // a separator miexit = new MenuItem(); miexit.Text = "E&xit"; miexit.Click += new EventHandler(miexit_Click); mifile = new MenuItem(); mifile.Text = "&File"; // add miopen, misep and miexit to mifile mifile.MenuItems.AddRange(new MenuItem[] {miopen,misep,miexit}); // set the MainMenu of the current window to a MainMenu containing mifile Menu = new MainMenu(new MenuItem[]{mifile}); } private void LoadFile(String fname){ if(fname != ""){ Stream s = new FileStream(fname,FileMode.Open); byte[] buffer = new byte[(int)s.Length]; s.Read(buffer,0,buffer.Length);

s.Close(); ChildForm cfrm = new ChildForm(); cfrm.MdiParent = this; cfrm.txtout.Text = Encoding.ASCII.GetString(buffer); FileInfo info = new FileInfo(fname); cfrm.Text = info.Name; startDir = info.Directory.Name; cfrm.Show(); } } private void miopen_Click(object sender, EventArgs e){ OpenFileDialog dlg = new OpenFileDialog(); dlg.InitialDirectory = startDir ; dlg.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*" ; if(dlg.ShowDialog() == DialogResult.OK){ LoadFile(dlg.FileName); } } private void miexit_Click(object sender, EventArgs e){ Application.Exit(); } [STAThread] public static void Main(string[] args) { Application.Run(new MainForm()); } } public class ChildForm : Form{ internal TextBox txtout; public ChildForm(){ Text = "ChildForm"; txtout = new TextBox(); txtout.Dock = DockStyle.Fill; txtout.Multiline = true; txtout.ReadOnly = true; // this will change its background color to gray txtout.BackColor = Color.White; txtout.ScrollBars = ScrollBars.Both; txtout.WordWrap = false; Controls.Add(txtout); }

} } Window Form controls can be bound to fields in a DataSet for viewing and updating data in the database. The program below (windbtest1.cs) 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. using System; using System.Windows.Forms; using System.Drawing; using System.Data; using System.Data.SqlClient; class WinDBTest1 : Form{ static SqlConnection cn = new SqlConnection( "server=localhost;uid=sa;pwd=;database=sales"); TextBox txtpno; TextBox txtprice; Button bnprev,bnnext; SqlDataAdapter cmdprod; DataSet ds; BindingManagerBase bm; bool Dirty = false; public WinDBTest1(){ 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); bnprev.Click += new EventHandler(bnprev_Click); Controls.Add(bnprev); bnnext = new Button(); bnnext.Text = "-->";

bnnext.Location = new Point(120,100); bnnext.Size = new Size(40,20); bnnext.Click += new EventHandler(bnnext_Click); 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"]; bm.PositionChanged += new EventHandler(bm_PositionChanged); bm.CurrentChanged += new EventHandler(bm_CurrentChanged); bm_PositionChanged(null,null); } private void bnprev_Click(object sender, EventArgs ev){ if (bm.Position > 0) bm.Position -= 1; } private void bnnext_Click(object sender, EventArgs ev){ if (bm.Position < bm.Count -1) bm.Position += 1; } private void bm_PositionChanged(object sender, EventArgs ev){ Text = String.Format("Record: {0}/{1}",bm.Position+1,bm.Count); } private void bm_CurrentChanged(object sender, EventArgs ev){ Dirty = true; } public override void Dispose(){ base.Dispose(); if(Dirty) { //Build commands for updating the database and update SqlCommandBuilder cb = new SqlCommandBuilder(cmdprod); cmdprod.Update(ds,"products"); } } [STAThread] public static void Main(string[] args){ Application.Run(new WinDBTest1()); }

} Our next program (windbtest2.cs) 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. using System; using System.Windows.Forms; using System.Drawing; using System.Data; using System.Data.SqlClient; class WinDBTest2 : Form{ static SqlConnection cn = new SqlConnection( "server=localhost;uid=sa;pwd=;database=sales"); ComboBox cbocust; DataGrid dgord; SqlDataAdapter cmdcust,cmdord; DataSet ds; public WinDBTest2(){ Text = "View Orders"; cbocust = new ComboBox(); cbocust.DropDownStyle = ComboBoxStyle.DropDownList; cbocust.Dock = DockStyle.Top; //fix on the top cbocust.SelectedIndexChanged += new EventHandler(cbocust_SelectedIndexChanged); Controls.Add(cbocust); dgord = new DataGrid(); dgord.ReadOnly = true; dgord.Dock = DockStyle.Fill; //grid occupies remaining area of form 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"); } private void cbocust_SelectedIndexChanged(object sender, EventArgs ev){ DataRowView drv = (DataRowView)cbocust.SelectedItem;

string cust = (string)drv["cust_id"]; DataView dv = new DataView(ds.Tables["orders"]); dv.RowFilter = "cust_id = '"+cust+"'"; dgord.DataSource = dv; } [STAThread] public static void Main(string[] args){ Application.Run(new WinDBTest2()); } } 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.cs) 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) using System; using System.Windows.Forms; using System.Drawing; public class ThreeDLabel : Control{ public event EventHandler Activated; private Color shadowColor; public ThreeDLabel(){ Text = "3DLabel"; shadowColor = Color.White; } public Color ShadowColor{ get{ return shadowColor; } set{ shadowColor = value; Refresh(); } } public override string Text{ set{ base.Text = value; Refresh();

} } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); float x = ClientRectangle.X; float y = ClientRectangle.Y; e.Graphics.DrawString(Text, Font, new SolidBrush(shadowColor), x, y); e.Graphics.DrawString(Text, Font, new SolidBrush(ForeColor), x+1, y+1); } protected override void OnResize(EventArgs e){ Refresh(); } protected override void OnDoubleClick(EventArgs e){ if(Activated != null) Activated(this,e); } } Compile: csc /t:library threedlbl.cs The program below (thdlbltest.cs) tests the above control using System; using System.Windows.Forms; using System.Drawing; class ThreeDLabelTest : Form{ private ThreeDLabel tdlheader; public ThreeDLabelTest(){ 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"; tdlheader.Size = new Size(160,30); tdlheader.Location = new Point(120,20); tdlheader.Activated += new EventHandler(tdlheader_Activated); Controls.Add(tdlheader);

} private void tdlheader_Activated(object sender, EventArgs ea){ MessageBox.Show("You activated the label","Button Clicked", MessageBoxButtons.OK ,MessageBoxIcon.Information); } [STAThread] public static void Main(){ Application.Run(new ThreeDLabelTest()); } } Compile: csc /r:threedlbl.dll thdlbltest.cs

Chapter 11: Web Programming with ASP.NET


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. . 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 given below assume existence of a virtual directory called myweb on IIS5.0 executing on the localhost. This directory is also assumed to contain sales.mdb. 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="C#" %> <html> <head> <title>simplehello.aspx</title> </head> <body> <center> <form> <b>Name:</b> <input name="myname"> <input type="submit" value="Submit"> </form> <% string name = Request.QueryString["myname"]; if(name != null){ if(name =="") name = "Visitor"; %> <h1>Hello <%=name%> </h1> <%}%>

</center> </body> </html> View the page: http://localhost/myweb/simplehello.aspx The page begins with a page directive identifying the language used for server-side scripting. The C# 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="C#" runat = "server"> private void SubmitBtn_Click(object sender, EventArgs ev){ string name = NameTxt.Text; if(name == "") name = "Visitor"; HelloLbl.Text = "Hello "+name; } </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 (mywebctl.cs) which displays the value of its text property in rainbow colors.

sing System; using System.Web; using System.Web.UI; using System.Drawing; namespace MyWebControls{ public class RainbowLabel:Control{ private static Color[] colors = {Color.Violet, Color.Indigo, Color.Blue, Color.Green, Color.Yellow, Color.Orange, Color.Red}; private string _text; public string Text{ get{return _text;} set{_text = value;} } private static string ToHtml(Color c){ return "#"+c.R.ToString("X2")+c.G.ToString("X2") +c.B.ToString("X2"); } protected override void Render(HtmlTextWriter page){ if(_text == null) return; int col = 0; foreach(char ch in _text){ if(Char.IsWhiteSpace(ch))page.Write(ch); else{ page.Write("<font color='{0}'>{1}</font>",ToHtml(colors[col]),ch); if(++col == 7)col=0; } } } } } Compile mywebctl.cs 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="C#" runat="server"> private void GoBtn_Click(object sender,EventArgs ev){ DataBind(); // resolve all data-binding expressions in the current page } </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="C#" runat="server"> public String UserId { get { return UsrTxt.Text; } } public String Password { get { return PwdTxt.Text; } } </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"/> <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. Given below is a customer login page (login.aspx) which uses above pagelet. <%@ Register TagPrefix="My" TagName="Login" Src="login.ascx" %> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.OleDb" %> <html> <head> <title>login.aspx</title> <script language="C#" runat="server"> private void Page_Load(object sender, EventArgs ev){ // Page.IsPostBack returns true if the page was reloaded // due to the users submission of the form if(!Page.IsPostBack) return; string constr="Provider=Microsoft.Jet.OLEDB.4.0;Data Source="+Server.MapPath("sales.mdb"); OleDbConnection cn = new OleDbConnection(constr); string sql = String.Format("select count(*) from customer where + cust_id='{0}' and pwd='{1}'", CustLogin.UserId, CustLogin.Password); OleDbCommand cmd = new OleDbCommand(sql, cn); cn.Open(); try{ OleDbDataReader dr=cmd.ExecuteReader(); dr.Read(); int n = dr.GetInt32(0); dr.Close(); if(n==1){ Session["custid"]=CustLogin.UserId; Response.Redirect("order.aspx"); } else ResultLbl.Text = "Login Failed"; }catch(Exception e){

ResultLbl.Text = e.Message; }finally{ cn.Close(); } } </script> </head> <body> <center> <h2>Login Form</h2> <form action="login.aspx" method="post" runat="server"> <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 the UserId is stored in the Session object (the Session object allows data to be stored on a per client session basis) with a key custid and the user is redirected to page order.aspx given below. <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.OleDb" %> <html> <head><title>order.aspx</title> <script language="C#" runat="server"> private OleDbConnection cn; private void Page_Load(object sender, EventArgs ev){ string constr="Provider=Microsoft.Jet.OLEDB.4.0;Data Source="+Server.MapPath("sales.mdb"); cn = new OleDbConnection(constr); if(Page.IsPostBack) return; OleDbDataAdapter cmd = new OleDbDataAdapter( "select pno from product", cn); DataSet ds = new DataSet(); cmd.Fill(ds,"products"); ProdLst.DataSource = ds.Tables["products"]; ProdLst.DataTextField="pno"; ProdLst.DataBind(); } private void SubmitBtn_Click(object sender, EventArgs ev){ string cust = (string)Session["custid"];

if(cust == null){ Response.Redirect("login.aspx"); return; } OleDbCommand cmd = new OleDbCommand(); cmd.Connection = cn; cn.Open(); cmd.Transaction = cn.BeginTransaction(); int ordid; try{ cmd.CommandText = "update ord_ctl set ord_no = ord_no + 1"; cmd.ExecuteNonQuery(); cmd.CommandText = "select ord_no from ord_ctl"; ordid = (int) 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 = Int32.Parse(ProdLst.SelectedItem.Text); cmd.Parameters.Add("qty", OleDbType.Integer).Value = Int32.Parse(QuantTxt.Text); cmd.ExecuteNonQuery(); cmd.Transaction.Commit(); ResultLbl.Text = String.Format("Order Number: {0}",ordid); }catch(Exception e){ cmd.Transaction.Rollback(); ResultLbl.Text = "Order Failed"; } cn.Close(); } </script></head> <body><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></p> <p><a href="logout.aspx">Logout</a></p> </center></body> </html> When the user visits 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 the list is Page_Load returns without repopulating ProdList) the customer id is obtained from the Session object and the order is placed. 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 in a DataGrid and logout.aspx which destroys the customers session. Given below is the ASP code for view.aspx. <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.OleDb" %> <html> <head> <title>view.aspx</title> <script language="C#" runat="server"> private void Page_Load(object sender, EventArgs ev){ string cust = (string)Session["custid"]; if(cust == null){ Response.Redirect("login.aspx"); return; } string constr="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" +Server.MapPath("sales.mdb"); OleDbConnection cn = new OleDbConnection(constr); string sql=String.Format("select ord_no,ord_date,pno,qty from ord + where cust_id='{0}'",cust);

OleDbDataAdapter cmd = new OleDbDataAdapter(sql, cn); DataSet ds = new DataSet(); cmd.Fill(ds,"orders"); OrdGrid.DataSource = ds.Tables["orders"]; OrdGrid.DataBind(); } </script> </head> <body> <center> <h2>Orders of <%=Session["custid"]%></h2> <asp:DataGrid id="OrdGrid" runat="server"/> </center> </body> </html> The code for logout.aspx is as follows. <script language="C#" runat="server"> private void Page_Load(object sender, EventArgs ev){ Session.Abandon(); //destroy the session Response.Redirect("login.aspx"); } </script> 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="C#" Class="PriceService"%> using System; using System.Data; using System.Data.OleDb; using System.Web.Services;

[WebService Namespace="http://km.hussain.com/"] public class PriceService : WebService{ [WebMethod] // make this method available to clients public double GetPrice(int pno, int qty){ OleDbConnection cn = new OleDbConnection( "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" +Server.MapPath("sales.mdb")); string sql = String.Format("select price from product where + pno={0}",pno); OleDbCommand cmd = new OleDbCommand(sql, cn); cn.Open(); double price = -1; try{ OleDbDataReader dr = cmd.ExecuteReader(); if(dr.Read()) price = qty * dr.GetDouble(0); dr.Close(); if(qty >= 50) price = 0.95 * price; }finally{ cn.Close(); } return price; } } 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 http://localhost/myweb/priceservice.asmx The above command will create PriceService.cs which can be compiled to a dll. This dll must be stored 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 applications bin subdirectory) and GetPrice method can be invoked. Given below (priceservicetest.cs) is a simple program which consumes the above web-service using System class PriceServiceTest{ public static void Main(string[] args){ int pno = Int32.Parse(args[0]); int qty = Int32.Parse(args[1]); PriceService proxy = new PriceService(); Console.WriteLine("Price : {0}",proxy.GetPrice(pno,qty)); }

} Compile: csc /r:priceservice.dll priceservicetest.cs Given below (prices.aspx) is an ASP.NET client for PriceService <html> <head><title>prices.aspx</title> <script language="C#" runat="server"> private void SubmitBtn_Click(object sender, EventArgs ev){ PriceService ws = new PriceService(); int pno = Int32.Parse(PnoTxt.Text); int qty = Int32.Parse(QtyTxt.Text); double price = ws.GetPrice(pno,qty); if(price < 0) PriceLbl.Text = "No such product"; else PriceLbl.Text = String.Format("{0:#.00}",price); } </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" /> </tr><tr> <td>Quantity:</td><td><asp:TextBox id="QtyTxt" runat="server" /> </tr><tr> <td>Total Price:</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>

Chapter 12: Interop and Enterprise Services


Code written in C# can easily work with the code written in some other .NET compliant language. We have already seen (in the end of Chapter 1) how a class composed in C# can be instantiated in VB.Net. It is also possible to subclass a class written in C# in some other language. In example below (basicaccount.vb) we derive a Visual Basic class called BasicAccount from class Project.Bank.BankAccount implemented in C# (see Chapter 3). Imports Project.Bank Imports System Public Class BasicAccount Inherits BankAccount A constructor which takes an optional initial balance Public Sub New(Optional bal As Double=0) If bal >= 0 Then balance = bal End Sub Public Overrides Sub Withdraw(amount As Double) If amount > balance Then Throw New InsufficientFundsException() balance = balance - amount End Sub End Class Module Test Public Sub Main() Dim acc As BasicAccount = New BasicAccount(12000) Console.Write("Enter the amount to withdraw: ") Dim amt As Double = Double.Parse(Console.ReadLine()) Try acc.Withdraw(amt) Catch e As InsufficientFundsException Console.WriteLine("Withdrawl failed due to lack of funds") End Try Console.WriteLine("Current Balance: {0}",acc.Balance) End Sub End Module Compile the above sample using: vbc /r:bank.dll basicaccount.vb (bank.dll was created in Chapter 3). Execute basicaccount.

The .Net framework also allows 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 C# applications. Lets first see how to call functions exported from native dll from a C# program. The C# program (callnatdll.cs) below invokes _getch() function exported by msvcrt.dll to input a character from the keyboard without displaying it on the console. using System; using System.Runtime.InteropServices; using System.Text; class CallNativeDll { [DllImport("msvcrt.dll")] public static extern char _getch(); public static string ReadPassword() { StringBuilder sb = new StringBuilder(); while(true) { char c = _getch(); //input a character without echoing if(c=='\r') { Console.WriteLine(); break; } Console.Write("*"); //display a * sb.Append(c); } return sb.ToString(); } public static void Main() { Console.Write("Enter Password: "); string p = ReadPassword(); if(p=="tiger") Console.WriteLine("SUCCESS"); else Console.WriteLine("FAILURE"); } } Check how _getch is redeclared in CallNativeDll class with an extern modifier. This method is also marked with DllImportAttribute through which the path of the dll is passed. It is also possible to receive callbacks from native code. Our next example (natcallback.cs) displays handles and names of all open windows. To do so it uses a 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.

using System; using System.Text; // for StringBuilder using System.Runtime.InteropServices; class NativeCallback { public delegate bool EnumWindowsProc(int hWnd, int lParam); [DllImport("user32.dll")] public static extern int EnumWindows(EnumWindowsProc ewp, int lParam); [DllImport("user32.dll")] public static extern int GetWindowText(int hWnd, StringBuilder title, int size); public static bool PrintWindow(int hWnd, int lParam) { StringBuilder title = new StringBuilder(80); GetWindowText(hWnd, title, 80); Console.WriteLine("{0}\t{1}",hWnd,title); return true; } public static void Main() { EnumWindowsProc ewp = new EnumWindowsProc(PrintWindow); EnumWindows(ewp, 0); } } 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 C# application with help of tlbimp.exe utility. This utility generates the necessary C# wrapper class for a given COM component from its type library. The code below (callcom.cs) plays a media (.wav, .med, .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. using System; using QuartzTypeLib;

class CallCom { public static void Main(string[] args) { FilgraphManager fm = new FilgraphManager(); fm.RenderFile(args[0]); fm.Run(); int ecode; fm.WaitForCompletion(-1,out ecode); // -1: wait indefinitely Console.WriteLine("TERMINATED WITH CODE {0}",ecode); } } Compile: csc /r:quartztypelib.dll callcom.cs 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 you'd need in unmanaged code. Given below (bizcalc.cs) 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 double OriginalCost; public float AnnualDepreciationRate; public double GetPriceAfter(int period){ double price = OriginalCost; for(int n = 1; n <= period; n++){ price = price * (1 - AnnualDepreciationRate/100); } return price; } } } Use following steps to build and deploy the above .NET class as a COM component: 1. Create a strong name key file using sn.exe utility sn k mykey.snk

2.

3. 4. 5.

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. Compile bizcalc.cs to create a .net module and link the module with the above key file using al.exe to generate a strong named library csc /t:module bizcalc.cs al /keyf:mykey.snk /out:bizcalc.dll bizcalc.netmodule 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 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. 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. 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. Given below is the source code for Order component (ordcomp.cs) which relies on COM+ transaction processing (explanation follows the code).

using System; using System.Reflection; using System.Data; using System.Data.SqlClient; using System.EnterpriseServices; [assembly: ApplicationName("OrderComponent")] [assembly: AssemblyKeyFile("ordcomp.snk")] namespace OrderComponent{ [Transaction(TransactionOption.Required)] public class OrderManager : ServicedComponent{ [AutoComplete] public int PlaceOrder(string customer, int product, int quantity){ SqlConnection cn = new SqlConnection("server=localhost;uid=sa;pwd=;database=sales"); SqlCommand cmd = new SqlCommand(); cmd.Connection = cn; cn.Open(); int ordid = -1; try{ cmd.CommandText = "update ord_ctl set ord_no = ord_no + 1"; cmd.ExecuteNonQuery(); cmd.CommandText = "select ord_no from ord_ctl"; int ordno = (int) cmd.ExecuteScalar(); //Unlike OleDbCommand, under SqlCommand the parameters must be named cmd.CommandText = "insert into ord values(@ord_no,@ord_date,@cust_id,@pno,@qty)"; cmd.Parameters.Add("@ord_no", SqlDbType.Int).Value = ordno; 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(); ordid=ordno; }finally{ cn.Close(); } return ordid;

} } } Here are important points to note about the above component class. A COM+ application must be identified with a suitable name. The name is provided using assembly: 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 in the code using assembly: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 OrderManager 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. Finally this class contains PlaceOrder() method which is marked with AutoComplete attribute. This attribute indicates that when the method completes without any exception the transaction must be commited. In case of any exception the transaction will be rolledback Compile the above class: sn k ordcomp.snk csc /t:library ordcomp.cs Given below is a client program(ordclient.cs) which uses the above component. using OrderComponent; using System; class OrderClient{ public static void Main(string[] args){ OrderManager om = new OrderManager(); int ordid = om.PlaceOrder(args[0], Int32.Parse(args[1]), Int32.Parse(args[2])); if(ordid >= 0) Console.WriteLine("Order placed, Order No: {0}",ordid); } } Compile the program: csc /r:ordcomp.dll ordclient.cs

When this program is executed for the first time, CLR will automatically register the OrderComponent. Registration occurs only once for a particular version of an assembly. The System.Web.Mail namespace contains classes that enable you to construct and send emails using the SMTP mail service built into Microsoft Windows 2000. The program below (mailtest1.cs) demonstrates this feature. It accepts the host-name of the smtp server and senders email address from command line. The receivers email address, subject and the content of the mail are accepted at run time and the email is sent. using System; using System.Text; using System.Web.Mail; class MailTest1{ public static void Main(string[] args){ SmtpMail.SmtpServer = args[0]; // defaults to localhost MailMessage msg = new MailMessage(); msg.From = args[1]; Console.Write("Mail to: "); msg.To = Console.ReadLine(); Console.Write("Subject: "); msg.Subject = Console.ReadLine(); StringBuilder body = new StringBuilder(); Console.WriteLine("Type message (enter Ctrl+Z to end)"); string text; while((text = Console.ReadLine()) != null) body.Append(text); msg.Body = body.ToString(); try{ SmtpMail.Send(msg); Console.WriteLine("Message delivered"); }catch(Exception e){ Console.WriteLine(e.Message); } } } Inorder to curb spamming, most of the SMTP servers currently available on web will not allow mailing as demonstrated in above program. These servers require you to authorize first with their corresponding POP servers with a proper user name and password. Unfortunately .NET does not provide any facility to communicate with POP server. The program below (mailtest2.cs) uses TcpClient to connect to the POP server for authorization purpose before sending a mail through the corresponding SMTP Server. This program accepts host-name of smtp server, host-name of pop server, user name,

password and the senders email address from command line. Receivers email address. subject and content of the mail are accepted at runtime. using System; using System.IO; using System.Text; using System.Net.Sockets; using System.Web.Mail; class MailTest2{ public static bool Authorize(string svr, string usr, string pwd){ TcpClient pop = new TcpClient(svr, 110); // default port of pop server is 110 String response; StreamReader sr = null; StreamWriter sw = null; try{ Stream s = pop.GetStream(); sr = new StreamReader(s); sw = new StreamWriter(s); sw.AutoFlush = true; sw.WriteLine("USER "+usr); // send user name response = sr.ReadLine(); if(response[0] == '-') return false; // response begining with - indicates an error sw.WriteLine("PASS "+pwd); // send password response = sr.ReadLine(); if(response[0] == '-') return false; sw.WriteLine("QUIT"); // exit }finally{ if(sw != null) sw.Close(); if(sr != null) sr.Close(); pop.Close(); } return true; } public static void Main(string[] args){ if(!Authorize(args[1], args[2], args[3])){ Console.WriteLine("Authorization failed"); return; } SmtpMail.SmtpServer = args[0]; MailMessage msg = new MailMessage(); msg.From = args[4]; Console.Write("Mail to: ");

msg.To = Console.ReadLine(); Console.Write("Subject: "); msg.Subject = Console.ReadLine(); StringBuilder body = new StringBuilder(); Console.WriteLine("Type message (press Ctrl+Z to end)"); string text; while((text = Console.ReadLine()) != null) body.Append(text); msg.Body = body.ToString(); try{ SmtpMail.Send(msg); Console.WriteLine("Message delivered"); }catch(Exception e){ Console.WriteLine(e.Message); } } } 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. Use your computer management console to create a private transactional queue named ordq. This queue can be referenced as .\private$\ordq. The program below (sendmsg.cs) sends two string messages to ordq. class SendMessage{ public static void Main(string[] args){ MessageQueue queue = new MessageQueue(".\\private$\\ordq"); MessageQueueTransaction mqtx=new MessageQueueTransaction(); mqtx.Begin(); try{ queue.Send(args[0], mqtx); queue.Send(args[1], mqtx); mqtx.Commit(); }catch(Exception e){ mqtx.Abort(); Console.WriteLine("Messaging failed: "+e.Message); }

queue.Close(); } } We first create a MessageQueueTransaction instance and we send two messages passed through command line arguments to the message queue. 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 wont be sent. You can use MessageQueueTransaction only with transactional queues. The next program (receivemsg.cs) polls the ordq queue and displays the messages sent to it. using System; using System.Messaging; class ReceiveMessages{ public static void Main(string[] args){ MessageQueue queue = new MessageQueue(".\\private$\\ordq"); queue.Formatter = new XmlMessageFormatter(new String[] {"System.String"}); while(true){ Message msg = queue.Receive(); //blocks if no message is currently available. Console.WriteLine(msg.Body); } } } 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. To put it all together, create and populate table called stock in your SQL Servers sales database using following sql commands. The qty field of stock table contains the number of items available in the stock for each product. The check constraint forces the condition that the qty field must assume only non-negative values. CREATE TABLE STOCK (PNO INT PRIMARY KEY REFERENCES PRODUCT(PNO),

QTY INT NOT NULL CHECK (QTY >= 0)) 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) The ASP.Net page (asptranstest.aspx) below starts a new transaction and uses OrderComponent to place an order. Then it sends a message to ordq, update stock table and finally send a mail to the customer. If everything goes well ContextUtil.SetComplete() is invoked which commits the transaction (the database permanently updated and message is sent to the queue). In case of an exception ContextUtil.SetAbort() is invoked which rollbacks the transaction (the database is not updated and no message is sent to the queue). <%@ Page Transaction="RequiresNew" %> <%-- start a new transaction when page is invoked --%> <%@ Assembly Name="System.Messaging" %> <%@ Import Namespace="OrderComponent" %> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %> <%@ Import Namespace="System.Messaging" %> <%@ Import Namespace="System.Web.Mail" %> <%@ Import Namespace="System.EnterpriseServices"%> <html> <head> <title>asptranstest.aspx</title> <script language="C#" runat="server"> private SqlConnection cn; private void Page_Load(object sender, EventArgs ev){ string constr="server=localhost;database=sales1;uid=sa;pwd="; cn = new SqlConnection(constr); if(Page.IsPostBack) return; SqlDataAdapter cmd = new SqlDataAdapter("select pno from product", cn); DataSet ds = new DataSet(); cmd.Fill(ds,"products"); ProdLst.DataSource = ds.Tables["products"]; ProdLst.DataTextField="pno"; ProdLst.DataBind(); } private void SubmitBtn_Click(object sender, EventArgs ev){ OrderManager om = new OrderManager(); string customer = CustTxt.Text; int product=Int32.Parse(ProdLst.SelectedItem.Text); int quantity=Int32.Parse(QuantTxt.Text);

try{ //PlaceOrder is invoked within the transaction context of current page int ordid = om.PlaceOrder(customer, product, quantity); MessageQueue mq = new MessageQueue(".\\private$\\ordq"); String msg = String.Format("Customer {0} ordered {1} piece/s of product {2}", customer, quantity, product); //msg is sent to ordq within the transaction context of current page mq.Send(msg); string sql = String.Format("update stock set qty=qty-{0} where pno={1}", quantity, product); SqlCommand cmd = new SqlCommand(sql, cn); cn.Open(); try{ cmd.ExecuteNonQuery(); //will cause an exception if qty goes below zero cmd.CommandText = String.Format("select email from customer where cust_id='{0}'", customer); MailMessage mail = new MailMessage(); mail.To = (string) cmd.ExecuteScalar(); mail.From = "ordermanager@yourserver.com"; mail.Subject = "Order Confirmed"; mail.Body = String.Format( "Your order ({0}) for {1} piece/s of product {2} has been confirmed", ordid, quantity, product); SmtpMail.Send(mail); }finally{ cn.Close(); } ContextUtil.SetComplete(); // commit the transaction ResultLbl.Text = "Order No: "+ordid; }catch(Exception e){ ContextUtil.SetAbort(); // rollback the transaction ResultLbl.Text = "Order Failed: "+e.Message; } } </script> </head> <body> <center> <h2>Order Entry Form</h2> <table border="0" bgcolor="#c9c9c9"> <form action="order.aspx" method="post" runat="server">

<tr> <td><b>Customer ID: </b></td> <td><asp:TextBox id="CustTxt" runat="server"/></td> </tr> <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> </center> </body> </html>

Potrebbero piacerti anche