-6-

 

User Controls

 

In this chapter, you will learn a little more about C#, so that you can incorporate it while writing your own controls.

 

We will create our own controls in a slightly different manner than what was done earlier. To do so, we create:

 

     a.aspx in c:\inetpub\wwwroot subdirectory.

     b.cs in c:\inetpub\wwwroot\bin subdirectory.

 

a.aspx

<%@ Register TagPrefix="ttt" Namespace="nnn" Assembly="c"%>

<html>

<body>

<form method="POST" action="s.aspx" runat=server>

<ttt:zzz runat=server/>

</form>

</body>

</html>

 

b.cs

using System.Web.UI;

namespace nnn

{

public class zzz : Control

{

}   

}

 

You can compile the above file named b.cs, by giving the following command:

 

>csc /target:library  /out:c.dll b.cs /r:System.Web.dll

 

The compiler creates a file called c.dll in the inetpub\wwwroot\bin subdirectory.

 

In the above C# program, we have simply created a class zzz that is derived from the class Control. The class Control belongs to the System.Web.UI namespace. We do not have any other code in our program. Thus, we see a blank screen.

 

The file a.aspx will remain the same for the initial programs.

 

b.cs

using System.Web.UI;

namespace nnn

{

public class zzz : Control

{

protected override void Render(HtmlTextWriter o)

{

o.Write("<b>hell</b>");

}

}   

}

 

We now have added a function called Render that accepts a parameter 'o', which is an instance of the class HtmlTextWriter, and its return type is void. The modifiers 'override' and 'protected', shall be explained to you, in a while. Also, we now call the Write function in the class HtmlTextWriter, and give it a string, <b>hell</b>.

 

When you load the aspx file, you will see 'hell' displayed in bold on your screen.

 

o is an instance of HtmlTextWriter. Now that we have seen some output, let us understand the working of the program.

 

In the file a.aspx, we have used a directive called Register (which has been explained in one of the earlier chapters), and created our own tag prefix called ttt. Along with these details, we have also declared a namespace called nnn. Our custom tag starts with the prefix ttt, followed by a colon and then the word zzz. By merely furnishing this information, we see the word 'hell' displayed in the browser window.

 

While executing the aspx file, when the web server comes across a custom tag, it first looks for the directory that is running the aspx file. On receiving the directory, it then looks into a subdirectory to locate another subdirectory called bin. In our case, bin is located in the c:\inetpub\wwwroot subdirectory. Thereafter, all the dlls in the bin subdirectory, are loaded into memory.

 

Every dll that is loaded into memory, contains information or metadata that consists of the classes and namespaces existing in it. The web server will check every file for the presence of a class named zzz (ttt: is followed by zzz) within the nnn namespace (This namespace is given with the Register directive). The file c.dll meets the match.

 

The Web server is programmed to call a function by the name of Render, which takes an HtmlTextWriter object as a parameter in the acquired dll. Using this object, text is rendered to the browser.

 

Thus, a user-defined tag is nothing but a class with the same name, but which derives from the class Control. Also, every user-defined tag must contain a function called Render.

 

Now, let us get back to C# and understand what the word 'protected' signifies.

 

z.cs

public class zzz {

public static void Main()

{

yyy a = new yyy();

a.abc();

}

}

public class yyy {

private void abc()

{

}

}

 

Output

z.cs(6,1): error CS0122: 'yyy.abc()' is inaccessible due to its protection level

 

If you compile the above code as

 

>csc z.cs

 

an error will be generated.

 

This is because the function abc is tagged with the modifier private. The private modifier allows only the members of the same class to access the function. As a result, only members of the class yyy are allowed to access the function abc. Therefore, the  object 'a' of type yyy in the class zzz, is restricted any access to private members of the class yyy.

 

Thus, private is called an access modifier, since it prohibits access to members of a class. Private is the most restrictive access modifier, while public is the most tolerant. These access modifiers do not apply to members of the same class.

 

z.cs

public class zzz

{

public static void Main()

{

xxx a = new xxx();

a.abc();

}

}

public class yyy

{

}

public class xxx : yyy

{

protected void abc()

{

}

}

 

Output

z.cs(6,1): error CS0122: 'xxx.abc()' is inaccessible due to its protection level

 

The same error gets repeated again with the protected modifier as a protected member is accessible only to a derived class object.

 

Had any member of class yyy tried to access the functions in class xxx, which has been derived from the class yyy, no errors would have been generated. A protected modifier signifies that only derived classes are allowed access its members. Thus, a protected modifier lies in the midst of the two extremes, private and public.

 

z.cs

public class zzz

{

public static void Main()

{

xxx a = new xxx();

yyy b = new yyy();

xxx c = new yyy();

yyy d = new xxx();

}

}

public class yyy

{

}

 

public class xxx

{

}

 

 

Output

z.cs(7,9): error CS0029: Cannot implicitly convert type 'yyy' to 'xxx'

z.cs(8,9): error CS0029: Cannot implicitly convert type 'xxx' to 'yyy'

 

C# is extremely stern about equating different data types to each other. Thus, we cannot equate a yyy object with an xxx object. The entities on both sides of an 'equal to' sign must have the same data type, or else an error stating 'cannot covert to', is generated.

 

z.cs

public class zzz

{

public static void Main()

{

xxx a = new xxx();

yyy b = new yyy();

xxx c = new yyy();

yyy d = new xxx();

}

}

public class yyy

{

}

public class xxx : yyy

{

}

 

Output

z.cs(7,9): error CS0029: Cannot implicitly convert type 'yyy' to 'xxx'

 

Now, the error vanishes because, class xxx is derived from class yyy. As a result, class xxx now comprises of two classes, a yyy class and an xxx class. Therefore, the statement xxx c = new yyy() does not raise any error.

 

As class xxx derives from class yyy, we call xxx a higher class, since it contains the lower class yyy, and more. We can thus, have a higher class on the right hand side of the 'equal to' sign and a lower class on the left hand side. Technically speaking, we can use a derived class on the right hand side, and a base class on the left hand side of an 'equal to' sign.

 

If they are placed the other way around, that is, if the class yyy is on the right hand side and the xxx class is on the left hand side, an error is generated. A derived class cannot be made equal to a base class.

 

z.cs

public class zzz {

public static void Main()

{

yyy a = new xxx();

a.abc();

a.pqr();

}

}

public class yyy {

public virtual void abc()

{

System.Console.WriteLine("yyy abc");

}

public virtual void pqr()

{

System.Console.WriteLine("yyy pqr");

}

}

public class xxx : yyy

{

public override void abc()

{

System.Console.WriteLine("xxx abc");

}

public new void pqr()

{

System.Console.WriteLine("xxx pqr");

}

}

 

Output

xxx abc

yyy pqr

 

We have two functions in class yyy namely, abc and pqr. We have added a modifier named 'virtual' to these functions. This modifier permits the derived classes to override the functions abc and pqr. Without the word virtual, the derived classes cannot override functions in the base class.

 

The derived class has the option of overriding the functions of the base class, i.e. creating a new function with the same names which may or may not bear any relation to the functions of the base class.

 

Adding a modifier 'new' to the virtual function in the derived class, breaks all links with the function having the same name in the base class. Thus, the base class pointer has no access to the derived class functions.

 

The override modifier overrides the function of the base class. 'a'  is a pointer to a yyy class and it is initialized to the derived class xxx. Hence, when we call a.abc(), the function abc of the derived class xxx will be called.

 

Thus, in the case of a virtual function, C# goes a step further and checks the run time data type of the object, and not the compile time data type. If the function in the derived class has an override modifier, as in the case of the function abc, the function gets called from the derived class. In case of new, as in the case of the function pqr, it becomes a new function. And thus, it ignores the existence of such a function in the class xxx. The only option available is to call the function from the class yyy itself.

 

There is a function called Render in the class Control. If we want our function Render to be called in class zzz, we have to use the override modifier. Otherwise, the function will be called from the class Control instead.

 

z.cs

public class zzz

{

public static void Main()

{

yyy a = new xxx();

a.abc();

}

}

public class yyy

{

public virtual void abc()

{

System.Console.WriteLine("yyy abc");

}

}

public class xxx : yyy

