he
programming interface of ASP .NET provides an extremely rich
framework of controls that you can use to populate server
pages. Most of these controls offer a huge number of
properties, methods, and events that can be used to create
even more specialized components. For example, in this column
I recently covered all the features that DataGrid controls
expose, including sorting, paging, selection, column
templates, and in-place editing. Using these properties
results in a control that's significantly more functional than
a regular DataGrid—even though a DataGrid is a highly
customizable control.
To implement all
of these functions, you normally have to write delegates for
up to six or seven events. The complexity of these delegates
depends largely on the exact tasks they accomplish. However,
in most real-world scenarios they take up dozens of lines of
code all wrapped by a <script runat="server">
tag.
Having all this
code concentrated within a unique tag is a good thing.
However, it ends up padding your ASPX files with page-specific
variables, logic, classes, and user-defined functions. An ASPX
page comprises three logical elements: directives, code, and
layout. While directives and layout are obviously and
inextricably tied to a specific page, code may contain a
certain degree of reusability and may be formed by classes,
functions, and controls that are shared by multiple pages in
the same application or even by different applications on the
same server machine.
For the past
several months I've been writing about ASP .NET, and in this
month's installment I'll dig through the so-called code-behind
technique and review its impact on project management and
development. Then, I'll take a look at a very special category
of components called user controls. They look like
user-defined controls, but their composite nature—a mix of
HTML and code—make them more similar to embedded and
programmable pages. Finally, I'll introduce custom server
controls as the finishing touch in the ASP .NET component
architecture.
Code-behind
An ASPX page
represents the source code of a Web Forms page—the new model
of Web applications introduced by Microsoft® .NET. Web Forms
stem from rapid application development tools like Visual
Basic®.
In Visual Basic,
a form is divided into two distinct parts: the graphical
component-based layout and the code that interacts with the
constituent controls. However, in Visual Basic this logical
and neat distinction gets lost when the form is serialized to
disk. In fact, you always have a single .frm file with both
code and layout information. The IDE lets you work on code and
layout in a fairly independent way, but doesn't allow two
people (say, a programmer and a designer) to work on the same
form at the same time.
In ASP, things
are much worse; the structure of the language simply doesn't
allow for a neat separation of layout and code. On the other
hand, this has been a key to the success of ASP as a Web
technology—the mix of script and HTML has made it easy for
people with a wide range of skills to write simple, effective
Web pages. ASP .NET allows better separation of layout and
code elements.
As I mentioned
earlier, there are three elements in an ASPX page: directives,
layout, and code. A directive just inserts messages for the
compiler and browser that will process the page. It specifies
the language used, enables or disables runtime services such
as tracing and state maintenance, indicates the transaction
support required, specifies the local code page identifier
(LCID) and the page to which the user should be redirected in
case of trouble.
The layout is the
template of what you want the browser to receive. Usually,
this is HTML code, but the exact MIME type can be specified
through the ContentType attribute of an @Page directive. For
instance, this specifies that XML will be returned:
<%@ Page ContentType="text/xml" language="VB" %>
For a complete description of ASP .NET page directives,
see the documentation for @Page at http://msdn.microsoft.com/library/en-us/cpgenref/html/cpconpagedirectives.asp.
For the layout, server controls, which are processed
by the HTTP runtime, can be used to produce output for the
browser. Server controls are not instantiated via a function
or a method call (such as CreateObject), but are inserted
declaratively through markup tags belonging to a custom or a
system-provided namespace.
In declarative
programming, you configure a control by setting its
attributes. While this may meet many of your needs, for a
highly interactive page, you usually need to write code
against your server controls. This code consists of the user
interface logic that lets your users interact with the Web
form.
Code-behind is
the ASP .NET feature that allows you to store directives and
the page layout in one ASPX file and store the Visual Basic or
C# code that's behind the page (that drives the page) in a
separate file. The link to the code-behind file is kept as an
attribute of the @Page directive. When users point their
browsers to the ASPX page, the code-behind class file runs and
dynamically produces the output for the page. When the page is
invoked, the code-behind code is loaded and treated as an
integral part of the page.
Before delving
into details of how this can be implemented and how it
actually works, let me first emphasize the practical advantage
of this approach. Developing a Web Form can involve two
distinct groups of people at different times: designers and
programmers. Once the structure of the page has been defined
in terms of the number, names, and types of the constituent
server controls, HTML designers can take the ASPX file and
make it appear as friendly and compelling as possible. They
decide the position of the various controls, colors, fonts,
and even client-side scripting. At the same time, when working
with Visual Basic and C#, programmers develop the code and
delegates required for the page to work properly and fulfill
user expectations.
This is the same
approach to form development you used in Visual Basic 6.0, but
this approach allows you to introduce a physical separation
between layout and code.
Enabling Code-behind
You enable
the ASP .NET code-behind feature by adding a couple of
attributes to the @Page directive.
<%@ Page Language="VB"
Inherits="StdPageClass"
Src="StdPage.vb" %>
You can only have one @Page directive per .aspx file.
Multiple attributes are defined through a space-separated
list, with no space around the equals sign.
The
Inherits attribute represents the code-behind class for the
page to inherit from. It can be the name of any class derived
from the Page class. The source attribute (Src) represents the
URL of the source file containing the code to compile for the
code-behind class. An ASPX page is then stored as a compiled,
time-stamped piece of code available to browsers via its .aspx
URL.
When the HTTP
runtime module is pointed to this ASPX page, it analyzes the
@Page directive. If the Src is present, then it loads the
given code-behind file. Next, after a successful compilation,
the HTTP runtime utilizes the content of the Inherits
directive to get the name of the class from which the current
page must be instantiated. Any ASPX page displayed through a
browser, in fact, is seen on the Web server as a running
instance of a class derived from the Page class of the .NET
Framework. If you don't use code-behind, then the source code
you insert in the various <script runat="server"> tags
are considered private methods or overrides of a particular
instance of a Page class. Otherwise, by choosing the
code-behind approach, you can control the base class of the
page and make it automatically inherit from previously coded
classes.
The Inherits
attribute can also point to a custom class scoped in a
particular namespace.
<%@ Page Language="C#"
Inherits="MyDotNetLib.StdPageClass"
Src="StdPage.cs" %>
In this case, the StdPage.cs file may look like the
following:
namespace MyDotNetLib {
using System;
using System.Collections;
using System.Data;
using System.Web.UI;
using System.Web.UI.WebControls;
public class StdPageClass : Page {
}
}
The ability to work with a class lets you declare and
create public data members in a much more elegant and
effective way than just inserting their declarations outside
of a procedure in a <script> tag. By making your globals
members of a class, you can better control their visibility.
In addition, you can define a code-behind class starting from
an existing subclass that already inherits from Page. When you
inherit from Page, or a Page-based class, you are taking
advantage of the wonderful feature called visual
inheritance—the backbone of Windows® Forms.
Aside from
the fact that you have to explicitly write a Page-derived
class instead of a simpler list of methods and variables
declaration, there's really nothing to differentiate a normal
ASPX page from a code-behind one. When the page is invoked for
the first time, if it is code-behind then the code generator
class initializes itself from the source file read from the
Src attribute. Otherwise, it builds up using the string
extracted from the <script> tag. In any case, users
won't detect any difference.
When you write
code for a code-behind page you must be aware of one key
point. You can safely access the runat="server" controls
declared in the layout only after declaring them as protected
or public members of your page class. For example, if you have
two textboxes like these
<asp:textbox id="fname" runat="server" />
<asp:textbox id="lname" runat="server" />
you can't just use their IDs in code-behind classes.
Instead, you must first declare them as protected or public
class members:
protected TextBox fname, lname;
Keep them as private (either through the keyword
private or by using no qualifier at all) and you'll get a
runtime error. The error is due to an unsuccessful attempt to
dereference a null object. In fact, there would be nothing in
your code to initialize such controls.
In Figure 1, you can see the source code of
a minimal code-behind page with just two textboxes whose
default value is set within the OnLoad override in stdpage.cs.
The actual page is shown in Figure 2.
 Figure 2 Code-behind
