An ASP.NET Xml Custom Dropdown Menu UserControl
By Peter A. Bromberg, Ph.D.
Printer - Friendly Version

Peter Bromberg

I'm in between contracts and jobs right now and I'm keeping very busy helping some buddies convert a legacy automotive dispatching / billing application that was originally written in Clipper / Foxpro to the .NET platform where it will soon be operating over the Internet with SQL Server as a backend.

I have an equity interest in the deal and since my time is limited, I need to gain economies of RAD wherever possible. One of the first things I realized I'd need for this app is a menuing system - preferably one that doesn't take up a lot of real estate in the browser, and can be customized to be different on each page. But I also needed it to be extremely easy to wire up without any custom coding because there are a lot of pages in the app.

So my first thought was a UserControl. There are two kinds, as we all know - ServerControls, which are full-blown class libraries with Design-time support, and .ascx User Controls, which aren't quite as scalable, but are extremely easy to author. Since this is not going to be a high-traffic site, I chose the ascx approach. By supplying a custom XML file containing the menu items for each custom page, and using the same XSL for the transformation and some script and css to handle everything else, all I need to do is drag-and-drop my ascx user control on to the top of each page, set the correct XML file for it, and I am "good to go!".

One of the coolest tricks with ascx controls, which replace the old-fashioned Classic ASP functionality of the include file, is that you can write up everything you need to get it working correctly as a regular WebForm ASPX page. Once you are done and satisfied with your work, converting it to an ASCX User Control is as simple as:



1) Remove all <html>, <body> and <form> tags.
2) Change the @Page directive to an @Control directive
3) Change the file extension from ASPX to ASCX, and
4) Have it derive from System.Web.UI.UserControl instead of System.Web.UI.Page

Yup, it's really that simple! In this case, I needed to get something working really fast, and so I looked around for some decent XML / XSL menu stuff I could borrow from, rather than taking a couple of hours of my valuable time writing it all from scratch. The old MSDN top menu bar used by Microsoft around 1999 - 2000 turned out to be just the ticket.

Now here's the code for the entire UserControl. Once you step through it, you'll see how elegantly simple it all is:

FIrst the "page" portion:

<%@ Control language="c#"   Codebehind="PageMenu.ascx.cs" AutoEventWireup="false" 
Inherits="MenuControl.PageMenu" %> <link rel="stylesheet" type="text/css" href="menus.css"> <script language="javascript" src="menus.js"></script> <asp:Label id="MenuPlaceHolder" runat="server"> Menu Table Goes Here </asp:Label>

The css references my stylesheet, and the script tag references my javascript that handles opening and closing the menus and the mouseover events.

And now, the codebehind class:

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Xml; 
using System.Xml.Xsl; 
using System.Xml.XPath; 
using System.IO; 
using System.Text; 

namespace MenuControl
{
    public class PageMenu :  System.Web.UI.UserControl 
    {        
        protected Label MenuPlaceHolder; 
        private string xmlFile = String.Empty; 
        private string xslFile = String.Empty; 
        public string XmlFileName 
        { 
            get { return(xmlFile); } 
            set {xmlFile = value;} 
        } 
        public string XslFileName 
        { 
            get { return(xslFile); } 
            set {xslFile = value;} 
        } 
        private void Page_Load(object sender, System.EventArgs e) 
        {              
            string XmlSystemFileName = Server.MapPath(xmlFile); 
            string XslSystemFileName = Server.MapPath(xslFile); 
            XslTransform xslt = new XslTransform();
            xslt.Load(XslSystemFileName);
            XPathDocument xpathdocument = new
            XPathDocument(XmlSystemFileName);            
            StringBuilder sb = new StringBuilder(); 
            StringWriter sw = new StringWriter(sb); 
            xslt.Transform(xpathdocument, null, sw, null);
            MenuPlaceHolder.Text = sb.ToString(); 
        }
        #region Web Form Designer generated code
        override protected void OnInit(EventArgs e)
        {
            InitializeComponent();
            base.OnInit(e);
        }
        