{

protected override void abc()

{

System.Console.WriteLine("xxx abc");

}

}

 

Output

z.cs(18,25): error CS0507: 'xxx.abc()': cannot change access modifiers when overriding 'public' inherited member 'yyy.abc()'

 

We get the above error because, when we override a function of the base class, we cannot change it's access modifiers. Everything, including the modifiers, must be identical to what is present in the base class.  Since the function Render is marked as protected in the base class, we have to use the same access modifier in the class zzz also.

 

Let us get back to Controls again.

 

a.aspx

<%@ Register TagPrefix="ttt" Namespace="nnn" Assembly="c"%>

<html>

<body>

<form method="POST" action="S.aspx" runat=server>

<ttt:zzz aa="hi" bb=100 runat=server/>

</form>

</body>

</html>

 

b.cs

using System.Web.UI;

namespace nnn

{

public class zzz : Control

{

public string s;

public string aa

{

get

{

return s;

}

set

{

s = value;

}

}

public int bb = 12;

protected override void Render(HtmlTextWriter o)

{

o.Write("hell " + bb + " " + aa);

}

}   

}

 

Output

hell 100 hi

 

A property can either consist of a getset accessor or it can contain a simple instance variable. Also, it can be used in place of a variable. In the case of a property, source code can be executed. However, the user of the tag will never be exposed to the internal workings of a property and its implementation in a tag.

 

The set accessor is called before the function Render gets executed. This enforces the situation where all the properties have to be initialized first, and only then the Render function can be executed.

 

a.aspx

<%@ Register TagPrefix="ttt" Namespace="nnn" Assembly="c"%>

<html>

<body>

<form method="POST" action="S.aspx" runat=server>

<ttt:zzz aa-bb="hi" aa-cc=100 runat=server/>

</form>

</body>

</html>

 

b.cs

using System;

using System.Web;

using System.Web.UI;

namespace nnn

{

public class yyy

{

public string bb = "no";

public int cc = 200;

}

public class zzz : Control

{

yyy a = new yyy();

public yyy aa

{

get

{

return a;

}

}

protected override void Render(HtmlTextWriter o)

{

o.Write(aa.bb + " " + aa.cc);

}

}   

}

 

Output

hi 100

 

We look at the .cs file first. In the class zzz, we have created an object a, that is an instance of the class yyy. This class is created in the same namespace nnn as given along with the aspx Register directive. aa is a property that returns a yyy object in its get accessor.

Class yyy also has two members named bb and cc, which are initialized to 'no' and 200, respectively. Once a yyy object is created, using the dot syntax, we can access these members individually. In an aspx file, a minus sign is used instead of a dot, to access the members. Hence, to access the bb member in yyy, we use the statement 'aa-bb'.

 

The only difference here is in the choice of the separator, between the name of the property and the name of the member, belonging to the object that the property returns.

 

Now, we return back to C#.

 

z.cs

public class zzz

{

public static void Main()

{

int [] a;

int i;

a = new int[3];

a[0] = 1;

a[2] = 8;

a[0]++;

System.Console.WriteLine(a[0]);

i = 0;

System.Console.WriteLine(a[i]);

i = 2;

System.Console.WriteLine(a[i]);

}

}

 

Output

2

2

8

 

Whenever we want to store multiple items of the same type, we use a data type called an array. An array stores similar items together. Here, 'a' is an array that is declared, using a pair of square brackets [].

We now proceed to create an array of ints. To do so, the keyword 'new' is used along with the size of the array, which in our case is 3. The array size cannot be specified at the time of creation of the array. It must be specified along with 'new'.

 

To access the members of the array, we use the name of the array, followed by a pair of square brackets which enclose the index of the specific array variable. Thus, the first variable is a[0], the second is a[1], and so on. The counting for the array index begins with zero and not from 1.

 

An array variable can be easily used in place of a normal variable. The advantage in using arrays is that it makes the code concise. For example, when we use the form a[i], i is an integer variable holding values ranging from 0 to 2. When the value of i is 0, the name of the variable becomes a[0]. When we change the value of i to 1, the name of the variable now becomes a[1]. Thus, by changing the value of one variable, we are able to change the name of another variable, and thus, access a different value.

 

It is this feature of arrays, which makes them very useful. We are guilty of the crime of having used arrays earlier in this book, without explaining them in detail.

 

z.cs

public class zzz

{

public static void Main()

{

int [] a;

int i;

a = new int[3];

for ( i = 0; i<=2; i++)

a[i] = i*2;

System.Console.WriteLine(a.Length);

for ( i = 0; i< a.Length; i++)

System.Console.Write(a[i]);

}

}

 

 

Output

3

024

 

Arrays are ideal in a looping construct. In the first 'for' loop, we initialize the variable a[0] to 0, a[1] to 2 and a[2] to 4. Every array type has a member called Length, that returns the size of the array. In our case, the array size is 3. We can use this fact to iterate through all the members of the array.

 

z.cs

public class zzz

{

public static void Main()

{

string  [] a;

a = new string[]{"hi","bye"};

foreach ( string s  in a)

System.Console.WriteLine(s);

}

}

 

Output

hi

bye

 

We can create an array of any data type. Earlier, we created an array of type int. In this program, we have created an array of type string. We are also allowed to initialize the array members at the time of creation, by using the square brackets.

 

We can use the 'foreach' statement to iterate through the array. During each iteration, the string 's' will sequentially hold the string values in the array. Thus, in the first iteration, 's' will contain the string 'hi', and in the next iteration, it will contain the string 'bye'.

 

The 'foreach' construct is a more convenient way of iterating through an array, though we could even have used a 'for' construct. You are free to choose from any of these constructs, as per your requirement.

 

 

z.cs

public class zzz

{

public static void Main()

{

yyy a = new yyy();

a[2] = "hi";

System.Console.WriteLine(a[2]);

}

}

public class yyy

{

public string a;

public string this[int i]

{

get

{

System.Console.WriteLine("get " + i);

return a;

}

set

{

System.Console.WriteLine("set " + value + " " + i);

a = value;

}

}

}

 

Output

set hi 2

get 2

hi

 

Although, we have not created any array in the above program, we are still using the array syntax. We first create an object 'a' of type yyy. Then we use the syntax a[2] = "hi". When the program reaches this statement, C# stops for a while and looks for an indexer in our program.

 

An indexer is a property with a special name called 'this'. Since we have created such a property, C# calls the set accessor of this property and assigns a value of 2 to the parameter i.

As the return type of the property is a string, the hidden variable named 'value' contains the string 'hi'. We are supposed to simulate an array within the property. Since this has not been done, the indexer simulates an array that does not exist.

 

Let us get back to ASP+.

 

a.aspx

<%@ Register TagPrefix="ttt" Namespace="nnn" assembly="c"%>

<html>

<body>

<form method="POST" action="a.aspx" runat=server>

<ttt:zzz runat=server>

vijay mukhi

</ttt:zzz>

</form>

</body>

</html>

 

b.cs

using System;

using System.Web;

using System.Web.UI;

namespace nnn

{

public class zzz : Control

{

protected override void Render(HtmlTextWriter o)

{

if ( HasControls() )

o.Write("True <br>");

LiteralControl l = (LiteralControl) Controls[0];

o.Write(l.Text);

}

}

}

 

Output

True

vijay mukhi

 

 

In the aspx file, we have inserted the text 'vijay mukhi' within ttt, which is a newly created tag. We would like to display this text in the browser.

 

To do so, we call a function called HasControls in the Render function, which does not take any parameter.

 

Since this function is not present in our class zzz, it is taken from the Control class. The return value of true or false depends upon the presence of controls in our web page. If the page has controls, HasControls shows true, or else, it shows false.

 

The class Control has a property called Controls that returns a ControlCollection object. This class has an indexer that returns a Control object. Thus, the use of Controls[0] will return the first control on our page.

 

In our program, Controls[0] returns a LiteralControl, which is a class derived from Control. Hence, the cast operator is required.

 

The Text property in this control, displays the text given within the tag.

 

a.aspx

<%@ Register TagPrefix="ttt" Namespace="nnn" assembly="c"%>

<html>

<body>

<form method="POST" action="a.aspx" runat=server>

