Data Tutorial #2: Building our Master Page and Site Navigation Structure
June 22nd 2006 Update: We've now published a whole series of new data tutorials based on this origional post. You can read all about it here.
This past weekend I posted a step-by-step tutorial
on how to build a strongly-typed DAL (data access layer)
using Visual Web Developer
(which is free) and ASP.NET 2.0.
My plan over the next few weeks is to post many
follow-up samples that show various ways to use this DAL
to build common data scenarios using ASP.NET 2.0 (master
details, filtering, sorting, paging, 2-way data-binding,
editing, insertion, deletion, hierarchical data
browsing, hierarchical drill-down, optimistic
concurrency, and more).
Before doing that, I wanted to setup a common site
structure to help organize all of these samples. I wanted to make sure that all the samples in the site
shares a consistent look and feel, and that the site has
an easy to navigate site structure. Thankfully this is now very easy using the new ASP.NET
2.0 Master Page and Site Navigation
features (and can be done without having to write any
code).
Here is a screen-shot of the sample site skeleton I put
together:
You can download the site sample here. The below set
of tutorials walks-through how I built it:
Step 1: Adding a master page
Below is the basic site structure we were left with
after building our data acess layer for the Northwinds
database in my previous blog post. It has a
strongly typed DAL that goes against the Northwinds
database:
What I want to-do now is add a Master Page to the
site. Master
Pages is a new feature in ASP.NET 2.0 that enables me to
define a common layout structure and look and feel that
I can easily apply to multiple (or all) pages across a
site/app.
To add a Master Page, right click on the project and
choose “Add New Item”. Pick the Master Page Template from the “Add New Item”
dialog and name it “Site.Master”:
I want my site to use a CSS based layout approach. As such, I am using <div> elements to organize
the structure (as opposed to <table>
elements). Here
is the HTML I added:
<%@
Master
Language="VB"
CodeFile="Site.master.vb"
Inherits="Site"
%>
<!DOCTYPE
html
PUBLIC
"-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html
xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Northwind Data
Samples</title>
<link
rel="stylesheet"
type="text/css"
href="stylesheet.css"
/>
</head>
<body>
<div
id="wrapper">
<form
id="Form1"
runat="server">
<div
id="header">
<span
class="title">Northwind
Data Tutorials</span>
<span
class="breadcrumb">Todo:
Breadcrumb will go here...</span>
</div>
<div
id="content">
<asp:ContentPlaceHolder
ID="MainContent"
runat="server">
</asp:ContentPlaceHolder>
</div>
<div
id="navigation">
Todo: Menu will go here...
</div>
</form>
</div>
<div
id="footer">
<a
href="http://weblogs.asp.net/scottgu">http://weblogs.asp.net/scottgu</a>
</div>
</body>
</html>
Notice the <asp:contentplaceholder> control that
is in the middle <div> element. This is a new ASP.NET 2.0 control that I can use to
define regions of the master template that can be
“filled-in” or replaced by pages that use this
master. You can
have any number of <asp:ContentPlaceHolder>
controls you want within a master-page – all you need
to-do is make sure that each has a unique “id”
value. For the
sample above I’ve added one
<asp:contentplaceholder> and named it
“MainContent” (since it will be where pages on the site
fill in their content).
I am also then using an external CSS stylesheet
(“stylesheet.css”) to define the CSS for the page. When in design-view the Master Page looks like
this:
Step 2: Create a Home Page based on the Master
Page
Now that I have my Master Page defined, I can go ahead
and build pages using it. To build one, right click on the Project and choose
“Add New Item”, and select the “Web Form” item:
Notice that I’ve selected the “Select master page”
checkbox near the bottom of the dialog. This tells Visual Web Developer that you want to have
this new page use a Master Page. When you click the “add” button it will then ask you to
pick the Master Page to use:
When I select the “Site.Master” file we defined above,
it will create a new Default.aspx file like so:
<%@
Page
Language="VB"
MasterPageFile="~/Site.master"
AutoEventWireup="false"
CodeFile="Default.aspx.vb"
Inherits="_Default"
title="Untitled Page"
%>
<asp:Content
ID="Content1"
ContentPlaceHolderID="MainContent"
Runat="Server">
</asp:Content>
Visual Web Developer has automatically added an
<asp:content> control for the “MainContent”
contentplaceholder we defined earlier (note the
“ContentPlaceHolderId attribute defines which
contentplaceholder we want to override). I can then fill this content region in with the unique
content I want to add to the page:
<%@
Page
Language="VB"
MasterPageFile="~/Site.master"
AutoEventWireup="false"
CodeFile="Default.aspx.vb"
Inherits="_Default"
title="Home"
%>
<asp:Content
ID="Content1"
ContentPlaceHolderID="MainContent"
Runat="Server">
<h1>Welcome to the
Northwind Data Samples Site</h1>
<p>This site is being
built as part of a set of tutorials that show off some of
the new data access
and
databinding features in ASP.NET 2.0 and Visual Web
Developer.</p>
<p>Overtime, it will
include a host of samples that demonstrate: building a DAL
(data access layer)
using strongly
typed TableAdapters and DataTables, master details,
filtering, sorting,
paging, 2-way
data-binding, editing, insertion, deletion, hierarchical
data browsing,
hierarchical
drill-down, optimistic concurrency, and more.
</p>
<p>Please subscribe to
<a
href="http://weblogs.asp.net/scottgu">http://weblogs.asp.net/scottgu</a>
to follow
along as they are published.</p>
</asp:Content>
Note that the page will automatically pick up the CSS
stylesheet from the master-page (ASP.NET will also
automatically “rebase” the CSS url when the .aspx page
is in a sub-directory – so you don’t have to worry about
fully qualifying the style-sheet or doing weird “../”
hacks).
Note also that I have set the “Title” attribute on the
<%@ Page %> directive to “Home”. This attribute allows me to declaratively provide the
page title, even though the <head> element is
defined within our Master Page.
When I switch into design-view on the page, Visual Web
Developer will automatically show me a merged view of
the page that combines both the Master Page and deriving
Page:
ASP.NET will also merge the content together at runtime
– and send down a single html page when a browser
requests this page. The beauty of this model is that the Master Page layout
is defined in one single place – so if I need to make a
change I can update one file, and have every file that
is based on it within the site immediately update.
Step 3: Adding More Pages to the Site
I can use the Master Page to quickly build several more
pages for the site. Specifically, I decided to add two top level section
pages “Basic Data Scenarios” and “Advanced Data
Scenarios” that I think I might want to use to group
several samples around. I then built several stub sample pages in directories
that will live within them.
After adding many files to the project (all based on
the Master Page file), my directory structure looks like
this:
Step 4: Defining a Site Map for the Site
One challenge I am going to have as I build-out my site
is keeping the organizational structure of it in shape
(especially if I keep adding samples each week). I’m going to want to have some type of menu system that
helps users on the site navigate their way around. What I want to make sure I avoid is having to manually
build and then update this menu structure within HTML
every-time I make a change. Instead, what I want to-do is to define the site link
structure with a clean data-model that I can then
dynamically data-bind my UI against. The good news is that ASP.NET 2.0 makes this easy with
the new Site Navigation
system.
Using the Site Navigation system I can define the
logical “site map” structure of what my site looks like
– specifically how the site structure is logically laid
out (this can be different to how they are physically
organized on disk), and how the different pages are
organized in relation to each other. I can then access this structure at runtime using the
new “SiteMap” property on each ASP.NET page and
user-control. What is powerful about this API is that I can also use
it to keep track of where the current request is within
the site structure – as well as dynamically lookup a
request’s relation to other urls within the site (for
example: what is the “parent, “sibling” and “child”
nodes in the site-map relative to the current
request). Even fancier, I
can integrate the Site Map system with the new ASP.NET
2.0 Role Management security features – so that I can
view the structure through the “trimmed view” of what a
visiting user has permission to see (for example: pages
that are secured only for users in an admin role
wouldn’t show up in the Site Navigation model when a
guest is visiting the site). The combination of all these features makes it very
easy to quickly build menu navigation and bread-crumb
UI. You can also
use this module
to help your site integrate better with search engines
like Google.
To define our Site Navigation structure, I’m going to
use the built-in XML Site Map Provider that ships with
ASP.NET 2.0. Alternatively, if I wanted to store the site-map
structure in a database I could have configured my site
to use the cool new SQL Site Map Provider
(the beauty of the ASP.NET 2.0 provider model is that
all the code and data-binding logic to work against the
Site Navigation system stays the same regardless of
which provider implementation you have configured).
The XML-file based provider uses XML files that by
default have the name “Web.SiteMap” to define the site
hierarchy. To
create one of these files, right click on the project
and choose “Add New Item” and the “Site Map” item:
This will create an XML file with a default schema for
defining a site-layout. Note that Visual Web Developer provides automatic
intellisense for this XML structure.
For my particular sample, I choose to define the site
structure like so:
<?xml
version="1.0"
encoding="utf-8" ?>
<siteMap
xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
<siteMapNode
url="Default.aspx"
title="Home" description="Home">
<siteMapNode
url="Samples_Basic/BasicSamples.aspx" title="Basic Data Samples" description="Basic Data Samples">
<siteMapNode
url="Samples_Basic/Sample1.aspx" title="Samples 1" description="Samples 1" />
<siteMapNode
url="Samples_Basic/Sample2.aspx" title="Samples 1" description="Samples 2" />
<siteMapNode
url="Samples_Basic/Sample3.aspx" title="Samples 1" description="Samples 3" />
<siteMapNode
url="Samples_Basic/Sample4.aspx" title="Samples 1" description="Samples 4" />
</siteMapNode>
<siteMapNode
url="Samples_Advanced/AdvancedSamples.aspx" title="Advanced Data Samples" description="Advanced Data Samples">
<siteMapNode
url="Samples_Advanced/Sample1.aspx" title="Samples 1" description="Samples 1" />
<siteMapNode
url="Samples_Advanced/Sample2.aspx" title="Samples 1" description="Samples 2" />
<siteMapNode
url="Samples_Advanced/Sample3.aspx" title="Samples 1" description="Samples 3" />
<siteMapNode
url="Samples_Advanced/Sample4.aspx" title="Samples 1" description="Samples 4" />
</siteMapNode>
<siteMapNode
url="About.aspx"
title="About" description="About" />
</siteMapNode>
</siteMap>
It has a top-level node called “Home” – and then three
sub-nodes – “Basic Samples”, “Advanced Samples” and
“About”. The
“Basic Samples” and “Advanced Samples” then have several
sub-nodes beneath them.
Note that ASP.NET will automatically cache the Site
Maps’ XML file so that it doesn’t get read on each
request – instead it will only be parsed and processed
on the first request to the application, and then on
subsequent requests the cached version will be used
(note: this will automatically get re-generated anytime
the file changes).
I can then programmatically use the SiteMap.CurrentNode
property within an ASP.NET page at runtime to get back a
SiteMapNode object that represents where the current
request is within the above Site Map definition – as
well as what its parent, children, and sibling node urls
are (and what their friendly names are as well).
Step 5: Data-Building a Site Navigation Menu
Structure
ASP.NET 2.0 introduces a new concept called “data
source” controls – which are control objects that
provide a standard way to expose data that UI controls
can then bind against. The data source model is extensible, so you can easily
build your own Data Source controls to plug into the
system (this blog post
points to how to-do this). One of the built-in data-source controls that ASP.NET
2.0 ships with is the <asp:sitemapdatasource>
control – which makes it super easy to databind any UI
controls against the Site Navigation data model.
ASP.NET 2.0 ships with built-in <asp:treeview>
and <asp:menu> controls, which can be used to
create menu and tree-view structures based on the
site-map structure. To add and then data-bind the <asp:menu> control
to a <asp:sitemapdatasource> control on a page, I
could simple add this markup to the Site.Master file
(replacing the previous to-do menu comment):
<div
id="navigation">
<asp:Menu
ID="foo"
DataSourceID="SiteMapDataSource1"
runat="server">
</asp:Menu>
<asp:SiteMapDataSource
ID="SiteMapDataSource1"
ShowStartingNode="false"
runat="server"
/>
</div>
I would then have a fly-out menu for navigating around
the site.
Alternatively, if I want even greater control over the
HTML generated, I could use more basic (but also more
flexible) controls – like the ASP.NET Repeater
control.
For example, I could use the <asp:repeater> to
create an html <ul></ul> list like so:
<div
id="navigation">
<ul>
<li>
<a
href="default.aspx">Home</a>
</li>
<asp:Repeater
ID="foo"
DataSourceID="SiteMapDataSource1"
runat="server">
<ItemTemplate>
<li>
<a
href='<%#Eval("url")
%>'><%#Eval("Title")
%></a>
</li>
</ItemTemplate>
</asp:Repeater>
</ul>
</div>
<asp:SiteMapDataSource ID="SiteMapDataSource1" ShowStartingNode="false" runat="server" />
With our Web.SiteMap file defined like it is above,
this would then generate the below html at runtime:
<div
id="navigation">
<ul>
<li>
<a
href="default.aspx">Home</a>
</li>
<li>
<a
href='/DALWalkthrough/Samples_Basic/BasicSamples.aspx'>Basic Data Samples</a>
</li>
<li>
<a
href='/DALWalkthrough/Samples_Advanced/AdvancedSamples.aspx'>Advanced Data Samples</a>
</li>
<li>
<a
href='/DALWalkthrough/About.aspx'>About</a>
</li>
</ul>
</div>
If I wanted to show the next level of hierarchy in the
SiteMap as well, I could add another
<asp:repeater> within the first one to also
generate a sub-hierarchy of
<ul><li><ul> elements. For example:
<asp:Repeater
ID="foo"
DataSourceID="SiteMapDataSource1"
runat="server" enableviewstate="false">
<ItemTemplate>
<li>
<a
href='<%#Eval("url")
%>'><%#Eval("Title")
%></a>
<ul>
<asp:Repeater
ID="bar"
DataSource='<%#Container.DataItem.ChildNodes()
%>' runat="server">
<ItemTemplate>
<li><a
href='<%#Eval("url")
%>'><%#Eval("Title")
%></a></li>
</ItemTemplate>
</asp:Repeater>
</ul>
</li>
</ItemTemplate>
</asp:Repeater>
Note that VB allows me to write
Container.DataItem.ChildNodes() as a direct data-bound
expression. In
C# I would need to cast like so: ((SiteMapNode)
Container.DataItem).ChildNodes()
This would then generate the below HTML markup:
<div
id="navigation">
<ul>
<li><a
href="default.aspx">Home</a></li>
<li><a
href='/DALWalkthrough/Samples_Basic/BasicSamples.aspx'>Basic Data Samples</a>
<ul>
<li><a
href='/DALWalkthrough/Samples_Basic/Sample1.aspx'>Samples 1</a></li>
<li><a
href='/DALWalkthrough/Samples_Basic/Sample2.aspx'>Samples 1</a></li>
<li><a
href='/DALWalkthrough/Samples_Basic/Sample3.aspx'>Samples 1</a></li>
<li><a
href='/DALWalkthrough/Samples_Basic/Sample4.aspx'>Samples 1</a></li>
</ul>
</li>
<li><a
href='/DALWalkthrough/Samples_Advanced/AdvancedSamples.aspx'>Advanced Data Samples</a>
<ul>
<li><a
href='/DALWalkthrough/Samples_Advanced/Sample1.aspx'>Samples 1</a></li>
<li><a
href='/DALWalkthrough/Samples_Advanced/Sample2.aspx'>Samples 1</a></li>
<li><a
href='/DALWalkthrough/Samples_Advanced/Sample3.aspx'>Samples 1</a></li>
<li><a
href='/DALWalkthrough/Samples_Advanced/Sample4.aspx'>Samples 1</a></li>
</ul>
</li>
<li><a
href='/DALWalkthrough/About.aspx'>About</a>
</li>
</ul>
</div>
I
can then use a standard CSS styling approach to
customize the look and feel of this structure however I
want. Rachel Andrew
has a great book that I use called “The CSS Anthology: 101 Essential Tips, Tricks &
Hacks” that provides a really nice scenario based tutorial
approach to using CSS. I used a technique she came up with in chapter 4 to
make the markup above look like this when I add some CSS
to my StyleSheet.css file:
And now I have a nice looking menu for my site,
data-bound to the Site Navigation system, which is in
turn data-driven from my web.sitemap file.
Step 6: Adding a “Breadcrumb” navigation control to
the page
The last touch I want to add to my site is support for
a “bread-crumb” UI paradigm near the top of the page
that will help users easily figure out where they
currently are within the application. The good news is that this is super easy with ASP.NET
2.0 and the Site Navigation system.
All I need to-do is add the new <asp:SiteMapPath>
control to my “header” <div>:
<div
id="header">
<span
class="title">Northwind
Data Tutorials</span>
<span
class="breadcrumb">
<asp:SiteMapPath
ID="Breadcrumb" runat="server"></asp:SiteMapPath>
</span>
</div>
This will then output the site hierarchy of the current
node relative to the root node of the site map. For example, if I was on “Sample1” within the “Basic
Data Samples” section of the site, the above control
would automatically output this:
If I click on the “Basic Data Samples” hyperlink (which
is automatically generated by the breadcrumb – or I
could just use the menu link), it would adjust to:
No code is required.
Summary
I now have the basic site structure and layout defined
for the sample site I am going to use to build my data
samples. It has
a consistent, centralized, layout and look and feel
structure by using the new ASP.NET 2.0 Master Page
feature. And my
site and link structure is nicely encapsulated by the
ASP.NET 2.0 Site Navigation system, which I’m also using
to dynamically generate a navigation menu and breadcrumb
UI for the site:
Best of all, I didn’t need to write any code to enable
this, and I still get full WYSIWYG designer support
within Visual Web Developer.
Hope this helps. Lots of data samples are now going to follow…
Scott