        private void InitializeComponent()
        {    
            this.Load += new System.EventHandler(this.Page_Load);
        }
        #endregion
    }
}

When you drop this onto a WebForm page, your HTML tags will look like this (you have to manually put in the XML and XSL file attributes):

<%@ Register TagPrefix="uc1" TagName="PageMenu" Src="PageMenu.ascx" %>
<%@ Page language="c#" Codebehind="Default.aspx.cs" AutoEventWireup="false" 
Inherits="MenuControl.WebForm1" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" > <HTML> <HEAD> <title>WebForm1</title> <meta name="GENERATOR" Content="Microsoft Visual Studio .NET 7.1"> <meta name="CODE_LANGUAGE" Content="C#"> <meta name="vs_defaultClientScript" content="JavaScript"> <meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5"> </HEAD> <body MS_POSITIONING="GridLayout"> <form id="Form1" method="post" runat="server"> <uc1:PageMenu id=PageMenu1 runat="server" XmlFileName="Menu.xml" XslFileName="Menu.xsl">
</uc1:PageMenu> </form> </body> </HTML>

The XML file structure for each custom menu is very simple:

<?xml version="1.0"?>
<TOPICLIST TYPE="MenuItems">
<TOPICS TYPE="DRIVER RECORDS">
  <TOPIC>
    <TITLE>Driver File Maintenance</TITLE>
    <URL>/default.aspx</URL>
  </TOPIC>
  <TOPIC>
    <TITLE>Terminate /Reactivate Drivers</TITLE>
    <URL>/Terminate.aspx</URL>
  </TOPIC>
  <TOPIC>
    <TITLE>Driver Types</TITLE>
    <URL>DriverTypes.aspx</URL>
  </TOPIC>
  <TOPIC>
    <TITLE>Driver Reports</TITLE>
    <URL>/DriverReports.aspx</URL>
  </TOPIC>
  <TOPIC>
    <TITLE>Driver Accounts</TITLE>
    <URL>/DriverAccounts.aspx</URL>
  </TOPIC>
  <TOPIC>
    <TITLE>Driver Codes &amp; Tables</TITLE>
    <URL>/DriverCodes.aspx</URL>
  </TOPIC>
  <TOPIC>
    <TITLE>Driver Management Log</TITLE>
    <URL>/DriverManagement.aspx</URL>
  </TOPIC>
  <TOPIC>
    <TITLE>Driver / Vehicle Schedule</TITLE>
    <URL>/DriverSchedule.aspx</URL>
  </TOPIC>
</TOPICS>

<TOPICS TYPE="ORDERS DISPATCH SCHEDULING">
  <TOPIC>
    <TITLE>Attributes</TITLE>
    <URL>/workshop/author/css/reference/attributes.asp</URL>
  </TOPIC>
  <TOPIC>
    <TITLE>Length units</TITLE>
    <URL>/workshop/author/css/reference/lengthunits.asp</URL>
  </TOPIC>

A couple of items to remember if you want to use this or customize it:

1) The XslTransform code is .NET Framework 1.1 compatible and will not work in version 1.0 - you'll have to retrofit it if you are still using the 1.0 version of the Framework.
2) The Solution and project files are Visual Studio.NET 2003. If you are using Visual Studio.NET 2002, you'll need to either start a new web project and bring in the files, or use my conversion utlity featured in a previous article here to "back convert" it.

Would you like to see a sample in action? Just click on over here. (I've only hooked up a couple of the links, so don't be surprised if most items don't go anywhere). For those who are interested, I also whipped up the same menu in the form of a ServerControl (a separate article) which you can view here.


Download the code that accompanies this article


Peter Bromberg is a C# MVP, MCP, and .NET consultant who has worked in the banking and financial industry for 20 years. He has architected and developed web - based corporate distributed application solutions since 1995, and focuses exclusively on the .NET Platform.