<ttt:zzz id="hi" runat=server>

vijay mukhi

</ttt:zzz>

<asp:Button id="bye" runat=server>

</asp:Button>

</form>

</body>

</html>

 

b.cs

using System;

using System.Web;

using System.Web.UI;

namespace nnn

{

public class zzz : Control

{

protected override void Render(HtmlTextWriter o)

{

int j = Controls.Count;

o.Write(j + "<br>");

for( int i = 0; i< j; i++)

{

if ( Controls[i] is LiteralControl )

{

LiteralControl l = (LiteralControl) Controls[i];

o.Write(l.Text + " " + l.ClientID);

}

}

}

}   

}

 

Output

1

vijay mukhi ctrl1 

 

The class ControlCollection has a member called Count, which contains a count of the number of Controls that are present on our page. The value held in this member is 1, which indicates that the Button control used outside the ttt tag, has not got added to our collection.

 

A 'for' loop is used to iterate through the collection. A check is performed for a LiteralControl; thereafter, two members of this control, Text and ClientID, are displayed. The member ClientID shows crtl1, even though it has been assigned an id of 'hi'. These controls are called child controls.

 

Composite Controls

 

a.aspx

<%@ Register TagPrefix="ttt" Namespace="nnn" assembly="c"%>

<html>

<script language="C#" runat=server>

void abc(Object sender, EventArgs e)

{

hi.aa++;

}

</script>

<body>

<form method="POST" action="a.aspx" runat=server>

<ttt:zzz id="hi" runat=server/>

<asp:button text="Add" OnClick="abc" runat=server/> 

</form>

</body>

</html>

 

b.cs

using System;

using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;

namespace nnn

{

public class zzz: Control, INamingContainer

{

public int aa

{

get

{

return Int32.Parse(((TextBox)Controls[1]).Text);

}

set

{

((TextBox)Controls[1]).Text = value.ToString();

}

}

protected override void CreateChildControls()

{

this.Controls.Add(new LiteralControl("Vijay Mukhi: "));

TextBox b = new TextBox();

this.Controls.Add(b);

b.Text = Controls.Count.ToString();

}

}   

}

 

Output

Vijay Mukhi:  

 

On loading the aspx file, we see ' 2' in the textbox and then, a button labeled Add is displayed.

 

In the second program, we have explained the function called Render, which is called when you have a User Defined Control. In the same vein, if a function by the name of CreateChildControl is present in the file, it too gets called. In this function, we can add as many controls as we desire.

 

Every function in a class is given an extra parameter called 'this'. This parameter refers to an instance of the class that the function resides in. Thus, truly speaking, the function Render is called with two parameters and not one. The code for it is as follows:

 

Render ( zzz this, HtmlTextWriter o).

 

Therefore, this.Controls.Add or Controls.Add means the same thing. Whenever you see this syntax, you can safely ignore it.

 

In the function CreateChildControls, we have added a Literal Control object, and created a Literal Control object on the same line. The syntax Add introduces one more control to the Controls collection, and hence, we can proudly see 'Vijay Mukhi' displayed in the browser window.

 

To add a TextBox to the controls collection, we simply create a TextBox object and use the same Add function from the Controls class. Hence, the count 'property' shows the number 2 in the editbox, because we have initialized the Text member to this value.

 

In the class zzz, we have a property called aa which is given the id of 'hi' in the aspx file. The property can now be accessed by writing hi.aa. The syntax consists of the id name, followed by name of the property.

Within this property, the value contained in the TextBox is incremented by one, every time. The set accessor uses value, which is the hidden variable, to change the Text member. In the get accessor, we simply return the value stored in the Text member. The cast operators are mandatory.

 

We also assume that the TextBox is the second member of the Controls Collection and hence, we use Controls[1] to access it. The user cannot be made aware of the fact that the controls were created in the function CreateChildControls.

 

The HTML file simply shows it as HTML tags. We are thus creating new controls by combining existing controls. Such controls are called Composite controls, and the technique used is called class composition. The User Controls created earlier, were present in a file with the .ascx extension which is a text file, whereas now, we have placed the control in an assembly or a dll file.

 

In every other sense, composite controls are similar to User Controls and use the same ASP.NET syntax.

 

Zzz is derived from the interface INamingContainer that does not contain any methods at all. We use it merely as a tagging interface. When the class implements the above interface, every new child control that is created, is given a unique id in ASP+. This concept is important because we may have many instances of our control on the same page; therefore, their ids need to be different. Zzz does not need to override the Render method, because the child controls contain their own rendering logic or code.

 

You can also expose a single property that internally uses multiple properties from multiple controls.

 

But before that, let us first learn all about events and delegates.

 

z.cs

using System;

delegate void ddd();

class zzz

{

public void abc()

{

Console.WriteLine("abc " );

}

public static void Main()

{

zzz z = new zzz();

ddd a;

a = new ddd(z.abc);

a();

}

}

 

Output

abc

 

The delegate ddd is created by specifying the return type, the parameter types, and the reserved word delegate. Thereafter, we are allowed to declare an object 'a', which is of the delegate type ddd.

 

To instantiate a delegate object, 'new' is used along with the name of the function, z.abc, which has to be called through a delegate object named 'a'. Thus, a() will call the function abc. This is an indirect way of calling the function abc through the delegate object.

 

z.cs

using System;

delegate void ddd(string s);

class zzz {

public static void abc(string s)

{

Console.WriteLine("abc " + s);

}

public static void pqr(string s)

{

Console.WriteLine("pqr " + s);

}

public static void Main()

{

ddd a, b, c, d;

a = new ddd(abc);

b = new ddd(pqr);

c = a + b;

d = c - a;

a("one");

b("two");

c("three");

d("four");

}

}

 

Output

abc one

pqr two

abc three

pqr three

pqr four

 

This example shows us the capabilities of a delegate. We have created two delegate objects, a and b, and associated them with the functions abc and pqr, respectively. These functions are called indirectly through the delegate objects.

 

The power of a delegate gets displayed when we combine them, as in a + b . The result of this operation is stored in delegate c. Thus, the syntax c() calls both the functions abc and pqr. This is the first time that the plus sign is being used to call two functions through a delegate. The same concept could be applied to call numerous functions by using a single delegate. The delegate 'c' represents the functions abc and pqr. Subtracting 'a' from 'c', i.e. subtracting the function abc from the functions abc and pqr, results in the delegate 'd' calling only the function pqr.

 

Events

 

z.cs

public delegate void ddd();

class zzz

{

public static void Main()

{

yyy l = new yyy();

l.c += new ddd(l.abc);

xxx x = new xxx();

l.c += new ddd(x.xyz);

l.pqr();

}

}

public class yyy

{

public event ddd c;

public void pqr()

{

c();

}

public void abc()

{

System.Console.WriteLine("abc");

}

}

public class xxx

{

public void xyz()

{

System.Console.WriteLine("xyz");

}

}

 

Output

abc

xyz

 

The delegate ddd is created to call the function abc. Next, we create two objects named 'l' and 'x', that look like classes yyy and xxx, respectively. In the class yyy, we create 'c', which is an instance of a delegate ddd. Note that the return type is replaced by the word event. Hence, 'c' is now an event.

 

The event 'c' is initialized to a delegate instance, which is associated with the function abc from the same class. Thereafter, we again initialize the event c to a delegate instance, but this time, we use a different function name i.e. xyz, from the class xxx. Thus, the event 'c' is now associated with two functions. The command l.pqr() executes the event object as c(). Thus, two functions belonging to two different classes get executed together. This is the power of an event, and it is used extensively for notification purposes.

 

a.aspx

<%@ Register TagPrefix="ttt" Namespace="nnn"  assembly="c"%>

<html>

<body>

<form method="POST" action="a.aspx" runat=server>

<ttt:zzz id="hi" runat=server/>

</form>

</body>

</html>

 

b.cs

using System;

using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;

namespace nnn

