-6-
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.