Simulating include Files with an ASP.NET Server Control

 

Paul Sheriff
PDSA, Inc.

October 2003

Applies to:
    Microsoft® ASP.NET
    Microsoft Visual Studio® .NET

Summary: Learn how server controls have many benefits over user controls or simple #include files. Learn how to create a central location for HTML snippets and how to modify HTML for multiple sites in one location. (17 printed pages)

Download HTMLIncludeSample.msi.

Contents

The Scenario
Create Your Own Server "Include" Control
Build the Server Control
Test the PDSAInclude Control
Adding Caching Capabilities
Customizing your Web Custom Control
Summary

The Scenario

In many companies you will typically have multiple intranet and/or extranet sites. You will most likely wish to maintain a common look and feel across all of these sites. In Microsoft® ASP.NET there is no mechanism built-in to allow you to create HTML snippets and bring them in as a header or footer on each page across multiple Web sites (see Figure 1).

Aa479009.aspnet-simulatinginclude-01(en-us,MSDN.10).gif

Figure 1. A central location for HTML is sometimes needed for enterprise development

What's Wrong with User Controls

For a single site you can use a user control, but then it must be copied to every other site whenever you make a change to the UI (see Figure 2). The reason for this lies in the fact that a user control must be compiled as part of the application under which it will run. If you make a change to one of the user controls on one of the Web sites, you will need to physically copy the .ASCX and associated .VB file to the other sites in your enterprise.

Aa479009.aspnet-simulatinginclude-02(en-us,MSDN.10).gif

Figure 2. User controls are limited in that they can only be associated with one site.

#include Files

In the past, when using Microsoft Active Server Pages, you would include common HTML snippets within each page using the #include directive. This directive would allow you to specify a file name to insert at the location where the #include was located within the ASP code. ASP.NET still supports the use of #include files, but there are a few issues that make their use unattractive.

  • You must hard code the path on each page.
  • There is no design-time interface to see what your page looks like.
  • You must go into the HTML view of the page to enter the #include.

Create Your Own Server "Include" Control

One way you can solve this problem is to create a server control that will read an HTML file from disk and display that HTML on any Web page within any Web site. The HTML file(s) can be centrally located where any Web site on any of your servers can get at them. In this way, you may modify the HTML files in one location and the change will affect all of your Web sites.

Aa479009.aspnet-simulatinginclude-03(en-us,MSDN.10).gif

Figure 3. A server control can be used across multiple sites and bring in a centrally located HTML snippet.

In this article you will create a control named PDSAInclude. This server control will allow you to set a FileName property where the HTML you wish to render is located.

Create Some Properties

Like any server control, you will need to create some properties that you can set either at design time or at run time. For the PDSAInclude control you will need three different properties; FileName, Cache and HTML. The properties and their attributes are listed in Table 1.

Table 1. PDSAInclude properties

Property Name Data Type Description
FileName String The full path and file name of the HTML file.
Cache Boolean True=Cache the HTML, False=Do not Cache the HTML
HTML String The HTML read from the file.

Create Some Methods

You will need some methods in the PDSAInclude control to perform the rendering of the control, reading the HTML from disk, and caching the HTML after you have read it. The methods you will create are listed in Table 2.

Table 2. PDSAInclude methods

Method Name Type Return Value Description
Render Protected Overrides Sub n/a Overrides the Render method for the base control. It is in this method that you will read the HTML file and display the HTML within this control.
IsDesignMode Private Function Boolean Return a True if the control is currently being displayed in the designer. Returns a False if the control is being rendered at runtime.
GetHtml Private Function String Attempts to open the file and read the HTML from the file. Places the HTML read into the HTML property of this control. The return value is the HTML read from the file.
IsFileValid Private Function Boolean Returns a True if the path and file input into the FileName properties exists, otherwise returns a False.
CacheExists Private Function Boolean Returns a True if the HTML from the file has already been cached in memory.
GetFromCache Private Function String Returns the HTML string from the Cache.
SetCache Private Sub n/a Places the HTML into the Cache.

Creating the PDSAInclude Project

If you have never created a server control before, don't worry; the steps are fairly simple and it is just like creating any class library project. In general here are the steps you will perform.

  • Create a Web Control Library project.
  • Create a server control named PDSAInclude.
  • Create an ASP.NET Web Application project named PDSAIncludeSample.