{

public class zzz: Control,INamingContainer

{

public int aa

{

get

{

return Int32.Parse(((TextBox)Controls[1]).Text);

}

set

{

((TextBox)Controls[1]).Text = value.ToString();

}

}

protected override void CreateChildControls()

{

this.Controls.Add(new LiteralControl("Vijay Mukhi: "));

TextBox b = new TextBox();

b.Text = "0";

this.Controls.Add(b);

Button a = new Button();

a.Text = "Add";

a.Click += new EventHandler(abc);

Controls.Add(a);

}

void abc(Object s, EventArgs e)

{

aa++;

}

}   

}

 

Output

Vijay Mukhi:  

 

The above example demonstrates as to how a delegate can be used in a control. In addition to the two controls that existed previously, we have a third control, which is a button named 'b'. The text or the label of the button is Add.

 

The syntax a.Click += new EventHandler(abc) can only be used with a delegate. Click is a delegate type property in the Button class, and is similar to our delegate objects a, b and c. The EventHandler class is like our delegate ddd, which represents the function abc. Thus, every time we click on the button, the function Click() is called.  As a result, all the functions associated with the delegate, get called. Thus, function abc is called, which increments the value of aa by 1.

 

b.cs

using System;

using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;

namespace nnn

{

public class zzz: Control,INamingContainer

{

public int aa

{

get

{

return Int32.Parse(((TextBox)Controls[1]).Text);

}

set

{

((TextBox)Controls[1]).Text = value.ToString();

}

}

protected override void CreateChildControls()

{

this.Controls.Add(new LiteralControl("Vijay Mukhi: "));

TextBox b = new TextBox();

b.Text = "0";

this.Controls.Add(b);

Button a = new Button();

a.Text = "Add";

a.Click += new EventHandler(abc);

a.Click += new EventHandler(pqr);

Controls.Add(a);

}

void abc(Object s, EventArgs e)

{

aa++;

}

void pqr(Object s, EventArgs e)

{

aa++;

}

}   

}

 

Each time we click on the button, the number increases by 2. This is because we have associated the two functions abc and pqr with the delegate object name Click. We could have called numerous other functions in a similar manner.

 

The use of delegates offers a cleaner method of calling our code whenever an event takes place. A composite control can add itself to an event raised by a child control. We guarantee that our code will get called with a click on a child control, i.e. a button.

 

a.aspx

<%@ Register TagPrefix="ttt" Namespace="nnn" assembly="c"%>

<html>

<script language="C#" runat=server>

void abc(Object sender, EventArgs e)

{

hi.aa++;

Response.Write(hi.aa.ToString());

}

</script>

<body>

<form method="POST" action="a.aspx" runat=server>

<ttt:zzz id="hi" Onchange="abc" runat=server/>

</form>

</body>

</html>

 

b.cs

using System;

using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;

namespace nnn

{

public class zzz: Control, INamingContainer

{

public event EventHandler Change;

public int aa

{

get

{

return Int32.Parse(((TextBox)Controls[0]).Text);

}

set

{

((TextBox)Controls[0]).Text = value.ToString();

}

}

protected override void CreateChildControls()

{

TextBox b = new TextBox();

b.Text = "0";

Controls.Add(b);

Button a = new Button();

a.Text = "Add";

a.Click += new EventHandler(add1);

Controls.Add(a);

}

void add1(Object sender, EventArgs e)

{

aa++;

Change(this,EventArgs.Empty);

}

}   

}

 

Output

4

 

This example once again demonstrates an event. Here, we have a tag with an id of 'hi'. Along with it, a new property called onChange is introduced, which calls the function abc when a change occurs in our user control. Thus, when anything is entered in the textbox, it  will result in a call to the function abc. This function merely increments the value of the property aa by 1, and then displays it.

 

The user control notifies ASP+ about the change, as a result of which, the code that has been written outside the control, gets called. The onChange property must be initialized to a function, if we want it to discern any change in the control. The control will simply trigger off an OnChange event, which results in a call to the function abc. An event object named Change, which is of delegate type EventHandler, is created. This happens because our property in the tag is named as OnChange.

 

A property called aa is added, which gets and sets the value in the textbox, i.e. Child control [0]. The function CreateChildControls creates a textbox, just as it did before, and a button with the label Add is also added.

 

The event object Click in the button class is associated with the EventHandler delegate, which represents the function named add1. The function add1 is executed when the button is clicked.

 

In this function, we merely add 1 to the property aa and then trigger off the Change event. This in turn, calls the function abc, since it is associated with the OnChange property in the aspx file. The program is not aware of this association, because these linkages are given in the aspx file.

 

The function abc requires two parameters, namely, a sender and an EventArgs data type. The sender in this case is the program itself. Hence, the sender and the EventArgs are empty. The statement aa++ increments the value by 1, and the Write function finally displays this value. Note that aa has been incremented twice overall.

 

Thus, it is evident that code can be called in a dll, as well as, in the aspx file. This occurs with each having no knowledge about the other's existence.

 

a.aspx

<%@ Register TagPrefix="ttt" Namespace="nnn" assembly="c"%>

<html>

<script language="C#" runat=server>

void abc(Object sender, EventArgs e)

{

if ( hi.aa < 0)

hi.aa=0;

}

</script>

<body>

<form method="POST" action="a.aspx" runat=server>

<ttt:zzz id="hi" Ond="abc" runat=server/>

</form>

</body>

</html>

 

b.cs

using System;

using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;

namespace nnn

{

public class zzz: Control, INamingContainer

{

public event EventHandler d;

public int aa

{

get

{

return Int32.Parse(((TextBox)Controls[0]).Text);

}

set

{

((TextBox)Controls[0]).Text = value.ToString();

}

}

protected void ddd()

{

d(this,EventArgs.Empty);

}

protected override void CreateChildControls()

{

TextBox b = new TextBox();

b.Text = "0";

b.TextChanged += new EventHandler(ccc);

Controls.Add(b);

Button a = new Button();

a.Text = "Add";

a.Click += new EventHandler(add1);

Controls.Add(a);

Button s = new Button();

s.Text = "Minus";

s.Click += new EventHandler(sub1);

Controls.Add(s);

}

void ccc(Object sender, EventArgs e)

{

ddd();

}

void add1(Object sender, EventArgs e)

{

aa++;

ddd();

}

void sub1(Object sender, EventArgs e)

{

aa--;

ddd();

}       

}   

}

 

Output

 

In the aspx file, we have now named the property as Ond, instead of OnChange, and it is assigned to the function abc. This function simply checks whether the property aa holds a negative number or not. If it does, then aa is set to zero. So, no matter how many times we click on the button labeled Minus, the textbox value will remain frozen at the value of 0.

 

The name of the event type has been changed from Change to 'd'. Hence, the name of the property has also been changed from OnChange to Ond. The textbox has an event called TextChanged, that calls the function ccc whenever the contents of the textbox are changed. We have also added an extra button labeled as Minus, which shall call the function sub1.

 

The three event handler functions finally call the function ddd. We also add or subtract the property aa by 1. Thus, whenever we click on the button, the function ddd finally gets called. Here, we carry out a single task i.e. call the event d(). This will result in a call to the event function, which is associated with the event d or Ond, in the aspx file. Thus, we can call the property by any name we want, provided, there is an event of the same name. But, we should not start the name of the aspx tag with the word 'On'.

 

We have encapsulated all the event handling code in the function ddd, even though it is a single function call. This is done to enable us to add a large amount of code associated with the events, in future. The textbox event handling function is not required. It comes into picture only when the user enters -100, directly into the textbox. Since a change occurs on doing so, the function abc gets called, which reverts the value back to zero.

 

State Management

 

a.aspx

<%@ Register TagPrefix="ttt" Namespace="nnn" assembly="c" %>

<html>

<script language="C#" runat=server>

void abc(Object sender, EventArgs e)

{

hi.FSize++;

}

</script>

<body>

<form method="POST" action="a.aspx" runat=server>

<ttt:zzz id="hi" FSize=1 runat=server/>

<br>

<asp:button Text="click" OnClick="abc" runat=server/>

</form>

</body>

</html>

 

b.cs

using System;

using System.Web;

using System.Web.UI;

namespace nnn

{

public class zzz: Control

{

public int FSize

{

get

{

return (int) ViewState["FSize"];

}

set

{

ViewState["FSize"] = value;

}

}

protected override void Render(HtmlTextWriter o)

{

o.Write("<font size=" + FSize + ">" + "Vijay Mukhi"+ "</font>");

}

}   

}

 

