Quick and Dirty Automatic Page Navigator

Over on the Asp.Net forums, a user asked how to store an ordered list of pages in an Xml file so he could use it to control the navigation of a group of pages on his web site. It was for a 'Wizard' where the user needed to go through the pages in sequence…no jumping directly to a page. He wanted it to be easily editable.

I thought about it and decided that Xml was overkill. There is no hierarchical data structure required. A simple text file would suffice (although it's not as sexy as Xml is) and there are more text editors than Xml editors….long live Notepad!

I thought about it some more and realized that once the contents of the text file was read in, it should be cached so that each page with the navigation control didn't have to reread it.

Yet more thought: Why put the list of pages in a separate file? If you are going to make a navigation control, why not put the list into the control where it's used? True, it's not a generic control in a traditional sense, but if you are going to have to edit the list of pages anyway, why not do it in the control?

Quick and dirty can be bad, but simple is always good…yah?

Well one thing led to another and I ended up writing a page navigation user control:

                                    

Features:

Pages are easily removed and added to the array of strings in the control (edit the file). 

The array of strings (pages) is static so there is only one copy in memory.

Navigation is automatic.

The Prev and Next buttons are automatically enabled and disabled.

Usage:

Edit the list of pages in the control.

Drop the User Control on each page to be navigated...or drop it on a Master Page.

Here's the UC_Navigator.ascx file (UC is for User Control).  It's basically two buttons in a div.

<%@ Control Language="C#" 
    AutoEventWireup="true" 
    CodeFile="UC_Navigator.ascx.cs"
    Inherits="UserControls_UC_Navigator" %>
 
 <div style="border-style:ridge; 
     border-width:medium;     
     padding: 5px; 
     background-color:#808080
     text-align:center; 
     width: 134px; 
     vertical-align: middle;">
    <asp:Button ID="ButtonPrev" runat="server" OnClick="ButtonPrev_Click" Text="Prev" />
    &nbsp;&nbsp;&nbsp;
    <asp:Button ID="ButtonNext" runat="server" OnClick="ButtonNext_Click" Text="Next" />
</div>

Here's the code-behind file:

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.IO;
 
public partial class UserControls_UC_Navigator : System.Web.UI.UserControl
{
    // list of pages to navigate - in order
    // page names must be in lower case
    static String[] Pages =
    {
        "default.aspx",
        "child1.aspx",
        "exportcalendar.aspx"
    };
 
    enum DIRECTION { PREV, NEXT }
 
    // ---- Page_Load ----------------------------
    //
    // Disable Prev/Next buttons when appropriate
 
    protected void Page_Load(object sender, EventArgs e)
    {
        int CurrentIndex = GetCurrentIndex();
 
        if (CurrentIndex == 0)
            ButtonPrev.Enabled = false;
 
        if (CurrentIndex == Pages.Length - 1)
            ButtonNext.Enabled = false;
    }
 
    // ---- GetCurrentIndex ----------------------------------
    //
    // Gets the index of the current page 
    // (from the array of Pages)
    //
    // returns -1 if the Page isn't in the Pages array
 
    private int GetCurrentIndex()
    {
        String CurrentPage;
 
        // get the current page the User Control is on (from parent page)
        CurrentPage = Parent.BindingContainer.TemplateControl.AppRelativeVirtualPath;
 
        // get filename only and force lower case
        CurrentPage = Path.GetFileName(CurrentPage).ToLower();
 
        // get the index from the page array
        int CurrentIndex = Array.IndexOf(Pages, CurrentPage);
 
        return CurrentIndex;
    }
 
    // ---- MoveToPage ---------------------------------
    //
    // Moves to previous, or next page if possible
 
    private void MoveToPage(DIRECTION Direction)
    {
        int CurrentIndex = GetCurrentIndex();
 
        if (Direction == DIRECTION.PREV)
        {
            if (CurrentIndex == 0)
                return// can't move before first page
 
            //Response.Redirect(Pages[CurrentIndex - 1]);
            Server.Transfer(Pages[CurrentIndex - 1]);
        }
 
        if (Direction == DIRECTION.NEXT)
        {
            if (CurrentIndex == Pages.Length - 1)
                return// can't move after last page
 
            //Response.Redirect(Pages[CurrentIndex + 1]);
            Server.Transfer(Pages[CurrentIndex + 1]);
        }
    }
 
    // ---- Navigation buttons ---------------------------
 
    protected void ButtonPrev_Click(object sender, EventArgs e)
    {
        MoveToPage(DIRECTION.PREV);
    }
    protected void ButtonNext_Click(object sender, EventArgs e)
    {
        MoveToPage(DIRECTION.NEXT);
    }
}

A few problems I had on the way:

Getting the current page the user control was on took a bit of digging around. I ended up using the AppRelativeVirtualPath property. It works for normal pages and child-of-master pages.

The first time AppRelativeVirtualPath was accessed, it returned the page in mixed case: subsequently it returned the page in all lower case. I decided to make everything lower case (remember the article is prefaced with Quick and Dirty J). 

I wasn't sure which to use, Response.Redirect(…) or Server.Transfer(…). Both worked so I left them both in (one is commented out).

Possible Enhancements:

I like the Spartan functional look…plain gray buttons. You could however, "sexy up" the control with CSS and graphics. For sure dude, this ain't purty:

                   

Adding First and Last buttons would be trivial.

You could make the control more "generic" by keeping the list of pages in a separate file or a database and reading them in once (do it in a static constructor).

You could add a property to switch between using Response.Redirect(…) and Server.Transfer(…) methods.

You can download the code Here.   My pithy advice:  Think before you type.

I hope someone finds this useful.

Steve Wellens

No Comments