Tải bản đầy đủ (.pdf) (40 trang)

Professional ASP.NET 1.0 Special Edition- P33 potx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (1.16 MB, 40 trang )

 Allows expando attributes (i.e. attributes that are not pre-defined properties of the control) to be specified in a
server-control declaration. Expando attributes are not interpreted and are written directly to the output HTML
stream. By default, the
Control class throws an exception if an attribute is not a property of the server control.
 Provides consistency with the standard ASP.NET web controls since they derive from the WebControl class.
 Persists the style object and any settings/state during postbacks using view state. In the control example earlier,
when we used the style object, any style changes made in event handlers or in other code would not have been
remembered after a postback, since the state of the style object was not being round tripped using viewstate. For
our control to remember any style changes (because it is derived from
Control), we'd need to implement
custom statement management, by overriding the
LoadViewState and SaveViewState methods. Viewstate
is discussed later in the chapter.
The
WebControl class is designed to either assist with the rendering of a control, or take complete control of the
rendering. For a simple control such as our label, the
WebControl class can be used to replace most of the code we have
written.

To make use of the
WebControl class we have to make the following changes to our code:
 Derive from the WebControl class rather than Control class.
 Declare a public constructor that calls the base constructor, specifying which HTML element to render.
 Override the RenderContents method to emit the content we want within our <h1> element. The
WebControl class takes responsibility for rendering the attributes and the begin/end-tags, so we remove the
Render method.
 Remove all of the style properties we implemented earlier, since the WebControl will automatically have
implemented them for us.
After making these changes, our C# control code looks like this:

using System;



using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;

namespace WroxControls

{

public class MyLabel : WebControl

{

string _text;

public MyLabel() : base ("H1")

{

}

public string Text

{

get{ return _text; }

set{ _text = value; }


}

protected override void OnInit(EventArgs e)

{

base.OnInit(e);

if ( _text == null)

_text = "Here is some default text";

}

protected override void RenderContents(HtmlTextWriter writer)

{

writer.Write( _text);

}

}

};

The same control in VB.NET is shown here:

Imports System


Imports System.Web

Imports System.Web.UI

Imports System.Web.UI.WebControls

Namespace WroxControls

Public Class MyLabel

Inherits WebControl

Private _text As String

Public Sub New()

MyBase.New("H1")

End Sub 'New

Public Property Text As String

Get

Return _text

End Get

Set


_text = value

End Set

End Property

Protected Overrides Sub OnInit(e As EventArgs) MyBase.OnInit(e)

If _text Is Nothing Then

_text = "Here is some default text"

End If

End Sub

Protected Overrides Sub RenderContents(writer As HtmlTextWriter)

writer.Write(_text)

End Sub

End Class

End Namespace

With these changes, we need to change our ASP.NET page since the available properties names for our control have
changed. You can use the
WebControl class documentation to get a list of all the available properties.


Here is our updated page, which now uses properties consistent with all the server controls provided as part of ASP.NET,
which were discussed in Chapter 5:

<%@ Register TagPrefix="Wrox" Namespace="WroxControls"

Assembly="MyLabel" %>

<html>

<body>

<Wrox:MyLabel runat="server"

ForeColor="Yellow"

BackColor="Black"

Font-Size="36"

Text="Web Controls" />

</body>

</html>

For controls that only require one HTML root element to be rendered, our updated control code provides a good model to
follow when building server controls. Your server controls should always derive from the
WebControl class unless they
do not render any UI, in which case the services provided by

WebControl do not provide any benefit.

You can use all the other techniques we've used to render HTML so far with server controls derived from
WebControl, the
only difference is that your code is moved to the
RenderContents method.

In this revised code demonstrating the use of
WebControl, I have removed support for the RepeatCount property,
since that would require one or more HTML elements to be rendered at the root level (you can have any number of
controls within the root element). This is something a basic web control can do. If you need to do this, you have to
override the Render method.