Output

 

Vijay Mukhi

 

Vijay Mukhi

 

View Source

<html>

<body>

<form name="ctrl2" method="POST" action="a.aspx" id="ctrl2">

<input type="hidden" name="__VIEWSTATE" value="YTB6LTIwMTg1MDIyMjVfYTB6X2h6NXoyeF9hMHpfaHo1ejF4X2Ew

emh6RlNpXHplXzV6NXh4X194eF94eF94X194e447caf7" />

<font size=5>Vijay Mukhi</font>

<br>

<input type="submit" name="ctrl6" value="click" />

</form>

</body>

</html>

 

The control zzz displays the text 'Vijay Mukhi'. It has a property called Fsize that controls the size of the font that is displayed. A button labeled 'click' is displayed, which calls the function abc, whenever it is clicked. The function abc increments the property Fsiz,e by one. The final effect that is perceived is an increase in the font size of the text 'Vijay Mukhi' displayed on the screen.

 

The Web Server keeps a record of our actions, because the code of the function resides on the server and not on the client. If you open a new browser window, you will observe that the two Fsize properties are maintained independent of each other. This concept is similar to the one we learnt earlier, when we were dealing with the shopping cart.

The protocol used while transferring the files is called HTTP or the Hyper Text Transfer Protocol. This protocol is stateless, implying that, it does not maintain the history of the interactions between the client and the server. Each interaction is an independent action, with no information being stored about the earlier proceedings. Since the protocol does not maintain any history, the web server is provided with the facility for storing state information.

 

In the file b.cs, there is a user control with the property Fsize and the overridden Render function. This function calls the Write function, which simply outputs 'Vijay Mukhi' after substituting the font size with the property value obtained from Fsize.

 

Normally, in a property, we store the value that is contained in the hidden parameter called value in a variable. But, in this case, we have used a different syntax, i.e. State["FSize"] = value;. This indicates that State is an indexer in the Control class, which is passed a property called FSize as a string parameter. It is now the Web Server's responsibility to store this value for every instance of the browser. To retrieve the value, the same indexer called State, is used with the property of FSize.

 

The View-Source menuoption reveals a hidden field called __VIEWSTATE that has a different value for each new copy of the browser. As this field is of type hidden, it does not get displayed on the screen. However, its value is sent across to the web server. The web server stores the value of the property Fsize, for every copy of the browser. Thus, if there are 100 connections that are active, there will be 100 copies of Fsize, each storing different numbers in a double dimensional array. This is how the information about the state is maintained between a server and a browser, with the server doing all the grunt work.

 

The indexer called State is of type System.Web.UI.StateBag, which is a data structure similar to a hash table. This structure stores values and gives each of them a number or a hash value. The hash value is used to access these values in a quick and efficient manner. Thus, data in the State indexer is stored as 'FSize=10', or better still, in the form of name-value pairs. The value of Fsize is not sent over. Instead, a unique number representing the browser server connection is sent. The HTML file created at the server, fills up the FSize property using this hash value.

 

However, the use of Indexers, such as State, entail a performance overhead. Hence, they should be used with care, or else, the performance is bound to suffer.

 

a.aspx

<%@ Register TagPrefix="ttt" Namespace="nnn"  assembly="c"%>

<html>

<script language="C#" runat=server>

void abc(Object sender, EventArgs e)

{

hi.aa++;

}

</script>

<body>

<form method="POST" action="a.aspx" runat=server>

<ttt:zzz id="hi" runat=server/>

<br>

<asp:button text="Vijay" OnClick="abc" runat=server/> 

</form>

</body>

</html>

 

b.cs

using System;

using System.Web;

using System.Web.UI;

using System.Collections.Specialized;

using System.Web.UI.WebControls;

namespace nnn

{

public class zzz: Control, IPostBackDataHandler

{

int bb = 0;

String p1;

public int aa

{

get

{

return bb;

}

set

{

bb = value;

}

}

public bool LoadPostData(String p, NameValueCollection v)

{

p1 = p;

bb = Int32.Parse(v[UniqueID]);

return false;

}

public void RaisePostDataChangedEvent ()

{

}

protected override void Render(HtmlTextWriter o)

{

o.Write("<input name=" + UniqueID + " type=text value=" + aa + ">");

o.Write("<br>" + p1);

}

}   

}

 

We first add the option /R:System.dll  to the compiler command and run it as follows:

 

>csc /target:library  /out:c.dll b.cs /r:System.Web.dll /r:System.dll

 

Output

 

In the aspx file, we see a textbox containing the value of 0 and a button labeled 'Vijay'. When we click on the button, the value increases by 1. We also see the word 'hi' displayed between the textbox and the button. Have we not performed something similar earlier?

We certainly have, but this time, the control code is very different.

 

The control zzz is now derived from an interface called IPostBackDataHandler, that has two functions named LoadPostData and RaisePostDataChangedEvent. Since it is the interface that we are deriving from, we have to include the code for these functions in our class.

 

Zzz also contains a property called aa that uses an int variable named bb, to store the value. In this program, the State indexer is avoided completely. Instead, every time the data is transferred to the server, or whenever we send the data on a round trip, the function LoadPostData gets called. It has two parameters:

 

     a string p stating the id of the contro, i.e 'hi'

     a NameValueCollection object v, which is an indexer that is used to access the property values.

 

Thus, the variable p1 in this function will contain the word 'hi'. UniqueId is a reserved word. It is the name given to the control, which has an id of 'hi'. A variable named 'bb' is used to hold values for the property. It will now contain this new value returned by the indexer. This is how the stateful values of the properties are maintained.

 

Since the value of false is returned, the next function named RaisePostDataChangedEvent does not get called. Hence, there is no code present in it. If a value of true is returned, it conveys a message to the system that we want to raise a changed notification. This is known as implementing post back data.

 

By deriving from the above interface, we are informing ASP+ about our participation in post back data handling.

 

a.aspx

<%@ Register TagPrefix="ttt" Namespace="nnn" assembly="c" %>

<html>

<body>

<form method="POST" action="a.aspx" runat=server>

<ttt:zzz id="hi" runat=server/>

</form>

</body>

</html>

 

b.cs

using System;

using System.Web;

using System.Web.UI;

using System.Collections.Specialized;

using System.Web.UI.WebControls;

namespace nnn

{

public class zzz : Control, IPostBackDataHandler, IPostBackEventHandler

{

int bb = 0;

public int aa

{

get

{

return bb;

}

set

{

bb = value;

}

}

public bool LoadPostData(String p, NameValueCollection v)

{

bb = Int32.Parse(v[this.UniqueID]);

return false;

}

public void RaisePostDataChangedEvent()

{

}

public void RaisePostBackEvent (String e)

{

if (e == "Add")

{

aa++;

}

else

{

aa--;

}

}

 

protected override void Render(HtmlTextWriter o)

{

o.Write("<input name=" + UniqueID + " type=text value=" + aa + ">");

o.Write("<input type=button value=Add OnClick=\"jscript:"+ Page.GetPostBackEventReference(this, "Add")+ "\">");

o.Write("<input type=button value=Subtract OnClick=\"jscript:"+ Page.GetPostBackEventReference(this, "Subtract")+ "\">");

}   

}   

}

 

View Source

 

<html>

<body>

<form name="ctrl0" method="POST" action="a.aspx" id="ctrl0">

<input type="hidden" name="__VIEWSTATE" value="dDwtMTIyNDI1MzU1Njs7Pg==" />

 

<input name=hi type=text value=2><input type=button value=Add OnClick="jscript:__doPostBack('hi','Add')"><input type=button value=Subtract OnClick="jscript:__doPostBack('hi','Subtract')">

 

<input type="hidden" name="__EVENTTARGET" value="" />

<input type="hidden" name="__EVENTARGUMENT" value="" />

<script language="javascript">

<!--

            function __doPostBack(eventTarget, eventArgument) {

                        var theform = document.ctrl0;

                        theform.__EVENTTARGET.value = eventTarget;

                        theform.__EVENTARGUMENT.value = eventArgument;

                        theform.submit();

            }

// -->

</script>

