In the final part of this tutorial, we will demonstrate how to construct large scale ASP.NET websites. In the previous tutorials of this series, we saw how to build single ASP.NET pages where all the code for a page was written on the page itself. This approach can quickly get tedious when you have code that is common across several pages. Thus, one of the most important elements in sites with a large number of pages is the ability to share code. Hence, we first describe ways in which to write code that can be used by multiple pages without the need for repeating the code on each page. Then, we will outline an efficient way to improve the performance of serving ASP.NET pages, a task that would be quite cumbersome in classic ASP. Like the prior two parts, our emphasis is using our ASP knowledge to develop a conceptual understanding of ASP.NET. As I had mentioned earlier, we are not looking for a line-by-line conversion method from classic ASP to ASP.NET as much as to leverage our existing classic ASP knowledge to learn ASP.NET.
In classic ASP, you can write code in a file and then "import" it in on another
page by using <!-- #INCLUDE virtual = "Name_of_file_that_has_common_code.asp" -->.
This statement has the effect of "pasting" the common code in the place of
the <!-- #INCLUDE ... -->.
In ASP.NET, on the other hand, there is an equivalent way to INCLUDE code, but the implementation
details are quite different.
We saw, in the first part of this tutorial, that ASP.NET has "intelligent" tags or controls which "know" how
certain data has to be rendered on the browser. (The <asp:DataGrid> is an example of a web control which
"knows" that data associated with it is rendered within an HTML table.)
In addition, ASP.NET allows you to define your own markup tags or custom
controls. The presentation markup "instructions" for these custom controls is
specified in another file (with an .ascx extension with a c
instead of the p.
You can think of the c in the .ascx extension to stand for custom control.)
Only the tag for the custom control is written on
the .aspx page just like any other control or presentation tag.
But, when the web page with the .aspx page is sent to the
browser, the custom control is replaced by the markup information specified in the
separate file. Thus, you can use this
ability of ASP.NET to define your own controls to "bring in" code from another file.
On the .aspx page, you need to specify which .ascx
page the control is associated with along with the name you want to call this custom
control in your .aspx page. The reference to the .ascx page
is made using the <%@ Register ... %> directive. The
tag names and prefixes used to "call" the custom control are specified as attributes of
this directive.
Finally, the page with an .ascx extension, on the other hand,
has a <%@ Control ... %> directive instead
of the <%@ Page ... %> directive on .aspx pages. Apart from this
difference, a page with an .ascx extension is very similar in structure to
a page with an .aspx extension.
Below is an example of an .ascx page and how it's referenced in a page with
an .aspx extension.
SimpleCustomControl_vb.ascx
<%@ Control Language="VB" %>
<script runat="server">
Public Color As String
</script>
<font color="<%=Color%>">This text will replace the custom
control tag on the .aspx page.</font>
SimpleCustomControl_vb.aspx
<%@ Page Language="vb" %>
<%@ Register TagPrefix="My" TagName="NewTag" Src="SimpleCustomControl_vb.ascx" %>
<html>
<head>
</head>
<body>
<form runat="server">
<p>
<My:NewTag color="red" runat="server" />
</p>
</form>
</body>
</html>
Note how the Color attribute on the My:NewTag control in the .aspx can
set the color of the text in the custom control.
While the example shown is quite basic, custom controls can be as advanced as you like. We can even use web controls and programmatically access and modify their attributes.
The important thing to bear in mind, though, is that, similar to the classic ASP INCLUDE file, you only have to make
changes to the markup code in the .ascx file and not on every page that references the custom control. Page navigation elements are, thus, often defined as custom
controls. For more details and an excellent and quick introduction to custom
controls, see this asp101 lesson.
Thus far, we have talked about the structure of an ASP.NET page and how we can achieve separation of server side code from presentation markup details. We have seen that ASP.NET web controls can be powerfully effective in reducing the amount of HTML and JavaScript that we have to explicitly write. We have also seen how to define custom controls, which is similar to the classic the ASP server side includes. Moreover, ASP.NET has another way of pulling out common code to a separate page. The page with the pulled out code is called the "code-behind" page, which simply means the "server side code behind the ASP.NET page." Pulling code out and placing it in a separate file has the advantage of further separating presentation from code. Graphically, what we want to accomplish is shown below:

Figure 1. Code to pull out.
|
Related Reading
ASP.NET in a Nutshell |
While pulling the script side code into a separate code-behind file, let us make a slight change. We can certainly continue to use Visual Basic as our server side script language. Let us take this opportunity, however, to switch languages to C# (pronounced c-sharp).
The code-behind file takes the extension of the scripting
language used. Since we are using C#, the extension of the code-behind file is .cs. If
we were using Visual Basic, the extension of the code-behind file would be .vb.
In classic ASP, when moving code to an Include file, we literally "cut-and-pasted"
from the "mother" .asp page to a separate INCLUDE file. In ASP.NET, the code that
gets moved to the code-behind page is also pretty much the same as in the "mother" .aspx page
but is structured a little differently.
When pulling the script-side code to a separate file, the Page directive on the
original .aspx file
does not migrate over--it stays on the .aspx file itself
and forms the glue between the .aspx file and the code-behind .cs file.
The src attribute on the Page directive specifies the name of the
code-behind file.
Code-behind files always have a namespace
and at least one class specification within it.
Thus, the Page directive also has an attribute
that specifies the namespace and class from the code-behind
page that gets inherited on the .aspx page. These specifications make the code
within the referenced class "available" to the .aspx page. Below is an example of a
Page directive which references a code-behind file:
<%@ Page Language="C#" Src="DataGrid_Codebehind.aspx.cs"
Inherits="MyNamespace.MyClass" %>
The structure of the code-behind behind is as follows:
namespace MyNamespace {
using System;
public class MyClass {
// Script code goes here
}
}
Note how the Inherits attribute on the Page directive references the namespace and
class on the code-behind page. Once a class has been defined, you can now place the code from the original .aspx
page.
Since the code-behind file is a totally separate
file from the original .aspx file, any web controls that were being referenced in
the original script-side block cannot be accessed without explicitly importing the System.Web.UI.Controls class.
(In C#, the import directive is replaced with the using namespace; statement.)
The class that uses these controls must inherit
the SystemWeb.UI.Page class.
Finally, you must declare the controls that will be accessed
in your code. While declaring the controls, you make them protected, which
means that they are accessible only within the code-behind code and the presentation
page that inherits from it. Class inheritance will then make these controls
available in the code-behind code so that you can access the controls and associate
records to them, etc., just as you would on the .aspx page itself.
using System;
using System.Web.UI.WebControls; // import web controls
namespace MyNamespace {
public class MyClass : System.Web.UI.Page {
protected DataGrid DataGrid1; // Declare controls
void Page_Load() {
// Script code goes here
} // end of Page_Load function
} // end of MyClass
} // end of MyNamespace
At this point, you can now "cut-and-paste" the code from the original .aspx page
into the code-behind page. Below is the complete code-behind code for assigning records
from a database table to a DataGrid. You can see the
similarity between the original code in the .aspx file and how it's specified in the code-behind file.
DataGrid_Codebehind.aspx.cs
using System.Web.UI.WebControls; // import web controls
using System.Data; // import for DataSet class
using System.Data.SqlClient; // import for SQL Server classes
namespace MyNamespace {
public class MyClass : System.Web.UI.Page {
// Controls from the HTML page
protected DataGrid DataGrid1;
void Page_Load() {
// Set up the SQL Server database connection
string ConnStr = "server='(local)'; trusted_connection=true; database='Demo'";
DataSet RecordSet = new DataSet();
// Now, pull the records from the database
string SQL = "Select * from Books";
SqlDataAdapter RecordSetAdpt = new SqlDataAdapter(SQL,ConnStr);
RecordSetAdpt.Fill(RecordSet);
// Set the data grid's source of data to this recordset and bind
DataGrid1.DataSource = RecordSet.Tables[0].DefaultView;
// Finally, bind this data to the control
DataGrid1.DataBind();
} // end of Page_Load()
} // end of MyClass
} // end of MyNamespace
The code in the .aspx file is:
<%@ Page Language="C#" Src="DataGrid_Codebehind.aspx.cs"
Inherits="MyNamespace.MyClass" %>
<html>
<head>
</head>
<body>
<form runat="server">
<asp:DataGrid id="DataGrid1" runat="server"></asp:DataGrid>
</form>
</body>
</html>
As you can see from comparing the code above to the DataGrid code in Visual Basic
described in Part 1,
switching to C# is not a major undertaking. In fact, you should
have no trouble following the C# code; in this case, the C# code is almost
the same as the Visual Basic code. The reason for this close similarity is that we
are essentially just using properties and methods of imported objects. In general,
however, you will see greater differences. But, in a very loose sense, C# is a lot
like JavaScript. Thus, instead of the if ( ) then ... endif construct in
Visual Basic, you have if ( ) { ... } construct in C#. A few other salient
points of C# that you will find useful as you transition from Visual Basic to C#:
//; multi-line comments are enclosed within /* ... */
Session["YOUR_SESSION_VARIABLE"] with the square brackets [ ... ] instead
of parenthesis ( ... )
string MY_STRING_VAR = "initial value" while
in Visual Basic we use Dim MY_STRING_VAR as String = "initial value"
AND, NOT, OR, etc.) are similar to JavaScript (&&, !, ||, etc.)
Record["FIELD_NAME"] where Record is
of type DataRow as in DataRow Record
(This is only a "cheat-sheet" overview of C# and does not do justice to the full range of programming constructs available, but it should get you started.)
Finally, a code-behind file can be referenced by several .aspx pages as
the following graphic shows:

Figure 2. Multiple pages using code-behind.
Hence, the code-behind files serve a similar purpose, albeit implemented differently, as the Server Side Includes in classic ASP, in that common code is not duplicated.
If you have certain classes in your code-behind code which are being used on the majority
of your web pages, then it may be more efficient to create what are known as assemblies and
then import these assemblies like the other namespaces. Creating and importing
your own custom assemblies will reduce the amount of code in your code-behind
page. Building assemblies, though, is beyond the scope of this article. For more information
on how to create and register assemblies, please refer to the book ASP.NET in a Nutshell.
Finally, with this introduction to code-behinds,
I'd like to end this section by suggesting why the term "forms" is so prevalent in ASP.NET. "Forms" are
reminiscent of programming in Visual Studio where you would build the graphical user
interface (GUI) by dragging and dropping controls onto a "work-area" called a "Form."
The code that drives the form is placed on a separate window which you access by right-clicking on the
"Form" in the Visual Studio Project pane and
selecting "View Code." Visual Studio managed the relevant
associations internally, so you didn't need Page directives. Thus, the terminology
from Visual Studio is carried over into ASP.NET. Hopefully, this explanation should
help clear some of confusion between ASP.NET forms and standard HTML forms.
|
I don't want to leave you with the impression that ASP.NET's power lies in its ability to simplify, reduce, and better structure your code only. So, in this section, I am going to outline an application that would be very cumbersome in classic ASP yet would be something you might consider using immediately on your site. The feature I would like to discuss is Page or Output Caching.
Suppose you have a page on your site which is populated from a database. Each time this page is requested, the corresponding records are pulled out before the page is delivered. With Page Caching, though, the output is cached so that when subsequent requests are made, the time-consuming database extraction is avoided, and the page is served from the cache, thus speeding up its delivery. Even if you have inefficiently coded your page, caching the page output will minimize performance degradation.
In ASP.NET, to cache a page's output, you simply specify the OutputCache directive
after the Page directive:
<%@ Page Language="C#" %>
<%@ outputcache duration="60" varybyparam="None" %>
The OutputCache directive has two attributes: duration and varybyparam.
The duration attribute specifies in seconds how long the page output is cached after
it's first request. The first request after this time will cause the page to be re-evaluated,
and then it will be re-cached. The duration is specified in seconds. So, in this example,
the page will be cached for one hour after it's first requested.
The second attribute, varybyparam, allows the page to be cached based on
query string parameters. Very often, you will have a page whose content depends on
what is passed on the querystring. In these kinds of pages, it is pointless to simply
cache the page without knowing what is on the querystring, since the code will
not know how to extract records from the database. The varybyparam attribute
allows you to cache the page based on a subset of the parameters passed on the querystring.
For instance, suppose you have a page which reads a parameter "City" on the querystring
and then displays some output related to that city. Then, if varybyparam= "City" and
a request is made to http://YOUR_PAGE?City=Dallas,
then the page output for Dallas will be cached so that subsequent requests for Dallas
will not have to make the database call. Thus, multiple versions of this page corresponding
to different cities is cached.
In classic ASP, on the other hand, to cache a database extraction of records, you
use Application variables. Then, each time the page is requested,
a check against another application variable has to be made to
determine whether the caching duration is over. If the request is made before the
duration is over, then the records are taken from the application variables
instead of making an expensive database call. At any rate, though, you have to make
some modifications in the code's logic flow to introduce the application variables.
In ASP.NET, on the other hand, all you have to do is specify the OutputCache directive
without affecting the rest of the code, thus making it a snap to cache your page's
output.
For pages whose output does not depend on the querystring, the varybyparam attribute
must be set to None. This attribute is mandatory; hence, if there is no dependence
on the querystring, you have to still set it to None.
The above discussion is just an outline of the Outputcache directive. For
more details, please refer back to ASP.NET in a Nutshell.
While page caching is certainly attractive, by itself, it's not particularly useful. The reason is that if the information in the database has changed in the time interval between the first request to the page and the end of the cache duration, then the new information is not reflected until the caching duration has elapsed. You can, of course, force a re-cache by opening your page and re-saving it. However, this solution is not elegant. It would be better to have a way to clear the page from cache on demand. So, in the remainder of this section, I will outline a method which will allow you to clear a page from cache using a browser. This technique has the advantage that the page can be cleared without the need to manually open the file.
The approach I take is to create a web page which contains a list of checkbox options corresponding to the files that are in a specific folder. A Clear Page(s) from Cache button will, when clicked, remove those pages from cache which have been checked. So that I can present the main ideas without a lot of clutter, I assume that all the pages for our site reside in one folder and that every page's output is not dependent on the querystring. Thus, I can simply remove them from cache without worrying about enumerating any querystring parameter values.
We give an outline of the code to clear pages from cache below. The main purpose is
to show you the program structure at a high level and to introduce a few more aspects
of ASP.NET rather than to go through the code line-by-line. Let's start by defining
the presentation .aspx page where we'll define the CheckBoxList control which
will display a list of checkbox options, each corresponding to a file in the folder.
<%@ Page Language="C#" Src="SimpleClearCache.aspx.cs" Inherits="MyNamespace.MyClass" %>
<html>
<head>
</head>
<body>
<form runat="server">
<asp:Label id="Label1" runat="server">Label</asp:Label>
<br />
<asp:CheckBoxList id="FileName" runat="server"></asp:CheckBoxList>
<br />
<asp:Button id="Button1" onclick="Submit_Click" runat="server"
Text="Clear Page(s) From Cache"></asp:Button>
</form>
</body>
</html>
This page has a code-behind page associated with
it which has all the code to clear pages from cache.
The key to clearing a page from cache is to make use of the RemoveOutputCacheItem of
the HttpResponse class. Below is an annotated full listing
of the code-behind page:
//SimpleClearCache.aspx.cs
using System; // for the Object on Submit_Click
using System.Web; // for HttpResponse class
using System.Web.UI.WebControls; // Web controls
using System.IO; // File system access
namespace MyNamespace {
public class MyClass : System.Web.UI.Page {
// Declare controls from the presentation block
protected CheckBoxList FileName;
protected Label Label1;
protected Button Button1;
void Page_Load() {
if (! IsPostBack) {
// Enumerate files in the directory and
// databind to CheckBoxList
String [] Files = Directory.GetFiles(Server.MapPath("/"));
Array.Sort(Files); // Sort files alphabetically
Label1.Text = "<br>Number of files: = " + Files.Length;
FileName.DataSource = Files;
DataBind();
} // end of IsPostBack if-block
}
public void Submit_Click(Object sender, EventArgs e) {
Label1.Text = "<p>The following pages are removed from " +
"the cache:<p>";
// Determine the selected item
string TempFileName = "";
for (int i=0; i < FileName.Items.Count; i++) {
// Loop through all files and determine which
// ones have been selected.
TempFileName = ""; // Reset the TempFileName string
if (FileName.Items[i].Selected) {
// Get the current file name
TempFileName = FileName.Items[i].Text;
// Strip the root from path using
// Regex.Replace(input_str,
// pattern_str,
// replacement_str)
TempFileName = TempFileName.Replace(
Request.ServerVariables["APPL_PHYSICAL_PATH"],
"/");
// Next, replace all the slashes
// to the proper direction
TempFileName = TempFileName.Replace("\\", "/");
Label1.Text += @"<a target=""_blank"" " +
"href='"+TempFileName+"'>" +
TempFileName + "</a>" + ":<br>";
// Remove page from cache
HttpResponse.RemoveOutputCacheItem(TempFileName);
}
}
} // end of Submit_Click
} // end of MyClass
} // end of MyNamespace
This solution is not ideal, though. A better way would be to make a request to the page after the page is removed from cache. As a result, it will be the system which will bear the brunt of an initial slow response. But, all your site visitors will get the faster cached page. An even further improvement is to trigger the cache clearing code the moment the database is updated for that page. This way, every time the database content for that page is updated, the page will be automatically cleared from cache so that it can immediately start reflecting the new material.
Finally, there are a few other things which you can implement right away using the
concepts discussed in this article. Firstly, you can define custom error handling
pages without having to modify settings on the Internet Information Server (IIS), as
is the case for classic ASP sites. In ASP.NET, you can define the custom error handling
pages within a file, web.config, which is placed in the root folder of your
site.
Another control that can save you a lot of time is the calendar control.
With this control, you can display a month of events, say, with minimal amount of coding.
Lastly, ASP.NET makes it very easy to create images on the fly. So, if you'd like
to create a "My Profile" area on your website and have an image tab with the user's
name, then based on the user logged in, you can stream a dynamically created image
to the browser. So, each user will see his or her name on the image, yet there is no explicit
image on your site with that user's name. You can, of course, also cache that image
too using the techniques discussed above to speed up the performance. Refer to the ASP.NET
Cookbook for more details on creating dynamic images.
ASP.NET is significantly different from classic ASP. However, we can use our knowledge of classic ASP to learn ASP.NET. As we have seen, if we look at controls as an evolution of classic ASP, we could introduce some of the more advanced concepts, such as DataBinding, in a natural manner. We have also seen that controls offer a powerful way to separate presentation from code as well as write more efficient code.
The objective of this three-part tutorial was to quickly give you a bird's eye-view of ASP.NET. The idea was to let you "see" in a rapid manner what this technology has to offer. As a result, we have only skimmed the surface of ASP.NET. But my hope, however, is to have given you enough justification to further study ASP.NET and to motivate you to start migrating your classic ASP site to ASP.NET.
Finally, and perhaps surprisingly, ASP.NET is not limited to just Microsoft platforms. Using Mono (http://www.mono-project.com), you can implement ASP.NET sites on several other platforms such as Linux, MacOS X, and Unix. Thus, you can finally build cross-platform applications.
Neel Mehta has a doctorate from the University of Pennsylvania and is an ardent fan of .NET and sees it as one of the few technologies that will be around for some time.
Return to ONDotnet.com.
Copyright © 2009 O'Reilly Media, Inc.