Classic ASP Include Files in ASP.NET

Include files within classic ASP are about the only way to achieve some measure of code-reuse within frameworks that offer little or nothing by way of composition or inheritance. Judging by the number of questions in forums, there are still a lot of people moving across from classic ASP who are puzzled as to how to approach replacing include files within ASP.NET. A number of articles illustrate individually how to do so for site-wide layout reasons, including widgets, reusing global functions, or actually including the content of a file. However, I haven't been able to find one resource that brings all these together, hence the purpose of this article.

There are two primary reasons for include files in classic ASP. One is to reuse some kind of templated UI artifact (often containing dynamic functionality), such as navigation menu, header, footer, news panel or other widget. The other is to make common subroutines or procedures available across selected pages within the application. There is third reason, and that is to actually include the static content of a file as part of output within a page. While this could be considered similar to the first reason, the ASP.NET way of dealing with this is different, so I'll look at that last.

The first thing to understand is the difference between the way that classic ASP and ASP.NET are processed. When an asp file is processed by the scripting engine, all code is executed from top to bottom. If the processor meets an include directive, the contents of the included file are dynamically "melded" with it at run time, and treated as part of the host file. The key thing here is that each asp file is purely a container of some scripting instructions that are interpreted and executed each time the file is invoked by IIS. Once finished, all procedures, subs, variables etc are forgotten. ASP.NET applications on the other hand are compiled to dlls. An aspx file is not the same as an asp file. It's just an easy way to create a new class in your application - the class being of type System.Web.UI.Page. These classes are not compiled on each use, so adding new code at that point is not possible. References to external code need to be added prior to compilation.

Site-wide Layout

The following snippet is typical among classic ASP files:


<body>
 <div id="head"><!--#include file="includes/head.asp"--></div>
 <div id="nav"><!--#include file="includes/nav.asp"--></div>


The site-wide layout is defined by minimal html with the head, navigation, footer and other components injected via include files. This basic template needs to be copied and pasted into every page that makes use of the template. If a change needs to be made to the structure, that change needs to either be copied across every page, or included in one of the existing includes, perhaps as an include itself. ASP.NET provides Master Pages to cater for this, which allows all the template structure to be defined in one file - a .master file. The default Master Page includes two ContentPlaceHolder controls:


<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Site.master.cs" Inherits="WebFormsTests.SiteMaster" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head runat="server">
    <title></title>
    <link href="~/Styles/Site.css" rel="stylesheet" type="text/css" />
    <asp:ContentPlaceHolder ID="HeadContent" runat="server" />
</head>
<body>
    <form runat="server">
    
            <asp:ContentPlaceHolder ID="MainContent" runat="server"/>
 
    </form>
</body>
</html>



ContentPlaceHolders are areas that are "filled" dynamically with content at runtime. The content comes from web forms that make use of the Master Page. In VS 2010, the option to create one of these is clearly marked:

And the code that you get looks like this:


<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" 
                                          CodeBehind="ChildPage.aspx.cs" Inherits="WebFormsTests.ChildPage" %>
<asp:Content ID="Content1" ContentPlaceHolderID="HeadContent" runat="server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
</asp:Content>



If you attempt to place any markup or controls outside of the Content controls (which are automatically married up to their respective ContentPlaceHolder controls in the Master Page), you will receive an error. You can add as many ContentPlaceHolder controls as you like on the Master Page, but some thought should go into the site structure at the outset. Once created, child pages will only acquire new Content controls manually. Within the Master itself, you will define the structure of your template:


<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Site.master.cs" Inherits="WebFormsTests.SiteMaster" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head runat="server">
    <title></title>
    <link href="~/Styles/Site.css" rel="stylesheet" type="text/css" />
    <asp:ContentPlaceHolder ID="HeadContent" runat="server" />