</form>

</body>

</html>

 

Output

 

 

We have a very simple aspx file which only displays a user control named ttt:zzz. In the control code, we are deriving from two interfaces this time, as against one, which was derived from, in the previous program. The new interface called IPostBackEventHandler has only one function named RaisePostBackEvent that enables a control to take charge of an event fired by the control, in the aspx file.

 

The property aa and the post back data handling code remain the same.

 

Each time we click on the buttons, the code in the control gets called. The function RaisePostBackEvent is called with a parameter, which signifies the value of the control. If we click on the button labeled Add, the string parameter 'e' stores Add. As a result, the property aa is increased by 1. The Subtract button does the opposite, i.e. it decreases the value by 1. Earlier, on an event, code was called in the aspx file. However in this case, code is called in our control.

 

In the Render function, we have created a textbox followed by two buttons. Each of these buttons has a label and an attribute called OnClick, which is initialized to a JavaScript function named GetPostBackEventReference. This function accepts two parameters, namely, this and some text viz. Add or Subtract. The text that is supplied as a parameter to this function, is further passed on as a parameter to the function RaisePostBackEvent.

 

You will not see any code for the Javascript function in our control. Since it is prefaced with Page, the code is generated in the HTML file created by the server.

 

 

The Render function in turn creates the Javascript function called  __doPostBack in our HTML file. (You could verify it through View-Source). Further, the function name given to the OnClick property in the Render method, is changed.

 

Thus, each time we click on the button, the function __doPostBack is called with two parameters. The first parameter is 'hi', while the second parameter is the string assigned in the Render function. This function initializes the hidden form variables with the values passed as parameters. And then, it calls the submit function, thereby, transferring parameters out of the HTML file to the web server.

 

To summarise, in order to capture post back events, such as, form submits from a client, the interface called IPostBackEventHandler is implemented. Each time an event occurs on the client, the RaisePostBackEvent method in the control, is called. Also, the ASP+ system generates the client side Javascript, which incorporates customized event handling, such that, any HTML element can initiate post back.

 

Attributes

 

Try out the following C# program to understand, what attributes are all about.

 

z.cs

class zzz

{

public static void Main()

{

yyy y = new yyy();

}

}

[vijay("hi")]

public class yyy

{

public yyy()

{

System.Console.WriteLine("yyy const");

}

}

public class vijay : System.Attribute

{

public vijay(string s)

{

System.Console.WriteLine("vijay const " + s);

}

}

 

Output

yyy const

 

An attribute is a class derived from System.Attribute. It can be assigned any name that we like. Hence, we chose the name 'vijay'. The parameters passed to the attribute 'vijay' are simply passed on to the constructor.

 

We could discuss a large number of concepts regarding attributes. Instead, we will make a simple statement here, and that is "C# likes attributes and it has a large number of attributes".

 

a.aspx

<%@ Register TagPrefix="ttt" Namespace="nnn" Assembly="c" %>

<script runat=server language=c#>

void Page_Load()

{ DataBind();

}

</script>

<html>

<body>

      <form method="POST" runat="server">

            <ttt:zzz aa="Hello World!" runat=server/>

          <br>

          <ttt:zzz aa="Hello World!" runat=server>

              <m>

                <u> <%# Container.aa %><br> </u>

              </m>

          </ttt:zzz>

      </form>

   </body>

</html>

 

b.cs

using System;

using System.Web;

using System.Web.UI;

namespace nnn

{

public class yyy: Control, INamingContainer

{        private String     msg = null;

        public yyy(String g) {

            msg = g;

        }

        public String aa {

           get {

                 return msg;

            }

           set {

                 msg = value;

            }

        }

    }

[ParseChildren(true)]

public class zzz : Control, INamingContainer

{

ITemplate mm = null;     

String s = null;  

public String aa

{

get {

return s;

}

set {

s = value;

}

}

[TemplateContainer(typeof(yyy))]

public ITemplate m

{

get {

return mm;

}

set {

mm = value;

}

}

public override void DataBind()

{

            EnsureChildControls();

            base.DataBind();

}

protected override void CreateChildControls()

{

 if (m != null) {

             Controls.Clear();

             yyy i = new yyy(aa);

             m.InstantiateIn(i);

             Controls.Add(i);

           }

           else {

              Controls.Add(new LiteralControl(aa));

           }

}

}   

}

 

Output

Hello World!

Hello World!

 

Templates in controls help in customizing the display of the output, as per our liking. First, we display our user control without a template. The control has a property named aa, whose value 'Hello World' is displayed by the control. Then, we use the same syntax, but add the tags with the template name i.e. <m> and </m>, to customize this display. The template is given the name 'm', thereby, indicating that we can have many more templates in the file. In this tag, we use the html tags as they are, at the same time, place C# code, but within the <%# symbol.

 

A container is an object available in ASP.Net that enables access to the properties of a control. Thus, Container.aa will result in display of the text "Hello World". As the code is placed within the HTML underline tag u, the property value is displayed as underlined.

 

In the file b.cs, zzz has a property named aa. The variable 's' stores the current value of this property. It also contains an instance variable mm of type ITemplate.

 

The template 'm' in the a.aspx file, is translated into a property 'm' of type ITemplate. The mm object stores the value of this property. Notice that an attribute Template has been added to this property.

 

An attribute is placed within square brackets[]. Thus, Template is an attribute that accepts one parameter, which is the name of the class that the code resides in. The attribute too is stored in the dll as part of the metadata.

 

Thus, to create a template, we simply need a property of type ITemplate along with the Template attribute.

 

In the function CreateChildControls, the control is checked for the existence of a template. If the property 'm' is null, no template is  created in the control. However, if it is not null, a template is created. In situations where templates are not implemented, a Literal Control is added. It contains the value of property aa as its text. But, for a control containing a template, the InstatiateIn function is called with a variable i. i is an object that looks like yyy. No further coding is required to handle templates. The user is provided with utmost flexibility, so that he can accomplish what he desires, in this regard.

 

Templates separate the user or presentation logic from the control or business logic. Therefore, each user can decide on how properties of the control are to be laid out.

 

Control Parsing

 

a.aspx

<%@ Register TagPrefix="ttt" Namespace="nnn" assembly="c" %>

<html>

<body>

<form method="POST" runat=server>

<ttt:zzz aa="2" runat=server>

<ttt:yyy m="a1" runat=server/>

<ttt:yyy m="a2" runat=server/>

<ttt:yyy m="a3" runat=server/>

<ttt:yyy m="a4" runat=server/>

</ttt:zzz>

</form>

</body>

</html> 

 

b1.cs

using System;   

using System.Web;

using System.Web.UI;

namespace nnn

{

public class yyy: Control

{

String mm;

public String m

{

get

{

return mm;

}

set

{

mm = value;

}

}

}

}

 

b.cs

using System;

using System.Collections;

using System.Web;

using System.Web.UI;

namespace nnn {

public class zzz: Control

{

ArrayList i = new ArrayList();

int bb=0;

public int aa

{

get

{

return bb;

}

set

{

bb = value;

}

}

protected override void AddParsedSubObject(Object obj)

{

if (obj is yyy)

{

i.Add(obj);

}

}

protected override void Render(HtmlTextWriter o)

{

if (aa < i.Count)

{

o.Write( ((yyy) i[aa]).m);

}

}

}

}

 

>csc /target:library /out:c.dll b.cs b1.cs /r:system.web.dll /r:system.dll

 

Output

a3

 

We have a total of three files. In the apsx file, there are two user controls. Both the controls are derived from Control and placed in two different files, but they are finally compiled into one dll. The first control called zzz is in the file b.cs. It contains one property called aa. The second user control called yyy has a property called m, and is situated in the file b1.cs. We have incorporated many instances of the second user control yyy, within the first user control. Since the property aa has a value of 2, the output is a3, which is the value contained in the property 'm' of the third instance of the user control. A point to be noted here is that the numbering for controls too start from 0.

 

Now, let us move on to the internals.

The class yyy has one property called 'm', that stores the string in the variable 'mm'. Further, no Render method is present. The class yyy represents the most basic and simplest control.

 

All the action takes place in the file b.cs. The control zzz diverts from the customary manner in which ASP.NET parses the controls. It calls code within itself to execute customized parsing.

 

