A Generic Xml Dropdown Menu ServerControl
By Peter A. Bromberg, Ph.D.
Printer - Friendly Version

Peter Bromberg

If you haven't followed the evolution of my DHTML Dropdown XML/XSL menu in its first incarnation as an ASCX User Control, you may wish to view that in my first article here.

After completing the menu as a User Control, I decided to put in a little extra time and create a full-fledged ServerControl from it. To summarize, what I did with the original is take the "guts" of the old MSDN - style menubar with the drop-down menu lists, doctor it up to my liking, and port it to an ASP.NET ASCX User Control. The advantage of this is that you can have a different XML menu contents file for each instance of the User Control and therefore have a menu that is customized to the context of each individual page. The XSL, CSS and Javascript files that handle the transform, styling and menu actions will typically remain the same.



As discussed previously, User Controls are the easiest to author, being basically a reusable portion of ASP.NET UI code that has been saved with the the extension ASCX. However, User Controls are somwhat limited in scope, being only available for a single application at a time, and any properties we wish to set for our control cannot be done through Property Sheets. We can also use the @OutputCache directive so that output from our User Controls doesn't have to be recreated or loaded from the database on every view. By enabling caching in the User Control and allowing the host page to remain dynamic, we are accomplishing what is referred to as fragment caching.

ServerControls, on the other hand, are more complex and powerful than User Controls, having been precompiled beforehand and supporting Property Sheets, Design - time HTML, and the Toolbox. With ServerControls we can handle postback data, events, and the gamut of ASP.NET strongly - typed classes and actions. In addition, where appropriate, a ServerControl can be added to the GAC, giving all applications on the machine access to it. Finally, ServerControls are more performant because they are strongly - typed compiled classes and don't have to be compiled along with the code in the ASPX page that they are rendered on.

In the case of this Menu Control, I wanted to provide for the user to be able to supply their own completely different CSS, Javascript, XML and XSL files for each separate instance of the control through easily set Property Sheet entries. In addition, I wanted to provide a default file name for each of the above so that a "Stock" set of files can be used without having to set anything; simply drag and drop the control from the Toolbox onto your WebForm and you "got your menu"! I didn't add Caching, because the control renders so quickly, but you could certainly add this if desired.

First, let's take a look at the code in the main PAB.Web.MenuCtrl class:

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.Xml; 
using System.Xml.Xsl; 
using System.Xml.XPath; 
using System.IO; 
using System.Text; 

namespace PAB.Web
{
    /// <summary>
    /// Generic DHTML Menu Web Custom Control
    /// </summary>
    [DefaultProperty("Text"),
    Designer(typeof(MenuCtrlDesigner)),
        ToolboxData("<{0}:MenuCtrl runat=server></{0}:MenuCtrl>")]
    public class MenuCtrl : System.Web.UI.WebControls.WebControl
    {        
        private string stylesheet;
        private string scriptname;
        private string xmlfilename;
        private string xslfilename;

        [Bindable(true),
            Category("Appearance"),
            DefaultValue("menu.css")]
        public string StylesheetName
        {
            get
            {
                return stylesheet;
            }

            set
            {
                stylesheet=value;
            }
        }

        [Bindable(true),
        Category("Appearance"),
        DefaultValue("menu.js")]
        public string ScriptName
        {
            get
            {
                return scriptname;
            }

            set
            {
                scriptname=value;
            }
        }

        [Bindable(true),
        Category("Appearance"),
        DefaultValue("menu.xml")]
        public string XmlFileName
        {
            get
            {
                return xmlfilename;
            }

            set
            {
                xmlfilename=value;
            }
        }

        [Bindable(true),
        Category("Appearance"),
        DefaultValue("menu.xsl")]
        public string XslFileName
        {
            get
            {
                return xslfilename;
            }

            set
            {
                xslfilename=value;
            }
        }