To implement the
RepeatCount property in our implementation, the overridden Render method calls the base
implementation of
Render a number of times. The following code just calls the base Render method 3 times to
demonstrate the technique:


C#
protected override void Render(HtmlTextWriter writer)

{

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

{

base.Render( writer );


}

}


VB.NET
Protected Overrides Sub Render(writer As HtmlTextWriter)

Dim i As Integer

For i = 0 To 2

MyBase.Render(writer)

Next i

End Sub 'Render

As expected, the control will render the label three times:

To show that our server control can also be programmatically manipulated, just like any other ASP.NET server control
provided out of the box, the following ASP.NET page sets all the properties of our server-control properties within the
Page_Init event:

<%@ Register TagPrefix="Wrox" Namespace="WroxControls"

Assembly="MyLabel" %>

<%@ Import Namespace="System.Drawing" %>


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

void Page_Init( object sender, EventArgs e )

{

ourLabel.Text = "Web Controls";

ourLabel.ForeColor = Color.Yellow;

ourLabel.BackColor = Color.Black;

ourLabel.Font.Size = 36;

}

</script>

<html>

<body>

<Wrox:MyLabel runat="server" id="ourLabel" />

</body>

</html>

Now that we've created a couple of server controls that generate their UI by emitting HTML, let's take a look at composite

controls. These controls render their UI by reusing other server controls. An ASP.NET page is a good example of a
composite control - so let's take a look at how they work in detail.

Composite Controls

All ASP.NET dynamic pages are created by the ASP.NET run-time using ASP.NET controls. Although you may not have
realized it, we have already built several ASP.NET controls in the earlier chapters of this book just by creating ASP.NET
pages and user controls. The ASP.NET page framework automatically converts and compiles pages into server controls
contained within a dynamically created assembly the first time a page is requested:

This server control is then used to render the page. Subsequently, when a page is requested, the precompiled server
control can just be instantiated and called, resulting in great performance:

The assemblies created for ASP.NET pages are automatically managed for you. If you search around your Windows
system directory, you'll find a directory called Temporary ASP.NET Files. Within this you'll find sub-directories for the
various web sites on your machine, which in turn will contain the assemblies for ASP.NET pages.

If we open up one of these generated assemblies using the ILDASM tool, we'll see that they typically contain a single class
with the same name as the ASP.NET page. This class extends (derives from) the class
System.Web.UI.Page: (see the
sixth item from the root in the tree control.)

The Page object (located in the assembly System.Web.dll) derives from the TemplateControl class, which in turn
derives from the
Control class. This diagram shows the main ASP.NET control classes and their inheritance hierarchy:

Here is a brief description of the role of each of these classes:
 Control - provides a common base class for all other control types.
 WebControl - provides methods and properties for dealing with the style. This class is the base class for all ASP
web controls. These are always declared in an ASP.NET page using an

ASP: prefix.
 HtmlControl - base class for standard HTML elements, such as input. Typically you will never derive from this
control.
 TemplateControl - contains functionality that is shared between user controls and pages, such as support for
loading user controls (.ascx) or templates.
 UserControl - base class from which all user controls derive.
 Page - base class from which all dynamically compiled ASP.NET pages derive.
The actual code generated by ASP.NET for these dynamically generated classes is not that different from the code we have
just written for our label control. One key difference is that it uses control composition to actually generate the page. That
is, a page is a server control that uses other server controls to render its UI.


Building a Composite Control

A server control can be a container for other controls. The
Control class has a Controls property of the type
ControlsCollection. This collection class can hold zero or more child controls. When a control is rendered, each of its
child controls is called upon to render itself. If these child controls also contain child controls, this process repeats, until
all controls have been rendered.

By default, if you derive a server control from the
Control class, nested elements declared within an ASP.NET page will
be added to the
Controls collection, assuming you haven't used the ParseChildren attribute discussed earlier to
change the default behavior.

Assuming we had a server control with a class definition that basically did nothing:

using System;


using System.Web;

using System.Web.UI;