The instance 'i' of ArrayList is created to store multiple values. It has a function called Add, that simply keeps track of each value. It also has an indexer to retrieve these values. There is nothing to write home about the property aa.

 

Every time ASP.Net comes across a user control, it calls the function AddParsedSubObject from the class Control. This function is given a parameter that represents the control to be added.

 

An important point to be noted is that, "All classes in the .Net world are derived from Object". Here, we check the run time data type of the parameter obj. If it is of type yyy, it is added to the ArrayList, using its Add function.

 

If we do not override the function AddParsedSubObject, the default behavior of this function will get added to every control, thereby, making it a child control. We want to prevent this from happening, since we are interested in adding a control of class yyy, only to the ArrayList. We are now in a position to decide as to what should ensue for each control.

 

Finally, the Render function gets called. Here, we simply use the value of the property aa to index into the ArrayList object i. Then, using the cast of yyy, we retrieve the value stored in the property m of the selected yyy object. Since array handling is done internally, an ArrayList makes it easier for us to handle multiple values.

 

 

If we have a control within a control, the page parser will add the inner control to the outer Controls Collection property. This is accomplished by calling the function AddParsedSubObject, which inserts the child control into the control hierarchy tree. We can override this behavior with whatever we feel is right.

 

a.aspx

<%@ Register TagPrefix="ttt" Namespace="nnn" assembly="c"%>

<html>

<body>

<form method="POST" runat=server>

<ttt:zzz Si="3" runat=server>

<ct m="a1"/>

<ct m="a2"/>

<ct m="a3"/>

<ct m="a4"/>

</ttt:zzz>

</form>

</body>

</html>

 

b1.cs

using System;

using System.Web;

using System.Web.UI;

namespace nnn {

public class yyy : Control

{

String mm;

public String m

{

get

{

return mm;

}

set

{

mm = value;

}

}

}

}

b.cs

using System;

using System.Collections;

using System.Web;

using System.Web.UI;

namespace nnn

{

public class ccc: ControlBuilder

{

public override Type GetChildControlType(String t, IDictionary a)

{

if (String.Compare(t, "ct", true) == 0)

{

return typeof(nnn.yyy);

}

return null;

}

}

[ControlBuilderAttribute(typeof(ccc))]

public class zzz: Control

{

ArrayList i = new ArrayList();

int s = 0;

public int si

{

get

{

return s;

}

set

{

s = value;

}

}

protected override void AddParsedSubObject (Object obj)

{

if (obj is yyy)

{

i.Add(obj);

}

}

protected override void Render(HtmlTextWriter o)

{

if (si < i.Count)

{

o.Write( ((yyy) i[si]).m );

}

}

}   

}

 

Output

a4

 

The aspx file, prima facie appears to be identical to the earlier one. It has a tag zzz, which contains four instances of another tag called 'ct' nested within it. As the property Si has a value of 3, a4 is displayed.

 

The file b1.cs remains the same as before.

 

In the class zzz, we also have a property named 'si', in conjunction with an ArrayList object 'i'. Only the name and the datatype of the property have been changed, whereas, the code remains the same.

 

The function AddParsedSubObject also remains the same. The Render function has an additional error check, which is to ensure that the value of the property 'si' does not exceed the number of items in the ArrayList object. This value is stored in a property called Count.

 

The most important change brought about in the Control class zzz is, the introduction of an attribute called ControlBuilderAttribute, to which one parameter is passed. This parameter is the return value of typeof, on an instance of the class ccc. The class ccc is derived from the class ControlBuilder. As the attribute is set on our control class, functions from this class will be called, to decide as to how a control is to be added to the list of controls.

 

There is a default implementation of the Control Builder called GetChildControlType. It returns either the type of control or a null, depending upon whether the control is to be added or not, respectively. As we have overridden this function, the logic incorporated by us in this function, decides the controls that are deemed necessary to be added.

 

The first string parameter passed to GetChildControlType, is the name of the control type. The 'if' statement, compares this string variable with ct. If it results in true, the typeof class yyy is returned. Otherwise, null is returned.

 

Databound Templated Controls

 

a.aspx

<%@ Register TagPrefix="ttt" Namespace="nnn" assembly="c" %>

<html>

<script language="C#" runat=server>

public void Page_Load(Object sender, EventArgs e1)

{

if (!IsPostBack)

{

ArrayList v = new ArrayList();

v.Add("10");

v.Add("200");

v.Add("31");

v.Add("423");

l.DataSource = v;

l.DataBind();

IEnumerator e = l.DataSource.GetEnumerator();

while(e.MoveNext())

{

Response.Write(e.Current.ToString());

}

}

}

void abc(Object sender, EventArgs e)

{

}

</script>

<body>

<form method="POST" action="a.aspx" runat=server>

<ttt:zzz id="l" runat=server>

<m>

<asp:textbox id="hi" Text="<%# Container.DataItem %>" runat=server/>

<br>

</m>

</ttt:zzz>

<asp:button Text="nothing" OnClick="abc" runat=server/>

</form>

</body>

</html>

 

b1.cs

using System;

using System.Collections;

using System.Web;

using System.Web.UI;

namespace nnn

{

public class yyy: Control, INamingContainer

{

int i;

object d;

public yyy(int it, object da)

{

i = it;

d = da;

}

public object DataItem

{

get

{

return d;

}

}

public int ItemIndex

{

get

{

return i;

}

}

}

}

 

b.cs

using System;

using System.Collections;

using System.Web;

using System.Web.UI;

namespace nnn

{

[ParseChildren(true)]

public class zzz : Control, INamingContainer

{

private ITemplate   mm   = null;     

private ICollection d = null; 

public ICollection DataSource

{

get

{

return d;

}

set

{

d = value;

}

}

[TemplateContainer(typeof(yyy))]

public ITemplate m

{

get

{

return mm;

}

set

{

mm = value;

}

}

protected override void AddParsedSubObject(object o)

{

}

protected override void OnDataBinding(EventArgs e1)

{

if (DataSource != null)

{

Controls.Clear();

ClearChildViewState();

IEnumerator e = DataSource.GetEnumerator();

int i = 0;

while(e.MoveNext())

{

yyy i1 = new yyy(i, e.Current);

m.InstantiateIn(i1);

Controls.Add(i1);

i++;        

}

ChildControlsCreated = true;

ViewState["ggg"] = i;

}

}   

protected override void CreateChildControls ()

{

object o = ViewState["ggg"];

if (o != null)

{

Controls.Clear();

int numItems = (int)o;

for (int i=0; i < numItems; i++)

{

yyy i1 = new yyy(i, null);

m.InstantiateIn(i1);

Controls.Add(i1);

}

}

} } }

 

Output

1020031423

 

Although, the above program is considerable in size, it has no worthwhile achievement to offer. Let us start with the aspx program, like we always do. There is a user control named zzz, which has an id of l, since we want to replicate a DataList or DataGrid type of user control. We want you to go through the experience of writing such controls.

 

We also have a template called 'm' inside our control, that encompasses a textbox with the id 'hi'. The most important point here is that the text property is equated to the DataItem property of the control. This DataItem property belongs to the class yyy and not to the class zzz. We also have a button named nothing. This button calls a function named abc. This function sends the page back to the server.

 

In the Page_Load function, an ArrayList object named 'v' is created and initialized to 4 arbitrary numbers. The next line has l.DataSource, which implies that we are referring to a property called DataSource, that exists in the class zzz. We consciously named it as DataSource, because the DataList Control refers to it by the same name. Then, we bind our user control by calling the DataBind function. This calls the OnDataBinding function in the class zzz.

 

The DataSource property is of type ICollection that iterates through any collection or list. The ICollection interface has a member function called GetEnumerator that returns an IEnumerator, which is stored in the object 'e'. This object 'e' in turn, calls a function MoveNext. The function MoveNext either returns true, (if there are any more items in the collection), or returns false, (if there are no more items in the collection). This active item is stored in a member called Current. It removes all the previous members. Thus, the loop iterates 4 times because we have 4 members in the list.

 