        public MenuCtrl()
        {
            if(XmlFileName==null) XmlFileName="menu.xml";
            if(XslFileName==null) XslFileName="menu.xsl";
            if(ScriptName==null) ScriptName="menu.js";
            if(StylesheetName==null) StylesheetName="menu.css";         
        }
        /// <summary>
        /// Render the control
        /// </summary>
        /// <param name="output"> The HTML writer to write out to </param>
protected override void Render(HtmlTextWriter output)
{
    //Test to see if the developer forgot to fill in or 
    // has accidentally removed the default names ---
    if(XmlFileName.Length <5) XmlFileName="menu.xml";
    if(XslFileName.Length <5) XslFileName="menu.xsl";
    if(ScriptName.Length <4) ScriptName="menu.js";
    if(StylesheetName.Length <5) StylesheetName="menu.css";
            
    string XmlSystemFileName = System.Web.HttpContext.Current.Server.MapPath(XmlFileName); 
    string XslSystemFileName = System.Web.HttpContext.Current.Server.MapPath(XslFileName); 
    string ScriptSystemFileName = System.Web.HttpContext.Current.Server.MapPath(ScriptName); 
    string StylesheetSystemFileName = 
System.Web.HttpContext.Current.Server.MapPath(StylesheetName); string strContent="<link rel=\"stylesheet\" type=\"text/css\" href=\"" + StylesheetSystemFileName + "\">"; strContent+="<script language=\"javascript\" src=\"" + ScriptSystemFileName +"\"></script> "; 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); strContent+= sb.ToString(); output.Write(strContent); } } }

 

You can see above that I have created four public properties, one each for the menu.xml, menu.xsl, menu.css and menu.js respectively, each with a default name, and that they are bindable and will appear in the Appearance portion of the property sheet for the control. Note also that I make reference to my designer class, which in this case simply provides a nice representation of the control on the design surface of the WebForm, and that I've specified my Toolbox attributes as "MenuCtrl". Finally, in the "meat" of the control's guts, the Render method is overriden to grab each of the files we need, spit out the CSS stylesheet and javascript references, and finally add the results of our XSL Transform and render the entire DHTML dropdown menu to the page. You can see that I also ensure that if the developer has messed with the control to blow away the default property names or forgotten to put them in, we recreate the default names. In this manner, as long as you have your four required files with the default names as above, you will get your default menu and no errors.

You can't see it here, but I also have a Toolbox bitmap (our signature Eggheadcafe.com "Fried egg" logo). If this has the same namespace and class name as your control (in this case, "PAB.Web.MenuCtrl.bmp") and it is added to the project and set as "embedded resource", you'll get your nice custom Toolbox icon when you add the control to any of your Toolbox tabs, instead of the crummy "gear" icon provided by default.

At this point, we only need to add the most simple of designer classes:


using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Reflection;
using System.Web;
using System.Web.UI.WebControls;
using System.Web.UI;
using System.Web.UI.Design;

namespace PAB.Web
{
 public class MenuCtrlDesigner : ControlDesigner
  {
   private MenuCtrl menuctrl ;
     public MenuCtrlDesigner()
     {
     }
 public override string GetDesignTimeHtml()
 {
  return "<div id=\'div1\' style=\'align: center; COLOR:Blue; valign: middle; background-color:FFCC66; 
border-width:2px;\' >Menu Table Here</div>"
;
} public override void Initialize(IComponent component) { menuctrl = (MenuCtrl)component; base.Initialize(component); return; } } }

And that's it! This control doesn't need to be that smart because it's purpose is to be a "Generic DHTML XML Transformer" - it simply takes the pieces you give it - the XML, XSL, Javascript and CSS - and does its thing. The beauty of this control is, it really doesn't matter what kind of DHTML menu you want to put together - as long as you can come up with a way to encapsulate it in the four files (Xml, Xsl, Css and Js), my control will load them and faithfully render it on your favorite ASP.NET Page!

Please refer to the first article referenced above for a more complete description of these files. They can be completely different for every instance of the control; they can even be set programmatically "on the fly". The downloadable solution below is in Visual Studio.NET 2003. If you don't have 2003, either back- convert it using the utility from a recent article here, or simply start a new WebControl Library project and bring in the files.

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.