ONDotNet.com    
 Published on ONDotNet.com (http://www.ondotnet.com/)
 See this if you're having trouble printing code examples


Learning ASP.NET for the ASP Developer - Part 3

by Neel Mehta
12/06/2004

Part 3: Scaling Up

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.

ASP.NET "Includes"

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:

Code to pull out
Figure 1. Code to pull out.

Related Reading

ASP.NET in a Nutshell
By G. Andrew Duthie, Matthew MacDonald

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#:

  1. Every statement in C# must end with a semi-colon
  2. C# comments begin with //; multi-line comments are enclosed within /* ... */
  3. Use Session["YOUR_SESSION_VARIABLE"] with the square brackets [ ... ] instead of parenthesis ( ... )
  4. In C#, variables are defined with the type first as in string MY_STRING_VAR = "initial value" while in Visual Basic we use Dim MY_STRING_VAR as String = "initial value"
  5. String concatenation is done using "+" in C# ("&" in Visual Basic)
  6. Logical operators (AND, NOT, OR, etc.) are similar to JavaScript (&&, !, ||, etc.)
  7. To access individual fields of records extracted from the database, you can use 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:

Multiple pages using code-behind
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.

Doing Something in ASP.NET Which is Cumbersome in Classic ASP

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.

Summary

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.

Related Links and References

  1. Page Directive: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconPage.asp
  2. Web Matrix: http://www.asp.net/webmatrix/default.aspx?tabIndex=4&tabId=46
  3. Custom User Controls: http://www.asp101.com/lessons/usercontrols.asp
  4. ASP.Net in a Nutshell,  G.Andrew Duthie, Matthew MacDonald, http://www.oreilly.com/catalog/aspdotnetnut/index.html, O'Reilly.com, June 2002
  5. ASP.NET Cookbook, Michael A. Kittel. Geoffrey T. LeBlond, http://www.oreilly.com/catalog/aspnetckbk/index.html, O'Reilly.com, August 2004
  6. The Mono Project, http://www.mono-project.com.

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.