In the file b1.cs, we are once again deriving class yyy from Control. We have two properties:

 

     DataItem, which is an object that gives it the flexibility to store anything.

     ItemIndex, which is an int.

 

There are no further complications.

The main source code resides in the class zzz. Note that the class has an attribute [ParseChildren(true)]. The attribute ensures the availability of code in other classes, hence no error is reported

 

The class has a property called DataSource, which is of type ICollection. It simply assigns the value contained in 'value' to the object 'd'. Since we are using templates, the template value is stored in mm, which is an instance of ITemplate. The yyy class is passed as a parameter, to the template attribute.

 

The function AddParsedSubObject is added to override the default, i.e., to avoid the default implementation from adding the control. Our function does not contain any code.

 

The function OnDataBinding first looks for a value in the property DataSource. In case some value is present, all the controls and the View state get cleared first. Thereafter, the earlier code of the function abc is executed to loop, through the members of the data list passed in the DataSource. For the time being, we do not need to concern ourselves with the type of data that the DataSource represents.

 

As we come across each member of the data source, we create a new yyy object, and give the constructor two values:

     a simple number i, that increases by 1, each time.

     the value of the list item.

 

Then, the Initialized member of the template is called with this newly created yyy object. Finally, we call Add of the control.

 

Once the loop exits, ASP.Net is signalled about the work completion on the child controls, by setting the property ChildControlsCreated to true. Finally, we restore the value held in the variable i in the state variable ggg, since it stores the number of items in the array.

 

As usual, the last function to get called is CreateChildControls. In this function, using the state variable ggg, a count of the controls displayed on the screen, is obtained.

 

<input name="l:ctrl0:hi" type="text" value="12" id="l_ctrl0_hi" />

The input box created by the template looks like the one shown above in View-Source.

 

a.aspx

<%@ Register TagPrefix="ttt" Namespace="nnn" assembly="c"%>

<html>

<script language="C#" runat=server>

public void Page_Load(Object sender, EventArgs e)

{

if (!IsPostBack)

{

ArrayList v = new ArrayList();

v.Add("1");

v.Add("2");

v.Add("3");

v.Add("4");

l.ds = v;

l.DataBind();

}

}

void abc(Object sender, EventArgs e)

{

for (int x=0; x<l.Items.Count; x++)

{

TextBox t1 = (TextBox) l.Items[x].FindControl("t");

t1.Text = (Int32.Parse(t1.Text) + 1).ToString();

}

}

</script>

<body>

<form method="POST" action="a.aspx" runat=server>

<ttt:zzz id="l" runat=server>

<m>

<asp:textbox id="t" Text="<%# Container.DataItem%>" runat=server/>

</m>

</ttt:zzz>

<asp:button Text="Update" OnClick="abc" runat=server/>

</form>

</body>

</html>

 

b.cs

using System;

using System.Collections;

using System.Web;

using System.Web.UI;

namespace nnn

{

[ParseChildren(true)]

public class zzz: Control, INamingContainer

{

ITemplate mm = null;     

ICollection ds1 = null; 

yyyc r = null;

public ICollection ds

{

get

{

return ds1;

}

set

{

ds1 = value;

}

}

[TemplateContainer(typeof(yyy))]

public ITemplate m

{

get

{

return mm;

}

set

{

mm = value;

}

}

public yyyc Items

{

get

{

return r;

}

}

protected override void AddParsedSubObject(object o)

{

}

protected override void CreateChildControls()

{

object o = ViewState["ggg"];

if (o != null)

{

Controls.Clear();

ArrayList it = new ArrayList();

int n = (int)o;

for (int i=0; i < n; i++)

{

yyy e = new yyy(i, null);

m.InstantiateIn(e);

Controls.Add(e);

it.Add(e);

}

r = new yyyc(it);

}

}

protected override void OnDataBinding(EventArgs e1)

{

if (ds != null)

{

Controls.Clear();

ClearChildViewState();

ArrayList it = new ArrayList();

IEnumerator dataEnum = ds.GetEnumerator();

int i = 0;

while(dataEnum.MoveNext())

{

yyy e = new yyy(i, dataEnum.Current);

m.InstantiateIn(e);

Controls.Add(e);

it.Add(e);

i++;        

}

r = new yyyc(it);

ChildControlsCreated = true;

ViewState["ggg"] = i;

}

}   

}   

}

 

b1.cs

using System;

using System.Collections;

using System.Web;

using System.Web.UI;

namespace nnn

{

public class yyy: Control, INamingContainer

{

int i;

object d;

public yyy (int it,object da)

{

i = it;

d = da;

}

public object DataItem

{

get

{

return d;

}

}

public int ItemIndex

{

get

{

return i;

}

}

}

}

 

b2.cs

using System;

using System.Collections;

using System.Web;

using System.Web.UI;

using System.Web.Util;

namespace nnn

{

public class yyyc  : ICollection

{

ArrayList i;

public yyyc (ArrayList it)

{

i = it;

}

public int Count

{

get

{

return i.Count;

}

}

public bool IsReadOnly

{

get

{

return false;

}

}

public bool IsSynchronized

{

get

{

return false;

}

}

public object SyncRoot

{

get

{

return this;

}

}

public yyy this[int ind]

{

get

{

return (yyy)i[ind];

}

}

public void CopyTo(Array array, int index)

{

for (IEnumerator e = this.GetEnumerator(); e.MoveNext();)

array.SetValue(e.Current, index++);

}

public IEnumerator GetEnumerator()

{

return i.GetEnumerator();

}

}

}

 

Output

    

 

The file b2.cs is added to the list of filenames during compilation. This example displays four textboxes like the last example, but they are in a horizontal form. When we click on the button labelled 'Update', the numbers within each of these textboxes, increase by one. In the  last program, when we clicked on the numbers, nothing happened. In this program, we are walking through the data in each textbox, at posting.

 

Now, we will only explain the variations between the current program and the previous one. Hence, we advise you to revise the earlier explanation.

 

When we click on the button, the function abc gets called as usual. b.cs holds the zzz user control, having an id of l in the file a.aspx file. This class contains a property called Items. This property in turn, has a member called Count that gives a count of the number of items in our collection.

 

The 'for' loop is repeated 4 times, since we have four items in our collection. The Items property is of type yyyc, which is located in b2.cs. The class yyyc implements functions from ICollection. It also contains an indexer that returns a yyy or Control object.

 

Using the indexer, we have called the function FindControl with the id of the textbox 't'. This function returns a textbox object that is stored in t1. We then added-on to the existing value of the textbox, and redisplayed it using the text property.

 

The class yyy in b1.cs remains the same as before. Here, the constructor simply initializes the variables i and d to 'it' and 'da', respectively. These variables are used to dispatch the values in the property DataItem and ItemIndexes.

 

In the file b.cs, which contains the zzz class and the attribute of ParseChildren as true, we have changed the DataSource name to 'ds'. This is done, basically to demonstrate to you, that you can choose whichever names you fancy. The template 't' remains unaltered. As mentioned earlier, we have introduced a new property called 'Items' that is of type class yyyc, found in the file b2.cs. This property simply returns the value of 'r', which is also of type yyyc.

In function CreateChildControls, we simply populate our array list object as before. It is now given as a parameter to the constructor for the newly created yyyc object. The return value is stored in 'r'. Thus, r now has a reference for the items in the list.

 

The Items property is 'readonly'. Hence, it can only return values. Here, it returns a yyyc object. The function OnDataBinding initializes the variable 'r' and then executes the same code as before.

 

In the file b2.cs, yyy is derived from the interface ICollection, which has a large number of properties and functions. Though, they are not always called, yyy still has to contain the code.

 

The ArrayList object i is initialized to the value passed to the constructor. It stands for the items in the list. The Count property in the aspx file returns the Count member of the ArrayList class.

 

The indexer uses the int ind parameter to index into the ArrayList's indexer, and to fetch the value. This value is cast to a yyy type. The other functions are not relevant at the moment.

 

Thus, we are able to receive each of the values in the textbox, and change them one at a time. The DataList and DataGrid controls, work on similar lines. By now, you must have realized that all the free controls have been written in the same manner. Further, by the time you receive a copy of this book, there will be over a trillion such controls that you would be able to buy, thus, making server side programming a real joy.