Page
In Figure 1 notice the role of the base
keyword that lets you invoke the same method on the parent
class.
base.OnLoad(e)
In the sample code, the two textboxes are read-only.
Unlike the Windows Forms TextBox control, the TextBox Web
control doesn't have a ReadOnly property—at least as of Beta
1. So if you want to make them read-only through code, you
must set the read-only attribute in much the same way as you
would in HTML:
fname.Attributes["readonly"] = "true";
lname.Attributes["readonly"] = "true";
Now, suppose that you want to create a new page
allowing users to edit the content of the textboxes and submit
it to the server. If you've used a code-behind class, this is
easy, thanks to class inheritance. In Figure 3, you can see the ASPX file with
the layout of a new page called myeditpage.aspx. It contains a
button and editable textboxes. When clicked, the button runs
the OnEdit procedure defined in the code-behind class.
<% @ Page Language="C#"
Inherits="MyDotNetLib.EditPage"
Src="EditPage.cs" %>
You can derive the page from a new class called
EditPage defined in editpage.cs. This class, in turn, could
inherit from StdPage, building a true hierarchy of page
classes specific to your application.
public class EditPage : MyDotNetLib.StdPage {
protected Button btnEdit;
protected Label statusbar;
•••
}
Figure 4 shows the source of the
EditPage class. It inherits from StdPage, gets free access to
the fname and lname textboxes, and defines the handler for the
button's click event. In order for this code to work, you must
have a compiled version of StdPage available in the Web
server's Bin directory or the application's Bin directory. The
following command compiles stdpage.cs to a DLL assembly.
csc /t:library /r:system.web.dll /r:system.dll stdpage.cs
Figure 5 shows the
output of the demo page.
 Figure 5 EditPage in