After you have completed these steps you will have a server control that can used across multiple Web sites.

Build the Server Control

To begin building the PDSAInclude server control you will need to first create a Web Control Library project. This project will contain just the one control, PDSAInclude. Follow the steps below to create this project.

  • Start Microsoft Visual Studio® .NET and click the New Project button.
  • Select Visual Basic Projects from the Project Types list
  • Select Web Control Library from the Templates.
  • Set the Name to PDSAInclude.
  • Set the Location to any valid folder on your hard drive. For purposes of this article I will assume you place it into a folder called D:\MyControls.
  • Click the OK button to create the new project.

Visual Studio .NET will create a new file named WebCustomControl1.vb that contains a template class with some attributes for your new custom control. You should see something like Listing 1 in your editor.

Listing 1. The default WebCustomControl class

Imports System.ComponentModel
Imports System.Web.UI

<DefaultProperty("Text"), _
  ToolboxData("<{0}:WebCustomControl1 _
  runat=server></{0}:WebCustomControl1>")> _
Public Class WebCustomControl1
  Inherits System.Web.UI.WebControls.WebControl

  Dim _text As String

  <Bindable(True), Category("Appearance"), _
   DefaultValue("")> Property [Text]() As String
    Get
      Return _text
    End Get

    Set(ByVal Value As String)
      _text = Value
    End Set
  End Property

  Protected Overrides Sub Render( _
   ByVal output As System.Web.UI.HtmlTextWriter)
    output.Write([Text])
  End Sub

End Class

Unlike a user control, when you create a Web Custom Control, there is no design time interface. Web Custom Controls are built entirely from code. You will need to make a few changes to this standard template to create the PDSAInclude control. Start at the top of the new class file and make the following changes.

  1. AddImports System.IOjust below the otherImportsstatements.
  2. At the very top of the control, locate the DefaultProperty attribute and change it fromTextto FileName.
  3. You will also modify the ToolboxData attribute and change it fromWebCustomControl1to PDSAInclude.

When you have completed these steps, the code at the top of the control should look like Listing 2.

Listing 2. Modify the attributes of the class to give this control a unique name

Imports System.IO

<DefaultProperty("FileName"), _
 ToolboxData("<{0}:PDSAInclude _
 runat=server></{0}:PDSAInclude>")> _

Next you will need to change the class name and the file name.

Change the class name from WebCustomControl1 to PDSAInclude.

Change the file name from WebCustomControl1.vb to PDSAInclude.vb.

Add Property Declarations

You will now add some private constants and variables to this control. These will be needed as we develop the properties for this server control. Just below theInheritsline in the class add the variables as shown in Listing 3.

Listing 3. Add private variables to hold the data for the properties of this class.

Private Const conHTML As String = "PDSAInclude"

Private mstrFileName As String = ""
Private mboolCache As Boolean = True
Private mstrHTML As String = ""
Private mstrMessage As String = "Fill in a File Name"
Private mboolDesignMode As Boolean

At the same time you add these properties, delete the Text property declaration including the line of code that creates the _text variable. The lines to delete are shown in Listing 4.

Listing 4. Delete the Text property as it will not be needed for our application.

Dim _text As String

<Bindable(True), Category("Appearance"), _
DefaultValue("")> Property [Text]() As String
  Get
    Return _text
  End Get

  Set(ByVal Value As String)
    _text = Value
  End Set
End Property

You will now need to create the appropriatePropertydeclarations that you will want to show up in the Properties window. Add the code shown in Listing 5.

Listing 5. Properties with the appropriate attributes will show up in the Properties window.

<Bindable(True), Category("Misc"), DefaultValue("")> _
  Property FileName() As String
  Get
    Return mstrFileName
  End Get

  Set(ByVal Value As String)
    mstrFileName = Value
  End Set
End Property

<Category("Misc"), DefaultValue(True)> _
  Property Cache() As Boolean
  Get
    Return mboolCache
  End Get

  Set(ByVal Value As Boolean)
    mboolCache = Value
  End Set
End Property

Property HTML() As String
  Get
    Return mstrHTML
  End Get

  Set(ByVal Value As String)
    mstrHTML = Value
  End Set
End Property

Does the HTML File Exist?

Before you can render the HTML coming from an HTML file, you will first need to read that file from disk. Of course, you should also check to see if that file exists before you attempt to open it. As a result you will need to create a method within your control to see if the file in the FileName property does exist. Create the IsFileValid method just below the Render method in your PDSAInclude control. The code to write is shown in Listing 6.

Listing 6. The IsFileValid method will ensure the file exists prior to attempting to read it.

Private Function IsFileValid() As Boolean
  Dim boolReturn As Boolean

  If mstrFileName.Trim().Length = 0 Then
    mstrMessage = "Fill in a FileName"
  Else
    If File.Exists(mstrFileName) Then
      boolReturn = True
    Else
      mstrMessage = "File: " & mstrFileName & " does not exist"
    End If
  End If

  Return boolReturn
End Function

The IsFileValid method uses the Exists method of the File class in the System.IO namespace to check for the existence of a file on disk. If the file does not exist, then you fill in the mstrMessage variable with an appropriate message that will be displayed on the Web page.

Retrieving the HTML

You now will write a method named GetHtml that will retrieve the HTML from the file you enter into the FileName property. Just below the Render event procedure add the GetHtml method as shown in Listing 7.

Listing 7. The GetHtml method reads the HTML from the file set in the FileName property.

Private Function GetHtml() As String
  Dim tr As System.IO.TextReader

  Try
    tr = File.OpenText(mstrFileName)
    mstrHTML = tr.ReadToEnd()
    tr.Close()

    Return mstrHTML

  Catch exp As Exception
    Throw exp

  End Try
End Function

The GetHtml method uses the File class to perform its work. Using the OpenText method you will pass in the file name you set in the control's FileName property. The OpenText method will return a TextReader object. You then use the ReadToEnd method of the TextReader to read the complete file into the member variable mstrHTML. You then return this HTML from this method. The return value will be used in the Render event to output into the response stream.

Create the Render Event

The Render event is responsible for drawing the control both at design time and at runtime. The default Render event attempts to output the Text property. Since you have deleted this property you will need to modify this event. The purpose of this control is to render HTML read from a file. As a result you will need to write the appropriate code to read from a file and output that HTML in the Render event.

The Render event is passed in an HtmlTextWriter object from the page on which this control lives. This object is used to output HTML into the response stream. As an ASPX Web page renders itself, it will invoke the Render event of each control on the page and pass in that page's HtmlTextWriter to each control. Your job in this control is to read the HTML from file passed into the FileName property and output that HTML into the response stream.

Now modify the Render event procedure and modify the code to look like the code shown in Listing 8.

Listing 8. The Render event procedure will write the HTML to the Web page.

Protected Overrides Sub Render(ByVal output As _
   System.Web.UI.HtmlTextWriter)
  Try
    If IsFileValid() Then
       output.Write(GetHTML())
    Else
       output.Write(mstrMessage)
    End If

  Catch exp As Exception
    output.Write(exp.Message)

  End Try
End Sub

You are now ready to give this control a try and see if it works. Follow the instructions in the next section to test this control.

Test the PDSAInclude Control

To test out your new PDSAInclude Control, you will need to create a new ASP.NET Web Application project. You will then be able to add your new custom control to the Toolbox and drop it onto a Web page.

  • Click on the Solution PDSAInclude in the Solution Explorer Window.

    Note   Make sure you click on the Solution and not the Project.

  • Right-click on the Solution and select Add | New Project from the context menu.

  • Select ASP.NET Web Application from the list of templates.

  • Set the name of the project to PDSAIncludeSample.

  • Right-click on this new project and select Set as Startup Project from the context menu.

  • Right-click on the Toolbox and select Customize Toolbox from the context menu.

  • Click the .NET Framework Components tab.

  • Click the Browse button.

  • Navigate to the D:\MyControls\PDSAInclude\bin folder (or wherever you placed your control) and select the PDSAInclude.dll file.

  • Click the Open button. You should now see the PDSAInclude control appear in the toolbox.

  • Double-click on this new control and it should now draw the new control on the Web page.

Create an HTML Page

  • Create an HTML page that you can read into your PDSAInclude control.

  • Using Microsoft Notepad or another editor, create a file in D:\MyControls named Header.htm.

  • Within this file place the following HTML snippet:

    <H3>This is a header</H3>
    

  • Return to your Visual Studio .NET project and give the PDSAInclude control focus by clicking on it once.

  • Bring up the Properties window and locate the FileName property.

  • Set the FileName property to D:\MyControls\Header.htm.

  • Press the ENTER key.

As soon as you press the ENTER key you should see the HTML you put into your Header.htm file appear where the control is located on the Web page. If you run the Web page this HTML will also appear in the running page.

Adding Caching Capabilities

Instead of reading HTML from disk every time you render your PDSAInclude control, you could add caching capabilities into your control. The first time you read from the disk, you store the HTML into some sort of cache. Then the next time your control is rendered, the HTML is read from the cache instead of disk (see Figure 4).

Aa479009.aspnet-simulatinginclude-04(en-us,MSDN.10).gif

Figure 4. Store commonly used HTML snippets in a cache for better performance.

Where you store the HTML is up to you. You could use a Cache object, the Application object, or even a Session object. In this article you will use the Application object.

Setting up the Cache

The first method you need to create will be SetCache. This method will place the HTML into the Application object. For the name of the Application variable, you will combine the constant that you created at the beginning of this article, conHTML, and the name of the file. Listing 9 shows an example of the SetCache method. Add this method to your control now.

Listing 9. The SetCache method will store the HTML into an Application object.

Private Sub SetCache(ByVal Value As String)
  Try
    HttpContext.Current.Application.Lock()
    HttpContext.Current.Application(conHTML & FileName) = Value
    HttpContext.Current.Application.UnLock()

  Catch
    Throw

  End Try
End Sub

This method will need to be called from the GetHtml method. Modify the GetHtml method so it looks like Listing 10.

Listing 10. The GetHtml method will cache the results after reading from the disk the first time.

Private Function GetHTML() As String
  Dim tr As System.IO.TextReader

  Try
    tr = File.OpenText(mstrFileName)
    mstrHTML = tr.ReadToEnd()
    tr.Close()

    If Not mboolDesignMode Then
      ' Cache the results by file name
      If mboolCache Then
        SetCache(mstrHTML)
      End If
    End If

    Return mstrHTML

  Catch exp As Exception
    Throw exp

  End Try
End Function

Checking for Design Time or Runtime

You will notice that that the GetHtml method now uses the mboolDesignMode variable. This Boolean variable will be set in a method called IsDesignMode. This method is needed so you do not try to retrieve HTML from the Cache at design time. The Application object does not exist during design time, so we need to set this variable so our code always gets the HTML from disk during design mode. Listing 11 shows the IsDesignMode method. Add this method to your control at this time.

Listing 11. The IsDesignMode method checks to see if the control is running or not.

Private Function IsDesignMode() As Boolean
  Try
    If HttpContext.Current Is Nothing Then
      mboolDesignMode = True
    Else
      mboolDesignMode = False
    End If

    Return mboolDesignMode
  Catch
    Throw

  End Try
End Function

The easiest method to check if the control is at design mode is simply to check the current HttpContext. If this variable returns Nothing, then the control is in design mode.

Checking to see if HTML is in the Cache

In the Render event you will want to check if there is data in the Cache prior to getting the HTML from disk. To do this, you simply need to check if the Application variable where you stored the HTML exists already. This is accomplished by creating a method named CacheExists as shown in Listing 12.

Listing 12. Check the Cache before trying to retrieve HTML.

Private Function CacheExists() As Boolean
  If HttpContext.Current.Application(conHTML & FileName) _
   Is Nothing Then
    Return False
  Else
    Return True
  End If
End Function

Getting HTML from the Cache

If the CacheExists method returns a True, then you need to call a method to retrieve the HTML from the cache. Create a method GetFromCache as shown in Listing 13.

Listing 13. Get the HTML from the Cache

Private Function GetFromCache() As String
  Try
    Return HttpContext.Current.Application(conHTML & FileName).ToString()

  Catch
    Throw

  End Try
End Function

Modify the Render Event

Now that you have created all the methods related to retrieving data from a cache, you are now ready to use these methods. You will now modify the Render event to check if the control is in design mode. If it is, then you will get the HTML from the file. If the control is running, you will first check to see if the HTML has already been cached. If it has you will call the GetFromCache method. If it is not already in the Cache, you will then retrieve the HTML from disk. Modify the Render event by typing the code shown in bold in Listing 14.

Listing 14. Modify the Render event to check the cache.

  Protected Overrides Sub Render(ByVal output As System.Web.UI.HtmlTextWriter)
    Try
      If IsFileValid() Then
        If IsDesignMode() Then
          ' We are in design mode
          output.Write(GetHTML())
        Else
          ' See if we can get data from cache
          If Me.CacheExists() Then
            output.Write(GetFromCache())
          Else
            output.Write(GetHTML())
          End If
        End If
      Else
        output.Write(mstrMessage)
      End If

    Catch exp As Exception
      output.Write(exp.Message)

    End Try
  End Sub

Customizing your Web Custom Control

To finish your custom control you will probably want to supply a custom bitmap to appear in the toolbox. In addition the TagPrefix for these custom controls is currently set to "cc1", and you might wish to supply a better prefix for all of your own custom controls.

Changing the Toolbox Bitmap

To change the toolbox bitmap you will first have to create a bitmap. There are a few requirements that must be met to change the bitmap that appears in the toolbox.

  • The bitmap must be a 16 x 16 pixel, 16-bit color bitmap, saved as a .BMP file.
  • It must have the same file name as your control's class name. Since the name of your custom control is PDSAInclude, you will need to save your image as PDSAInclude.bmp.
  • You will need to change the Build Action property of the bitmap control to Embedded Resource. This informs Visual Studio to compile the bitmap into the project.

Follow the steps below to create a bitmap for your control:

  1. Select Project | Add New Item from the Visual Studio.NET menu.
  2. Select Bitmap File from the list of templates.
  3. Set the name to PDSAInclude.
  4. Click on the new bitmap and set the Build Action property to Embedded Resource.
  5. Draw something.
  6. Build the Project.
  7. Switch back to the PDSAIncludeSample project and open the WebForm1.aspx page in design mode.
  8. Right-click on the PDSAInclude control in the toolbox and select Delete from the context menu.
  9. Re-add the PDSAInclude control back to the toolbox to see the new bitmap you just created.

Modifying the TagPrefix

If you were to look at the HTML view in your WebForm1.aspx page you will see a Register tag at the top. This tag informs the .ASPX page where the definition for this control comes from. If you look at this directive right now, you should see something that looks like the following:

<%@ Register TagPrefix="cc1" Namespace="PDSAInclude" 
  Assembly="PDSAInclude" %>

If you then look further down within the<FORM>tag, you will see the definition for your PDSAInclude control. You will see the cc1 prefix that matches the TagPrefix in the Register directive.

  <cc1:PDSAInclude id="PDSAInclude1" runat="server">
  </cc1:PDSAInclude>

You will most likely wish to change this prefix as "cc1" is not very descriptive. To accomplish this you will need to follow the steps below.

  1. Double-click on the AssemblyInfo.vb file within the PDSAInclude project.

  2. Add anImportsstatement at the top of the file to bring in the System.Web.UI namespace:

    Imports System.Web.UI
    

  3. Locate the<Assembly:>tags within this file and add the following tag:

    <Assembly: TagPrefix("PDSAInclude", "pdi")>
    

  4. Rebuild your solution.

You must now delete the control from the toolbox and re-add the control to the toolbox to have it refresh the TagPrefix attribute. You will also need to delete the control from your Web page, and you will most likely have to manually delete the old Register directive from the HTML view of the WebForm1.aspx file.

After adding the control back to your Web page you can now view the HTML and see the new TagPrefix of "pdi"

<%@ Register TagPrefix="pdi" Namespace="PDSAInclude" 
  Assembly="PDSAInclude" %>

You will also see the new prefix in the declaration of your custom control as well.

<pdi:PDSAInclude id="PDSAInclude1" 
runat="server"></pdi:PDSAInclude>

Of course when you delete the control, all of the design time properties have been deleted as well. You will need to reset the FileName property.

Summary

Server controls have many benefits over user controls or simple #include files. Some of the best benefits include: getting a design time rendering of the control, and the ability to centralize the code. With user controls you get no rendering and you must copy the control from one site to another. The controls presented in this article will provide you the ability to have a central location for all your HTML snippets like headers and footers for your Web sites.

About the Author

Paul D. Sheriff is the President of PDSA, Inc., a Microsoft Partner in Southern California. Paul is the Microsoft Regional Director for Southern California, and he has three books on .NET. The first book is entitled "ASP.NET Developer's Jumpstart" from Addison-Wesley and is co-written with Ken Getz. His other two books are eBooks and can be purchased directly from the PDSA Web site. You can contact Paul directly at PSheriff@pdsa.com.

© Microsoft Corporation. All rights reserved.