</head>
<body>
    <form runat="server">
    <div id="wrapper">
        <div id="header">Some head content</div>
        <div id="nav">Some navigation content</div>
        <div id="leftcol">Some items that go into the left column</div>
        <div id="main">
          <asp:ContentPlaceHolder ID="MainContent" runat="server" />
        </div>
      <div id="footer">Some foot content</div>
    </div>
    </form>
</body>
</html>



Now, every web form that is added with a Master Page will automatically inherit the structure defined in the Master Page. This means that changes to the site layout only need to be done in one place. For this reason, I always add a Master Page as the first step in creating a web site - even if it may not need a common look and feel to start with. One day it might, and it is so simple to create that in one place.

UI Widgets

OK. Now we need to look at filling in some of the content, which is where we started with the classic ASP snippet above. In that, the head content and the navigation are drawn from included files. There is nothing to stop you from adding these artifacts directly to the Master Page, just as I have added the text "Some head content" in the code above. From there they will be available wherever the Master Page is used, and this is how many people work with Master Pages. But lets imagine that you have an advertising spot on some, but not all pages. In this, you may want to display optionally an image or a flash banner, and have some code that decides which advert to pull from the image folder or database depending on the Url of the page. This is where Web User Controls come into play. A User Control is a self-contained "pagelet" (which is what they were initially referred to as) which consists of some html perhaps, server controls and a code-behind file in which associated logic can run. They have a .ascx file suffix.

Once created, they can literally be dragged from the Solution Explorer on the right hand side of Visual Studio, and dropped onto the designer where they are to appear within a page. the can even be added to existing User Controls. Here's a simple control that contains and Image control and a Literal:


<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="MainControl.ascx.cs" Inherits="WebFormsTests.MainControl" %>
<%@ Register src="SubControl.ascx" tagname="SubControl" tagprefix="uc1" %>
<asp:Image ID="Image1" runat="server" />
<br />
<asp:Literal ID="Literal1" runat="server"></asp:Literal>
<br />


I've added some code in the Page_Load event in the code behind to set the ImageUrl property of the Image control and the Text property of the Literal control to display the file name of the currently executing page:


using System;

namespace WebFormsTests
{
  public partial class MainControl : System.Web.UI.UserControl
  {
    protected void Page_Load(object sender, EventArgs e)
    {
      Image1.ImageUrl = "~/Content/images/comment.png";
      Literal1.Text = "Main control says the current page is " 
          + System.IO.Path.GetFileName(Request.Url.ToString());
    }
  }
}

And here's another user control, which simply gives the date:


<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="SubControl.ascx.cs" Inherits="WebFormsTests.SubControl" %>
<%= "Sub control says the date is " + DateTime.Now.ToShortDateString() %>


I drop this onto the previous control:

which I then drop onto the Content area within the ChildPage.aspx I created earlier. When I run the whole thing in the browser, you can see Master Page, Child page, and user controls all working together (please excuse the garish css):

Reusable routines and procedures

As a classic ASP developer, I collected several libraries of code over time which has all sorts of utility code within them - string functions that determined if a user supplied email address was in a valid format, something that generated html select lists (DropDowns) from databases, another one that tidied up html from rich text boxes etc. I also had some pieces of code that needed to run on every page, such as connection initialisation and closing. The .NET framework offers a number of ways to manage these kinds of things in a cleaner manner than classic ASP. We'll take a look at the concept of a Base Page first.

Since .NET is fully object oriented, it supports inheritance. I already mentioned that each page you create is a class that inherits from System.Web.UI.Page. As a result, it inherits all the properties, methods and events of System.Web.UI.Page. A Base Page is a kind of shim within this inheritance hierarchy. And it's simple to create. Just add a class to the project, within the App_Code folder called BasePage.cs (or .vb if that's your language). You don't have to call it BasePage - you can call it anything you like, but BasePage is the convention:


namespace WebFormsTests
{
  public class BasePage : System.Web.UI.Page
  {
  }
}

