R
ecently, I've thrown myself into the study of ASP .NET and ADO .NET. While plowing through these topics, I remembered the Cutting Edge column I wrote for the December 1999 issue of Microsoft® Internet Developer. In it I discussed Windows® Script Components (WSC), focusing on ASP components in particular. With the public beta of ASP .NET available, it's time for another look at the concept behind ASP components.
As I said in that earlier column, a script-based COM component written for use with ASP knows how to interact with the page's object model. In addition, the component provides its own set of methods and properties for easy programmability. As a result, you have server-side objects exposing a programming model that is easy to maintain, read, understand, and extend. Some readers have argued that the performance of this architecture pales in comparison to that of ASP pages that employ more traditional techniques to improve maintenance and reusability. They say that when used in place of scripted COM objects, server-side includes and script functions save the overhead associated with COM initialization, even though they lead to spaghetti code rather than object-oriented code. In terms of raw performance, it's hard to disagree. Nevertheless, the idea behind ASP script components was neat and smart, albeit somewhat ahead of its time.
Today, developers tend to think of Web applications and Web sites as very different animals, and it's easy to see why. A Web site is frequently made up of many smaller applications. With .NET, however, they can be thought of as a single entity—software is software, no matter what protocol or URI you're using.
In this column, I'll look at the component model in ASP .NET. I won't spend much time describing the class diagrams. Instead, I will concentrate on design issues, the runat attribute, and the intrinsic characteristics that distinguish the various families of ASP .NET server-side controls. After reading this column, you should have a clearer idea of which controls to use based on your existing (or legacy) code, your goals, and your needs. You'll also have a better understanding of the intrinsic elegance of the ASP .NET infrastructure.
The ASP .NET Extensibility Model
The pervasive use of components within ASP .NET is the key to its extensibility model. Everything you program within ASP .NET is a component, and everything you can manipulate programmatically within ASP .NET exposes itself as a component. OK, but what kind of component? Generally speaking, you've been able to create two families of components for ASP: JavaScript objects and COM objects. JavaScript is portable and lightweight while COM is rigorous, fast, platform-specific, and resource-intensive.
However, you pay a price for using JavaScript objects. First, JavaScript code is interpreted, so it can seriously decrease overall performance. Second, you're forced to choose just one language and stick to it—so your entire team will need to be skilled in that one language. Furthermore, the script code talks to the parser, which in turn talks to the ASP environment, which is just an application running in the context of Microsoft Internet Information Services (IIS)—the Web server. The overall architecture is fragmented rather than modular, with channels connecting the various pieces that sooner or later result in bottlenecks.
JavaScript, or even better yet, the subset of it that complies with the ECMAScript specification, is both portable and cross-platform. The JScript runtime knows how to marshal objects, and these objects are not only very lightweight but also have an extremely small memory footprint.
In contrast, COM objects are platform-specific. (Actually, this is not such a big issue since they run on the server, where you can be certain of the hardware and software configuration.) Instantiating COM objects is significantly more resource-intensive, especially when your objects are physically separated from the runtime. Each COM object you instantiate is a de facto object that utilizes the system services as a client. A COM object that you might call from within your ASP pages is never a native part of the IIS environment. The only exceptions to this rule are the ASP intrinsic objects that are created by IIS and injected in a scripting context in which page code blocks are evaluated.
COM objects outperform script functions, thanks to their compiled binary code. Hence, it's no coincidence that one of the most famous rules for good ASP code is to take as much script code out of the pages as possible. But where do you put this code, if not in compiled COM objects?
Also, COM objects can be written in a variety of languages, shifting the spotlight to functionality rather than syntax.
In short, there are good reasons to use each technology. JavaScript objects are lightweight, easy to code, and straightforward to install and uninstall since they don't have to deal with the registry. COM objects are compiled, fast, and don't force developers to adopt a language they don't like.
However, if you want a universal platform on which to build net-aware and net-enabled applications, you need a new component model. This model is based on, and powered by, the Microsoft .NET Framework.
How Does XML Fit in?
Before I take a look at the .NET common language runtime (CLR) and how it relates to ASP .NET, let's stop to reflect on XML and its offspring. In Cutting Edge in the July 1999 issue of Microsoft Internet Developer, I used an XML vocabulary, rather than plain HTML, to glue the various parts of ASP pages together. The following pseudocode shows this method:
<%
strHtml = strHtml & "<dino:MENU>"
For Each popup In MenuItems
strHtml = strHtml & _
"<dino:POPUP " & _
"Text=" & Quote(popup.Caption) & " />"
Next
strHtml = strHtml & "</dino:MENU>"
%>
As a result of this code, the browser receives something like this:
<dino:MENU>
<dino:POPUP Text="File" />
<dino:POPUP Text="Edit" />
<dino:POPUP Text="Tools" />
</dino:MENU>
This XML code can be translated into plain HTML through a client-side script or, better yet,
through XSLT transformations. This solution works great, especially with browsers that know how to handle XML and recognize XSL scripts. At the present time, only Microsoft Internet Explorer 5.0 and higher supports this.
The XML and XSL combination seems to be a good way to extend ASP beyond its present capabilities. However, to be adopted as the primary server page language, XML and XSL need some additional capabilities. In particular, they need the support of a server-side engine that is capable of caching XML code and transforming it to HTML as needed before sending text back to the browser.
The XSL ISAPI filter is a tool in IIS that does just that. I hope it will be integrated with ASP .NET in the near future. The XSL ISAPI filter currently works only on Windows 2000 Server. It basically detects both the calling browser and any linked XSL and performs any required conversion. With this tool installed, you can manipulate XML on the server.
One way to take advantage of XML on the server is to think of an application as a series of pages written with a particular XML vocabulary. Each page represents a state of the application. When an application is built this way, you can have total separation between data and views. You'll also have a language that is much closer to your business and data than plain HTML could ever be.
This approach has some flaws though. In terms of performance, there's no question that XSL transformers need optimization and a redesign to allow for caching and per-session data storage. Rewriting the XSL processor on your own isn't impossible, but it's not worth the effort. Aside from all the performance issues that could arise, XSL remains a difficult language to write in. XSL scripts aren't particularly readable, and they often need very special support from development tools.
The real problem with XML and XSL is the extensibility model. You can use XML to describe virtually anything, including components, but you cannot alter its very nature, a text-based data description language. How do you write new components with XML or XSL? You can describe or even render them, but you can't write executable code with XML or XSL alone. XML and XSL are extremely useful tools for putting together and building a brand new development platform, but they are in no way the constituent elements of the platform that applications code against.
The Common Language Runtime and ASP .NET
ASP .NET runs in the context of the new .NET common language runtime (CLR). The CLR offers a set of native components that follow a brand new component model. Everything you use from .NET works the same way and exposes a consistent programming interface. The CLR manages the execution of code and provides handy services for programmers. Like any other .NET-compliant application, ASP .NET takes advantage of this environment and mirrors the facilities of the CLR to its Web applications.
ASP .NET inherits many of its features from the .NET runtime infrastructure. ASP .NET benefits from .NET's cross-language integration and exception handling, automatic memory management, enhanced security, versioning and deployment support, debugging and profiling services. However, the most important aspect is the simplified model for component interaction. All elements you manipulate in an ASP .NET page are instances of .NET objects and derive from classes of the Base Class Library (BCL).
To some extent ASP .NET is similar to client-side scripting. Any element can be identified through an ID, and can have code written against it. While this is rather natural on the client, it's new on the server. More importantly, this is made possible by the component-based architecture underlying .NET and ASP .NET.
Turning HTML Elements into Server Controls
A common task for an ASP(.NET) page is generating the text for an HTML element and filling it with some data, like so:
Response.Write("<A id=myAnchor>Click</A>");
Within an ASP page, there's no way for you to code against the myAnchor element. It sits there,
frozen and lifeless, good only to be packed up and sent to the browser. The myAnchor element can spring to life and start accepting instructions only on the client.
If you need to dynamically determine the value of the anchor's href attribute, you should organize your ASP code accordingly by waiting to get the needed value then properly formatting the string for Response.Write.
strHtml = "<A id=myAnchor "
strHtml += "href=" + strHref;
strHtml += ">Click</A>";
Response.Write(strHtml);
This code will work unchanged in an ASP .NET page. However, if the extension of your page is
.aspx—which makes it an ASP .NET page—you can give life to the anchor element on the server, too. The key instruction is the runat attribute. Any HTML element that gets processed by ASP .NET is managed through a .NET object if it has the runat attribute set to server:
<A runat="server" id="myAnchor">Click</A>
This means that you can use the ID attribute to code against it on the server. For example, the following code
<A runat="server" id="myAnchor">Click</A>
allows you to do this:
<SCRIPT runat="server" language="VB">
Sub Page_Load(source As Object, e As EventArgs)
myAnchor.href = "http://www.asp.net"
End Sub
</SCRIPT>
You can set the element's properties, as well as invoke its methods, through scripting.
Notice that this requires the element to be declared as a server-side control through the runat attribute. In addition, you should manipulate it with script code in the Page_Load event or with inline script code that appears before the element is actually inserted into the page:
<HTML> <BODY>
<% myAnchor.href = "http://www.asp.net" %>
<A id="myAnchor" runat="server">Click</A>
</BODY> </HTML>
However, if you place the <%...%> code block after the <A> tag, the link won't work.
<HTML> <BODY>
<!--THIS WON'T WORK!-->
<A id="myAnchor" runat="server">Click</A>
<% myAnchor.href = "http://www.asp.net" %>
</BODY> </HTML>
Whenever the page parser encounters a tag in an ASP .NET page, it checks for the runat attribute.
If this attribute is not set, then the tag string is treated as plain text and added as a string to the output stream to be sent to the browser. Otherwise, if the tag is marked as runat=server, the parser creates an instance of the corresponding object (see Figure 1). There's an ASP .NET object for every possible tag that you can use. An ASP .NET object is activated according to the value of runat.
When runat=server is specified, the page object that represents the ASP .NET page is added to the page's control hierarchy. From that point on, the element is programmatically accessible. Therefore, the ID attribute is mandatory. For performance reasons, the get accessor method is configured to create an instance of the object only on first use. Figure 1 shows the BCL objects that will be instantiated when you set the runat attribute for their corresponding HTML elements. If you assign the runat attribute to other tags, such as <DIV>, <SPAN>, or <FONT>, ASP .NET will use the generic HTMLGenericControl class instead.
Another interesting feature of the runat attribute is that you can bind the HTML element to page-level events and handle them on the server. For example:
<script runat="server" language="VB">
Sub myAnchor_OnClick(Source As Object, e As EventArgs)
' do something
End Sub
</script>
•••
<FORM runat="server">
<A id="myAnchor" runat="server"
OnServerClick="myAnchor_OnClick">
Click</A>
</FORM>
When someone clicks on the hyperlink, an onclick event is fired on the server and
resolved by running the specified procedure. All the elements involved must be on the server side and, more importantly, the element that will handle the event must be contained in a <FORM> tag. This particular feature is called postback events. This is a topic that I will be looking at more closely in an upcoming column.
ASP .NET Server Controls
There are basically two families of server controls in ASP .NET: HTML controls and Web controls. The HTML controls belong to the System.Web.UI.HtmlControls hierarchy (see Figure 2). Figure 3 shows the diagram for Web controls whose path is System.Web.UI.WebControls. Notice that in Figure 3 I'm not considering abstract classes. There an abstract class called BaseDataList groups DataGrid and DataList. Another abstract class, ListControl, encompasses CheckBoxList, RadioButtonList, DropDownList, and ListBox. A third abstract class called BaseValidator contains all validation controls.
Figure 2 HTML Controls
Within the whole set of HTML and Web controls there are four different logically related groups of controls: HTML intrinsic controls, rich controls, validation, and bound list controls. HTML controls represent the bridge between ASP and ASP .NET. Normally, plain HTML code is not programmatically accessible while the page source code is being processed on the server. The only exception to this is when a control is explicitly marked as runat=server. When this happens, the HTML element is initialized through the corresponding HTML control, as shown in Figure 2. This is a good way to ease the porting of existing code from ASP to ASP .NET.
Figure 3 Web Controls
By simply adding some runat attributes, you can give life to otherwise static strings and quickly enter the world of the ASP .NET programming model. As I mentioned earlier, this model is heavily component-based. In other words, every element can be seen as a programmable set of properties and methods that you can invoke at will. This model clearly differs from ASP, where you build the output stream piece by piece. It's important to keep in mind that HTML controls are strictly related to the runat attribute. But they are only the beginning of the ASP .NET programming story. From the programmer's perspective, Web controls are much more interesting.
Web Controls
I cannot help but think back to WSCs whenever I look at rich Web controls in ASP .NET. Functionally speaking, there's nothing you can do with Web controls that you can't do with old-style ASP pages. You can easily implement data-bound controls using WSCs. You can also set up all those nice features that make the asp:DataGrid control particularly attractive, such as column binding, datagrid behavior, element selection, and in-place editing, to name a few. But you have to write or purchase a custom component to get them. ASP .NET, on the other hand, provides you with many of those ready-to-use components for free. Furthermore, since these objects are fully integrated with the underlying framework, you don't have to pay the setup price (registry entries and so on) that you do with regular COM objects such as WSCs.
Another significant difference between WSC and ASP .NET is that ASP .NET allows you to program in a more declarative way. ASP .NET utilizes a new page parser built completely from scratch. It now understands namespaces and supports many new tags. The structure of the page parser is similar to the one that Internet Explorer employs to detect and process custom HTML tags associated with client-side behaviors, allowing you to use syntax like this:
<asp:Table runat=server>
<asp:TableRow>
<asp:TableCell> One </asp:TableCell>
</asp:TableRow>
<asp:TableRow>
<asp:TableCell> Two </asp:TableCell>
</asp:TableRow>
</asp:Table>
The <asp:Table> tag generates an instance of the Table Web control.
Save this code as an .aspx page, point your browser to it, and you'll see the following in the source code:
<table>
<tr>
<td>
One
</td>
</tr><tr>
<td>
Two
</td>
</tr>
</table>
In my opinion, the <asp:Table> syntax is more readable.
And when you consider that the <asp:Table> supports a number of additional attributes such as different colors for alternate rows, gridlines, and a consistent programming interface to set colors and fonts, you'll really be hooked. In the end, you're not creating a new table, you're declaring one. Using the runat=server attribute is the key that enables object accessibility on the server.
You may ask why you should favor the <asp:Table> syntax over the old HTML syntax, or what the difference is between Web controls and HTML controls. HTML controls are the components that extend the behavior of typical HTML elements on the server. Web controls are richer controls that build a higher level interface on top of typical HTML functionality. A datagrid, for instance, is not a native HTML element, yet it represents commonly used Web functionality. Web controls make these long-needed features available using the same declarative programming model you know from HTML. There are a number of .NET objects behind Web controls. If you define controls as tags on a page, you don't need to worry about these underlying objects. You always have the choice of defining them programmatically, as shown in Figure 4.
There's no filter between HTML controls and the output they generate. However, the output of Web controls is subject to system inspection and adaptation. There's a .NET layer that can help Web controls generate the right code for the browser that called them. For example, a datagrid control could offer editing capabilities through the MSHTML Editing control or Dynamic HTML. Right now, this is commonly used for validation controls. Validation controls, in fact, need to use client-side script code to check input values against rules and fixed ranges. The ability to run client-side scripts depends on the browser. You can affect the way in which Web validation controls work by declaring the type of browser you want to target:
<%@ Page ClientTarget="DownLevel" %>
By setting the ClientTarget attribute to DownLevel you force the asp:xxxValidator to do any validation
on the server with a new round-trip for each control to validate. You can do the opposite as well:
<%@ Page ClientTarget="UpLevel" %>
In this case, the code that ASP .NET generates always attempts to validate on the
client through scripting, whether the browser supports it or not. If you don't set the ClientTarget attribute, then ASP .NET first detects the browser's capabilities before generating proper code. You can save valuable server time by forcing ASP .NET to always target a particular category of browser.
If you know how behaviors work on the client (see the December 2000 and January 2001 installments of Cutting Edge), you're halfway to understanding how ASP .NET implements Web controls. Any element that belongs to the ASP namespace is automatically associated with a .NET component as long as it has the runat=server attribute set.
Extensibility of Web Controls
As you may have guessed already, the low-level association between the namespace-tag pair and a component makes it really easy for you to extend the ASP .NET native set of components. You write your component more or less as you used to do with WSCs or COM. Then you import it in the page and establish an association between a namespace/tag name and the component itself.
The following code imports all the necessary definitions for a newly created component:
<%@ Register TagPrefix="dino" Namespace="Expoware" %>
This tells the ASP .NET runtime that whenever it encounters a tag like <dino:xxx>
it should scan the specified namespace, searching for the given class. What's the class? It is:
<dino:MyClassName ... />
where MyClassName is just the name of the root class of the component.
In C# this assumes some wrapper code, like the following:
namespace Expoware {
public class MyClassName : Control
{ ... }
}
The extensibility model puts both Web controls and custom controls on the same level. Better yet,
the component model of ASP .NET manages just one category of components—no matter who wrote them. And the overall rules for their behavior have been set by the .NET Framework.
To some extent, this component model goes beyond the boundaries of the Web server. When the browser is Internet Explorer 5.0 or higher, the ASP .NET server controls have a straight client-side counterpart in element and attached behaviors. I like this idea so much that I think Microsoft should give Internet Explorer behaviors a name such as "IE Client Controls" to help people understand the component model of the .NET platform and show how they map to ASP .NET server controls.
Next Month: Data Binding
Web controls are a key feature of ASP .NET and the result of the effort of the various teams to produce a server framework that can respond more flexibly to user requests. People are using ASP to generate all sorts of reports, getting rows of data from varying data sources. Any system-provided tool or component that can help format, display, and manipulate this data is a welcome addition.
Next month, I'll dig into ASP .NET server-side data binding and discuss how to use and improve all the data access Web controls. Stay tuned!