Action
Code-behind is an
interesting technique that has been primarily designed to let
you write more modular code. Its main goal is to enable you to
keep code and layout physically separate. In doing so, you can
exploit the native object-orientation of the .NET Framework to
build a hierarchy of page classes that provide more
specialized functions. All the objects you manipulate are
descendants of the Page class and, as such, must be
specialized and customized instances of Page. As a result,
with code-behind you get modularization plus a little bit of
page-level reusability. Unfortunately though, there's nothing
in the layout that you can reuse. You can derive a code-behind
class from one you've previously defined, but the layouts of
the two pages are fairly independent entities.
Toward Embeddable Forms
Pagelets represent another level of page reusability
and, in fact, they're more reusable because of their finer
granularity. With pagelets, forms—not pages—are the unit of
reusability. A pagelet can be seen as an embeddable Web Form.
You define one in much the same way as a Web Form, then give
it a name and treat it as a component. If you are familiar
with DHTML scriptlets, you can think of pagelets as their ASP
.NET counterparts.
However, unlike
forms, pagelets are visual components that you import and use
in your ASP .NET pages through a given namespace and tag name.
In other words, you declare them as you would any other
standard .NET server control. You code against them using the
properties and methods they expose.
You write
pagelets as self-contained ASP .NET pages, complete with
layout and code blocks. The layout represents the user
interface of the component—the controls making the form. The
internal code ties together the various constituent controls
and implements their business logic. Here's a quick example of
a pagelet, taken from the Beta 1 documentation.
<script language="C#" runat="server">
public String Color = "blue";
public String Text = "Message User Control!";
</script>
<span id="Message"
style="color:<%=Color%>"><%=Text%>
</span>
This code is saved in a file with an .ascx extension.
The content of the <script> tag defines both the
programming interface of the component and its internal code.
Everything that is declared public is visible outside the
control and is externally callable by clients.
The HTML portion of the code (which looks more like
ASP because of the <%...%> blocks) is the user interface
of the control. This produces the output that's injected in to
the host page as the result of the pagelet
declaration.
To become usable,
a pagelist must first be registered within an ASPX page. This
is done through the @Register directive:
<%@ Register
TagPrefix="expo" TagName="Message"
Src="pagelet.ascx" %>
The TagPrefix and TagName attributes indicate the
namespace prefix and the tag name that will identify the
control within the page. In this case:
<expo:Message>
The Src attribute points to the source code for the
pagelet. Figure 6 shows the pagelet-enabled
version of the ASP .NET page you saw in action in Figure 5. The pagelet uses a syntax
similar to ASP to make use of publicly callable properties and
methods in its internal code. For example, if the user sets
the pagelet's Color property to red, the code substitutes the
property value in its style tag like this:
... style="color:<%=Color%>">
When the property tag is replaced with its actual
value, the tag looks like this:
... style="color:red">
Despite appearances, though, this code couldn't be
further from ASP. The <%...%> symbols merely identify a
sort of data-bound piece of code. The binding is not between
the control and a data source, but between the control and its
container environment.
User
Controls
With a few
modifications, virtually any Web Form can be encapsulated in a
pagelet user control, thereby becoming a component that you
can embed in another page. Pagelets provide an easy way to
modularize and reuse simple, common user interface
functionality across Web applications. Pagelets are compiled
before use and stay cached on the server until expiration or
modification occurs.
When the HTTP
runtime module—the system component that processes ASP .NET
pages—encounters a pagelet, it promptly generates a wrapper
class for it whose name is based on file name and extension.
For message.ascx, the class name is ASP.message_ascx.
So how do you take a Web form you've written and
transform it into a user control? First, make sure that the
pagelet does not contain any <html>, <body>, or
<form> tags. Since pagelets are merged with the layout
of the Web Form that hosts them, these tags can cause nesting
problems. Bear in mind that only controls that want to take
advantage of postback events actually need to be enclosed in a
<form runat="server"> tag. Furthermore, if the page
you're converting into a pagelet contains an @Page directive,
change it to an @Control directive or the HTTP runtime will
complain. Once you've cleaned out these unwanted HTML tags,
rename the file with an .ascx extension. This is the key that
will enable special treatment on the file.
The
@Control directive is almost as powerful as the @Page
directive, except for a couple of unsupported attributes:
Trace and OutputCache. This isn't really bad news—if you need
tracing, just enable it at the page level and it will
automatically work for the pagelet as well. If you need to
control the caching policies for the page, you can also do
that at the page level. You can't just have different caching
policies for different portions of the page.
The
ability to set an @Control directive for a pagelet makes it
possible to use different languages to write the pagelet and
the hosting page. For example, you can use Visual Basic in the
pagelet and C# in the calling page. The Message pagelet can be
rewritten in Visual Basic like the following code and still
work in a C# page.
<% @ Control Language="VB" %>
<script runat="server">
public Color As String = ""
public [Text] As String = ""
</script>
<span style="color:<%=Color%>"><%=[Text]%> </span>
Notice that you must wrap the word Text with square
brackets to mean that you want it to be a custom property in
Visual Basic only. This is because Text is a reserved keyword
in Visual Basic .NET; it's part of the Option Compare
statement.
Custom Server-side
Controls
The .NET
Framework is a hierarchy of classes that lets you write new
classes according to the principles of object-oriented
programming using inheritance. You can write a new class and
make it inherit the behavior and the interface of an existing
one. (Multiple class inheritance is not allowed in .NET, but
one class can implement as many interfaces as it needs.)
If you have a function you want to share across
applications, nothing is easier than writing a new class. This
is just one of the features of .NET and OOP. In the context of
ASP .NET, though, you have another interesting possibility
that's on par with code-behind classes, pagelets, and regular
classes: server controls. ASP .NET pages are made of server
controls—components that you declaratively place on a form and
tie together through code. Although the
System.Web.UI.WebControls namespace has a lot of classes to
offer, sometimes you may need to write your own. What's the
difference between server controls and pagelets? A
user-defined server-side control is an explicit class placed
in the Control class tree. In contrast, while pagelets are
treated like classes, they aren't written and exposed as part
of the framework.
Furthermore,
server controls have no built-in user interface. Their source
is not divided into layout and code sections. A pagelet looks
like a page that is embedded in another one and then treated
like a control. By contrast, a server control is a .NET class
specifically designed to work within an ASP .NET page. This is
due to the way ASP .NET server controls are inherited—they use
the Control class as a base. This means that they inherit the
ability to be included on a Web Form as well.
The code
in Figure 7 illustrates a very simple
control built from a TextBox control. It differs from the
parent object only in that it displays default text instead of
an empty buffer. This control can be used wherever a TextBox
is accepted and can be used as the starting point for building
more specialized controls.
To host the
control on an ASP .NET page, you use the @Register directive
(see Figure 8). As you can see, the derived
control automatically supports all the properties and methods
of the base class, including CssClass and, of course, the
runat directive.
A pagelet doesn't
need compilation prior to use. A control, however, must be
explicitly compiled to a library and placed in the Bin
directory of the Web server. For more on server controls, see
the Visual Programmer column in the October
2000 and January
2001 issues of MSDN Magazine.
Next Month
There's a lot
more to say about ASP .NET custom server controls, and I'll
look at them in more depth in my next column. In particular,
I'll be building several progressively more complex controls
to illustrate how powerful inheritance can be. One of the
examples I will show next time illustrates how to build custom
controls that have children, like a DataGrid.
Send questions and comments for Dino to cutting@microsoft.com.
|