Notice one thing in particular - the bit after the colon. This new class inherits from System.Web.UI.Page, and as such, it inherits all the methods, properties events etc from the Page class just as a web form does. If you make your web forms inherit from BasePage instead of Page, they will still acquire all these properties, members, events etc, but they now get them from BasePage, which in turns gets them from Page. However, you can now add code to BasePage, and ensure that any page that inherits from it will cause that code to run. We just need to modify ChildPage to inherit from BasePage:


namespace WebFormsTests
{
  public partial class ChildPage : BasePage
  {
    protected void Page_Load(object sender, EventArgs e)
    {

    }
  }
}

You could, for example, add a Page_Load event to BasePage, and use that to log page views:


public class BasePage : System.Web.UI.Page
{
  void Page_Load(object sender, EventArgs e)
  {
    string connect = "My Connection String";
    using(SqlConnection conn = new SqlConnection(connect))
    {
      string sql = "INSERT INTO PageViews (DateVisited, Page) VALUES (GetDate(), @Page)";
      SqlCommand cmd = new SqlCommand(sql, conn);
      cmd.Parameters.AddWithValue("@Page", this.Title);
      conn.Open();
      cmd.ExecuteNonQuery();
    }
  }
}

Other uses for the BasePage include setting the page title, or some meta content such as keywords or a description. If you want to run some code that times the total execution of all your pages, this is also the kind of place to put that.

Utility Functions

So far I have looked at methods that can be included in every page, because they are required to run as part of every, or a selection of pages. The other types of functions are utility functions that need to be available to any page in the site, but might only be used by a few of them. A typical example is a method that replaces newline characters with their HTML counterpart. This type of method is independent of any object, so the best way to create it is as a static (shared in VB) method, and to place it in a static class:


public static class Utils
{
  public static string LineBreaks(string s)
  {
    return s.Replace(Environment.NewLine, "<br />");
  }
}

You would place this in the App_Code folder if you have created a New Web Site, where it will be compiled, and the Utils class will be visible to your code behind files. If you have created a Web Application, you will not have an App_Code folder. But if you place the file in any folder and then click the file to select it, hit F4 to bring up the Properties panel and change the Build Action to Compile, you should be able to reference it in code behind. If you wanted the method to act on the user input provided via a TextBox control, it would look like this:


string story = Utils.LineBreaks(txtStory.Text);

However, a neater way to do this kind of thing if you are working with ASP.NET 3.5 or greater is to use Extension methods. These allow you to effectively "extend" existing types with their own behaviour. The type that the above method works on is a System.String. The previous method needs a tiny addition to become an extension method - the this keyword before the type parameter (OK, I changed the name of the method too...):


public static string WithLineBreaks(this string s)
{
  return s.Replace(Environment.NewLine, "<br />");
}

Now, whenever you hit the dot after a string, you will see a new method added in Intellisense:

When you use this extension method, the result looks like this:


string story = txtStory.Text.WithLineBreaks();

Now you can see why I changed the name of the method. It makes it much easier to understand what the code does.

Including a File

There may still be times when you want to actually include the contents of a file within the output of an ASP.NET page. The way to do this in classic ASP was to use the FileSystemObject and read a Stream from it. You can still use a StreamReader object in .NET to do essentially the same thing, or more simply, use a method of the Response object that has been added - Response.WriteFile(). This method should be used inline within the aspx file:


<pre>
<% Response.WriteFile("App_Code/Utils.cs"); %>
</pre>


The sample above grabs the Utils.cs file that we created for the extension method and writes its content to the page within <pre> tags:

There are three things to notice here. First, do you see the odd formatting of the code compared to the snippet earlier? There's a line break that's crept in. That goes to illustrate that html or client-side script contained in the file that is included is rendered as exactly that, and treated as such by the browser. The second thing to note is that when you place Response.WriteFile() in a code-behind file, the content is prepended to the http output, and will appear before any other page markup. Consequently, it should only really be used within the aspx file. Finally, the argument accepted by the Response.WriteFile() method is a string. This means that you can set the value dynamically, which is something you could not do with the include file directive within classic ASP.