Adding a Navigation Bar User Control
Denise Wynn
December 5, 2004
Download the example code for this article. NavBarControlsCode.zip
In the previous article we created a page template that contained a couple of very simple user controls to render the banner and footer on pages. In this article we're going to implement a navigation bar control that will take an xml file as input and use that to dynamically generate the nav bar for our pages.
Navbar.ascx
In the test directory create a text file called navbar.ascx and add the following code to it.
<%@ Control runat="server" language="c#" classname="navbar" %>
<%@ Import namespace="System.Xml" %>
<script runat="server" language="c#">
private string sourcexml = null;
public string SourceXml
{
get{ return sourcexml;}
set{ sourcexml = value;}
}
public void Page_Load(object o, EventArgs e)
{
}
</script>
<div>
<asp:placeholder runat="server" id="NavBar"></asp:placeholder>
</div>
This is just the start of the code for the control - we'll add the rest as we go - but I want to explain some of the key elements of the control so far and how it differs from the controls we created in the previous article.
In the @ Control directive you can see that we've added a classname attribute with a value of "navbar". The classname attribute tells the page to compile this control using the specified classname. It allows us to reference the control as a class from the page. We'll go more into that in a later article.
We've also added an @ Import directive with a namespace attribute set to "System.Xml." ASP.NET imports several namespaces by default for all pages but System.Xml is not one of them. Since we will be using an xml file for our nav bar control we need to import this namespace ourselves.
In our script tag we've added a private field sourcexml and its get and set property accessors. This allows us to use a SourceXml property on our control when we implement it in our page. We've also added a Page_Load event that we'll add some code to in a bit.
After the script tag we have an asp:placeholder tag. This placeholder is where we will add all of the left nav content dynamically. Using a placeholder allows us to add as many items as we need to without worrying about exactly how many items there will be.
Using Xml to store our navigation links
At the moment our control doesn't actually do anything. If we implemented it in a web page we would see nothing because it doesn't actually have any output to display. We also don't have the xml file that specifies the links we want in our nav bar so lets do that now.
Create a text file in the test directory called nav.xml and add the following xml to it.
<?xml version="1.0" encoding="utf-8" ?> <items> <item title="Home" url="/test/default.aspx" /> <item title="Template" url="/test/template.aspx" /> <item title="First Page" url="/test/firstpage.aspx" /> </items>
Each item is one of the items that will appear in your nav bar using the title as the text to be displayed and the url as the link. By using an xml file for our navigation links we separate the content from the code in the user control. We can easily add links or change the entire list of links without having to modify any code.
Adding the link generation code to the control
Back in the navbar.ascx file add the following bold lines.
<%@ Control runat="server" language="c#" classname="navbar" %>
<%@ Import namespace="System.Xml" %>
<script runat="server" language="c#">
private string sourcexml = null;
private string contexturl = null;
public string SourceXml
{
get{ return sourcexml;}
set{ sourcexml = value;}
}
public void Page_Load(object o, EventArgs e)
{
contexturl = Request.Url.PathAndQuery;
if(SourceXml!=null)
{
XmlTextReader navreader = new XmlTextReader(Server.MapPath(SourceXml));
try
{
while(navreader.Read())
{
string Title = "";
string Url = "";
if (navreader.HasAttributes)
{
while (navreader.MoveToNextAttribute())
{
if(navreader.Name=="title")
Title = navreader.Value;
else if(navreader.Name=="url")
Url = navreader.Value;
}
}
if(Title!="" && Url!="")
RenderLink(Title, Url);
navreader.MoveToElement();
}
}
catch(Exception){}
finally
{
if(navreader.ReadState != ReadState.Closed)
navreader.Close();
}
}
else
this.Visible = false;
}
</script>
<div>
<asp:placeholder runat="server" id="NavBar"></asp:placeholder>
</div>
We added a contexturl field that will capture the current url to a string. This will allow us to do something special if the item being rendered in the nav bar happens to have the url for the current page.
The rest of the code verifies that the SourceXml property has been set on the control in the page and then loads that file into an XmlTextReader so we can evaluate and render each item in the nav bar.
All we are missing now is the RenderLink method that we call from the Page_Load event. Below the Page_Load add the following bold lines.
<%@ Control runat="server" language="c#" classname="navbar" %>
<%@ Import namespace="System.Xml" %>
<script runat="server" language="c#">
private string sourcexml = null;
private string contexturl = null;
public string SourceXml
{
get{ return sourcexml;}
set{ sourcexml = value;}
}
public void Page_Load(object o, EventArgs e)
{
//make sure there is an xml file reference from the control
if(SourceXml!=null)
{
contexturl = Request.Url.PathAndQuery;
XmlTextReader navreader = new XmlTextReader(Server.MapPath(SourceXml));
try
{
while(navreader.Read())
{
string Title = "";
string Url = "";
if (navreader.HasAttributes)
{
while (navreader.MoveToNextAttribute())
{
if(navreader.Name=="title")
Title = navreader.Value;
else if(navreader.Name=="url")
Url = navreader.Value;
}
}
if(Title!="" && Url!="")
RenderLink(Title, Url);
navreader.MoveToElement();
}
}
catch(Exception){}
finally
{
if(navreader.ReadState != ReadState.Closed)
navreader.Close();
}
}
else
this.Visible = false;
}
public void RenderLink(string Title, string Url)
{
Label s = new Label();
if(Url==contexturl)
{
//special case - we don't want to render the current
//pages nav item as a link
s.Controls.Add(new LiteralControl(Title));
}
else
{
HyperLink h = new HyperLink();
h.Text = Title;
h.NavigateUrl = Url;
s.Controls.Add(h);
}
NavBar.Controls.Add(s);
NavBar.Controls.Add(new LiteralControl(" "));
}
</script>
<div>
<asp:placeholder runat="server" id="NavBar"></asp:placeholder>
</div>
The RenderLink method takes the title and url strings that were pulled from the xml item node and uses them to create a HyperLink control. The HyperLink control is added to a Label control and the Label control is added to the NavBar Placeholder control that resides in the body of our user control. I've used a Label control here to contain each link because they render as span elements on the client it's easy to add styles to them.
We've also added a check to see if the Url string that is being passed in matches the contexturl field. If it does then we know that this item in the nav bar is for our current page and we are not going to render it as a hyperlink. We use the LiteralControl class to add the Title string to the Label control for this.
For now we've also added a couple of non-breaking spaces after each Label in our nav bar. Since we haven't added any styles yet if we didn't add the spaces all the text would run together.
Updating the template
Now our control is finished so all we need to do is add it to our template. Open the template.aspx file that we created in the previous article for editing. Add the following bold lines.
<%@ Page runat="server" language="c#"%>
<%@ Register TagPrefix="nbs" TagName="banner" Src="banner.ascx" %>
<%@ Register TagPrefix="nbs" TagName="footer" Src="footer.ascx" %>
<%@ Register TagPrefix="nbs" TagName="navbar" Src="navbar.ascx" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>User Control Template</title>
</head>
<body>
<nbs:banner runat="server" />
<nbs:navbar sourcexml="nav.xml" runat="server"/>
<nbs:footer runat="server" />
</body>
</html>
We've registered the navbar control the same way we registered the banner and footer controls previously. We've also added an nbs:navbar tag to the page below the nbs:banner tag. Unlike the other tags this tag has a sourcexml property that we have set to the location of our nav.xml file. This corresponds to the SourceXml property in our navbar control.
Now if you view the template.aspx file in a browser you will see banner with the navigation links below it and the footer below the navigation links. It should look something like the following image.
Putting it to work
Now let's create a couple pages from our template so we can actually see our banner in action. In the test directory make a copy of template.aspx and call it firstpage.aspx. Open firstpage.aspx in a text editor and add the following bold line.
<%@ Page runat="server" language="c#"%>
<%@ Register TagPrefix="nbs" TagName="banner" Src="banner.ascx" %>
<%@ Register TagPrefix="nbs" TagName="footer" Src="footer.ascx" %>
<%@ Register TagPrefix="nbs" TagName="navbar" Src="navbar.ascx" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
<html>
<head>
<title>User Control Template</title>
</head>
<body>
<nbs:banner runat="server" />
<nbs:navbar sourcexml="nav.xml" runat="server"/>
<p>This is my first page with my new template.</p>
<nbs:footer runat="server" />
</body>
</html>
In the test directory make another copy of template.aspx and call it default.aspx. Open default.aspx in a text editor and add the following bold line.
<%@ Page runat="server" language="c#"%>
<%@ Register TagPrefix="nbs" TagName="banner" Src="banner.ascx" %>
<%@ Register TagPrefix="nbs" TagName="footer" Src="footer.ascx" %>
<%@ Register TagPrefix="nbs" TagName="navbar" Src="navbar.ascx" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>User Control Template</title>
</head>
<body>
<nbs:banner runat="server" />
<nbs:navbar sourcexml="nav.xml" runat="server"/>
<p>This is my default page made from my new template.</p>
<nbs:footer runat="server" />
</body>
</html>
Save default.aspx and open a browser and go to http://localhost/test/default.aspx. In the nav bar the Home item should just be plain text while the Template and Home Page items should be links. Click each link in the nav bar and when that page loads you should notice that the current pages item is text and the other items are links. If you create a page from the template that doesn't have a link in the nav bar then loading that page will show all of the nav bar items as hotlinks.
In Conclusion
You now have extended your template to include some basic navigation and have learned how to add code to a user control that dynamically generates html output. You've also learned a bit about separating content from code by storing the navigation links in an xml file.