namespace WroxControls

{

public class CompositeControl : Control

{

}

};

and then declared that control with nested sub-controls (a button and some literal text) on a page:

<%@ Register TagPrefix="Wrox" Namespace="WroxControls"

Assembly="MyFirstControl" %>

<html>

<body>

<form runat="server">

<Wrox:CompositeControl runat="server">


<asp:button Text="A Button" runat="server" />

Some Text

</Wrox:CompositeControl>

</form>

</body>

</html>


The ASP.NET page parser automatically creates a
LiteralControl for each block of text or significant whitespace.

The control would actually render the button and the literal text, since the default Render implementation invokes the
Render method of each control in the Controls collection:

When writing a composite control, the control itself typically decides on what child controls to create, rather than the user.
If the user is going to create all the UI, you should use a
User control rather than a custom server control.

Server controls override the
CreateChildControls method and populate the Controls collection. The ASP.NET
framework calls this method to signal to a control that it should create its child controls. What controls you populate the
controls collection with depend on the UI your control renders. Any class can be used as long as it derives from the
Control class.

The code required to implement our first server control using control composition is shown here:


using System;

using System.Web;

using System.Web.UI;

namespace WroxControls

{

public class CompositeControl : Control, INamingContainer

{

protected override void CreateChildControls()

{

LiteralControl text;

text = new LiteralControl( "<h1>ASP.NET Control Development in C#</h1>" );

Controls.Add(text);

}

}

};


In this code we have removed the Render method and replaced it with the CreateChildControls method. When
called, this code creates a single
LiteralControl and adds it to the child control collection. We have also modified the
class to derive from the
INamingContainer interface to indicate that it is a naming container. We'll discuss naming
containers in more detail later in this chapter, but as a general rule all composite controls should implement this.

By removing the
Render method from our class, the default implementation of the Render method defined in the
Control class is called during the render phase of a page. The default Render method enumerates the Controls
collection and invokes the
RenderControl method of each child control in turn. This effectively causes a control's UI to
be drawn, by allowing each child control to output their own HTML, by either using the
HtmlTextWriter object, or, again,
by also using child controls.

When deriving from the
WebControl class and overriding the RenderContents methods, you should always call the
base implementation of
RenderContents if your WebControl uses control composition. This is because it's the method
that calls its base classes' (the Control class) Render method. The same is true if you derive from Control and
override
Control.Render (assuming you want child controls declared in a page to be rendered).


CreateChildControls

The
CreateChildControls method can be called at different stages in the life cycle of a composite control, but it will

only ever be called once, unless you explicitly reset the state of a control using the
ChildControlsCreated property.
In this respect it is a non-deterministic event that occurs during the lifetime of a control, unlike the
Init and Render
events already discussed, which occur at fixed times in the life cycle of a page, so are deterministic.

If not called before, the
CreateChildControls method will always be called in the pre-render stage of a page. The
method will be called prior to this if either the public
FindControl method of the control is called, or if the Control itself
calls the protected
EnsureChildControls method. The FindControl method is used when pushing postback data
into a control, so
CreateChildControls will also be called during the postback stage of a page, if there is postback data
associated with a given control.

As a control author you should always use the
EnsureChildControls method if you need to populate your own controls
collection prematurely. This method calls
CreateChildControls only if the public property ChildControlsCreated
is
false. If the property is false, it is set to true once CreateChildControls is called. This method ensures child
controls are only ever created once.

If the
ChildControlsCreated property is set to false in your code, all child controls are released automatically.
Subsequently,
CreateChildControls may be called again.



ASP.NET Pages and Composite Controls

An ASP.NET page is essentially compiled into a composite control. Consider this simple page:

<html>

<body>

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

Name: <asp:textbox runat="server"/>

</form>

</body>

</html>

When it's compiled, the page will be rendered using the following server controls, which effectively form a hierarchical tree
of server controls:

The Page object's Controls collection will contain the child controls LiteralControl, HtmlForm, and
LiteralControl. The HtmlForm control will contain the child controls LiteralControl, TextBox, and
LiteralControl. When the page is rendered, each of these controls will be rendered in turn, starting at the top,
recursively rendering each child control before moving onto the next sibling. So, for this page the rendering sequence
would be
Page, LiteralControl, HtmlForm, LiteralControl, TextBox, LiteralControl, LiteralControl.


Control Tree Navigation


A server control tree can be navigated using different methods of the
Control class. To navigate down one level you use
the
Control.Controls collection. To navigate up the tree one level, from a control to its parent, you use the Parent
property. To navigate from a
Control to the container Page you use the Control object's Page property. To recursively
search down the tree for a control you use the
Control object's FindControl method.


The Advantages of Control Composition

Using control composition for simple controls really doesn't have much advantage over rendering the HTML directly using
HtmlTextWriter. To see when there is a much greater benefit, let's create a more complex control. This control will
create an HTML table below a
<h1> element. The table will contain 10 rows, each with 5 cells. We could render this using
the
HtmlTextWriter or by creating lots of LiteralContent objects, but it makes a lot more sense to use the
high-level ASP.NET web controls we have seen in earlier chapters of this book. Although we have typically only made
references to these controls in ASP.NET pages before, creating these controls dynamically within a control is
straightforward:

using System;

using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;


namespace WroxControls

{

public class CompositeTableControl : Control, INamingContainer

{

Table _table; // Make table a member so we can access it at any point

protected override void CreateChildControls()

{

LiteralControl text;

text = new LiteralControl(

"<h1>ASP.NET Control Development in C#</h1>");

Controls.Add(text);

TableRow row;

TableCell cell;

// Create a table and set a 2-pixel border

_table = new Table();


_table.BorderWidth = 2;

Controls.Add(_table);

// Add 10 rows each with 5 cells

for(int x = 0; x < 10; x++) {

// Create a row and add it to the table

row = new TableRow();

_table.Rows.Add(row);

// Create a cell that contains the text

for(int y = 0; y < 5; y++) {

text = new LiteralControl("Row: " + x + " Cell: " + y);

cell = new TableCell();

cell.Controls.Add(text);

row.Cells.Add(cell);

}

}


}

}

};

This code may look at bit complex a first, but what it's doing is very simple. Firstly, it adds the LiteralControl
containing the
<h1> element as a child control. Next, it creates a Table object
(System.Web.UI.WebControls.Table), sets the border width to two pixels and adds that as a child control:

_table = new Table();

_table.BorderWidth = 2;

Controls.Add( _table );

The table is then populated with rows by running a for loop for ten iterations, creating, and adding a TableRow object
using the
Rows collection property:

row = new TableRow();

_table.Rows.Add( row );

Finally, for each row that is added, an inner loop is executed for five iterations adding a TableCell object that has a
LiteralControl as its child. The Text of the LiteralControl is set to indicate the current row and cell. Compiling
and executing this code produces the following:


All of the controls we have used (Table, TableRow, TableCell, etc.) derive from the Control class somewhere in
their inheritance hierarchy. Each of these controls also uses control composition to render its UI. The control tree for our
page actually looks something like this:

If you view the source for this generated page you'll see around 130 lines of HTML. We didn't directly create any of that,
and we have gained a number of key advantages by using control composition over direct rendering:
 We have saved ourselves a great deal of error-prone HTML creation via code, and therefore saved time and
increased our productivity.

 We have programmatically created objects, called methods, and set properties. The code we've written is
therefore simple to read, and easily extendable at a later date.
 We have not been exposed to the underlying HTML generated by the various controls we have used.
 The fact that they actually render table, tr, and td HTML elements to the page is an implementation detail that
we do not have to concern ourselves with. In theory, the output could just as easily be WML or any other markup
language - our control will still work just fine.
The argument for using composite controls becomes more apparent as the child controls we use, such as the
Table
control, provide more and more functionality - and hence save us more time and effort. For example, let's modify our table
so the user can edit each of the cells within it by using a
TextBox control within our TableCell control, rather than a
LiteralControl:

for( int y=0; y < 5; y++ ) {

TextBox textbox;

textbox = new TextBox();

textbox.Text = "Row: " + x + " Cell: " + y;


cell = new TableCell();

cell.Controls.Add(textbox);



By changing four lines of code we now have an editable table:

The underlying HTML generated by our control is now even more complex; with the child controls creating all the input
elements needed within the
td elements:

<html>

<body>

<form name="ctrl0" method="post" action="compositetablecontrol.aspx" id="ctrl0">

<input type="hidden" name="__VIEWSTATE" value="dDwtNTMxODUzMzMxOzs+" />

<h1>ASP.NET Control Development in C#</h1>

<table border="0" style="border-width:2px;border-style:solid;">

<tr>

<td>

<input name="grid:ctrl2" type="text" value="Row: 0 Cell: 0" />


</td><td>

<input name="grid:ctrl3" type="text" value="Row: 0 Cell: 1" />

</td><td>

<input name="grid:ctrl4" type="text" value="Row: 0 Cell: 2" />

</td><td>

<input name="grid:ctrl5" type="text" value="Row: 0 Cell: 3" />

</td><td>

<input name="grid:ctrl6" type="text" value="Row: 0 Cell: 4" />

</td>

</tr><tr>

You'll notice within this HTML that each of the input elements has a name attribute. By default, these ids are assigned
sequentially by ASP.NET as the child controls are created and added to our control. When a postback occurs, ASP.NET
uses these
ids to automatically push postback data into server controls that are created with the same id. This automatic
management of postback data is part of the magic that gives the impression to page developers that controls are
intelligent - since they don't have to write lots of code to manually make controls, like a textbox, remember their state.
Of course, under the hood, a control author has to implement some code that maps the postback data to properties, but
once that is done, page developers and custom control writers can simply reuse an intelligent self-maintaining control.

Writing a TextBox Control


To understand some of the more advanced development aspects of server control, it is useful to actually write a simple
control like a textbox. Although this isn't the most exciting control in the world to write, especially since it already exists,
it clearly demonstrates some important aspects that control authors need to understand including:
 Interacting with postback.
 Using viewstate.
 Raising events from a control.
To render a textbox our server control needs to output an HTML
input element with a type attribute containing the value
"text". Using the WebControl as our control's base class makes this first task simple. We have to perform three main
tasks:
 Create a new class that derives from the WebControl class.
 Implement a public constructor that calls the base class constructor specifying that our server control should
output an
input element.
 Override the AddAttributesToRender method. This is called to allow derived classes to add attributes to the
root element (
input). We override it and add the type attribute with a value of "text". We also need to add
a
name attribute whose value is derived from the UniqueID property. This property is used by ASP.NET to hold
the unique
id of each control. We have to output the name property containing this value since an HTML form
will not postback the value entered into an input field without a name.
Here is the sourcecode that implements these steps:

using System;

using System.Web;

using System.Web.UI;


using System.Web.UI.WebControls;

namespace WroxControls

{

public class MyTextBox : WebControl

{

public MyTextBox() : base("input")

{

}

protected override void AddAttributesToRender(HtmlTextWriter writer)

{

base.AddAttributesToRender(writer);

writer.AddAttribute(HtmlTextWriterAttribute.Name, UniqueID);

writer.AddAttribute(HtmlTextWriterAttribute.Type, "input" );

}

}


};

The implementation of the AddAttributesToRender method calls the base-class implementation. This is called since
the base-class implementation will add various other attributes to our element depending on other properties that have
been set in the base class, such as style attributes.

Once compiled, we can use the following ASP.NET page to reference our control (assuming the control is called
MyTextBox and is compiled to the assembly MyTextBox.dll):

<%@ Register TagPrefix="Wrox" Namespace="WroxControls"

×