asp.net2.0数据访问(3)-入门-母版页和站点导航

所有的界面友好的网站都有一个共同的特点:那就是都有贯穿整个网站的风格一致的页面布局和导航。本文重点介绍如果创建风格一致且易于更新的网页。
一、介绍
ASP.NET2.0引入了两个特性来实现贯穿整站的风格一致的页面布局和导航:主页面(MASTER PAGE)和站点导航(SITE NAVIGATION).主页面允许开发者开发一个带有可编辑区域的通用的模板。可以在网站的其它页面应用该模板,这样的ASP.NET页面只需要在可编辑区域放入内容元素就可以了。其它的部分都是一致的。这种模型对于开发者创建可编辑的统一外观的网站提供了很大的便利。

网站导航系统一方面提供了设计网站页面的机制;另一方面,提供了网站地图(SITE MAP)可编程查询的API.类似于Menu,TreeView和Site Map这些新的WEB导航控件。将会使得呈现部分或全部网站地图非常容易。我们将使用默认的网站导航,也就是用XML文件来定义网站地图。

为了演示上述概念,同时也使得整个系列的课程更加实用。本文将介绍如何定义统一的页面布局,实现网站地图,添加导航用户接口。学完本文后,将得到一个设计优美的基础网站结构供后续课程使用。

asp.net2.0数据访问(3)-入门-母版页和站点导航
步骤一:创建主页面
第一步是为整个站点创建主页面。现在我们的网站只包含强类型的数据集(NORTHWIND.XSD),BLL类文件,和数据库文件。还有配置文件和样式表文件。我们用DAL和BLL来区分这些页面和文件。

asp.net2.0数据访问(3)-入门-母版页和站点导航
可以通过右击项目,选择“添加新项”,选择主页面模板并命名为Site.master。

asp.net2.0数据访问(3)-入门-母版页和站点导航
通过在主页面中定义网站的统一布局。可以往实际视图里面摆放你所需要的任意的布局控件或Web控件。当然,你可以通过代码视图手动编写代码实现。在主页面中,使用层叠样式表来布局和限定样式,通过外部文件Style.css实现。下面的代码演示了如何通过CSS样式定位一个div的内容,使其靠左对其,而且宽度是200px。

Site.master:
<%@ Master Language="C#" AutoEventWireup="true"
CodeFile="Site.master.cs" 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>Working with Data Tutorials</title>
<link href="Styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="wrapper">

<form id="form1" runat="server">

<div id="header">
<span class="title">Working with Data Tutorials</span>
<span class="breadcrumb">
TODO: Breadcrumb will go here...</span>
</div>

<div id="content">
<asp:contentplaceholder id="MainContent"
runat="server">
<!-- Page-specific content will go here... -->
</asp:contentplaceholder>
</div>

<div id="navigation">
TODO: Menu will go here...
</div>
</form>
</div>
</body>
</html>

通过主页面即可以定义静态页的布局,也可以定义使用该主页面的可编辑区域的内容。可以通过ContentPlaceHolder控件来编辑内容区域。实际上,就是一个<div>。我们定义的主页面只有一个ContentPlaceHolder。但是可以在其上摆放多个ContentPlaceHolder控件。

上面的标识敲完后,切换到设计视图,将会显示如下布局。任何应用该主页面的页面都将拥有这个统一的布局。

asp.net2.0数据访问(3)-入门-母版页和站点导航
步骤2:为网站添加首页
当我们定义完主页面后,就开始为网站添加其它的asp.net页面了。先添加一个Default.aspx,作为首页。步骤:右击项目,选择添加新项,命名为Default.aspx。注意:要选中“使用主页面”复选框。

asp.net2.0数据访问(3)-入门-母版页和站点导航
单击“add”按钮,将让我们选择主页面,选择需要的主页面(如果有多个,我们只有一个)。

asp.net2.0数据访问(3)-入门-母版页和站点导航
当选择完主页面后,会生成如下代码:
Default.aspx:
<%@ Page Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true"
CodeFile="Default.aspx.cs" Inherits="_Default" Title="Untitled Page" %>
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent"
Runat="Server">
</asp:Content>

在@page指令中,有一个引用来使用主页面(MasterPafeFile="~/Site.master")。同时页面的描述语言,为每一个ContentPlaceHolder控件分配了一个ContentPlaceHolderID。你可以把在ContentPlaceHolder呈现的内容写到Content控件的标签中。在content标签中放置一些欢迎信息。
Default.aspx:
<%@ Page Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true"
CodeFile="Default.aspx.cs" Inherits="_Default" Title="Home" %>
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent"
Runat="Server">
<h1>Welcome to the Working with Data Tutorial Site</h1>

<p>This site is being built as part of a set of tutorials that
illustrate some of the new data access and databinding features in
ASP.NET 2.0 and Visual Web Developer.</p>

<p>Over time, it will include a host of samples that
demonstrate:</p>

<ul>
<li>Building a DAL (data access layer),</li>
<li>Using strongly typed TableAdapters and DataTables</li>
<li>Master-Detail reports</li>
<li>Filtering</li>
<li>Paging,</li>
<li>Two-way databinding,</li>
<li>Editing,</li>
<li>Deleting,</li>
<li>Inserting,</li>
<li>Hierarchical data browsing,</li>
<li>Hierarchical drill-down,</li>
<li>Optimistic concurrency,</li>
<li>And more!</li>
</ul>
</asp:Content>

@page指令的Title属性允许我们设置页面的title。甚至<titel>元素都可以用过主页面定义。我们也可以使用Page.Title属性,以编程的方式来设定。同时注意:在主页面中的对样式表的引用会自动更新,使之能够应用于如何使用主页面的asp.net页面中,而无论页面路径和主页面的关系如何。

切换到设计视图,你会看到页面的内容和浏览器的类似。注意:在实际视图中只有内容编辑区域是可编辑的。不可编辑区域呈灰色。

asp.net2.0数据访问(3)-入门-母版页和站点导航
当首页(Default.aspx)通过浏览器访问时,asp.net引擎自动合并主页面的内容和asp.net页面的内容,最终将请求的html返回给客户端一起显示。当主页面的内容更新,所有应用该主页面的都将在下次访问时及时更新。也就是说,主页面就是整个网站的布局模板,这使得其改变的内容迅速应用到整个网站中。

添加其它的asp.net页面
添加以下页面,用于以后的不同场景。整个系列将会有35个示例。首先来添加前面用到的。为了更好的区分示例的内容,我们为每一个系列添加一个分类文件夹。添加下面的文件夹:
BasicReporting
Filtering
CustomFormatting
最后,参考下图添加文件。注意:添加任何下面的文件时,记得应用“主页面”模板。

asp.net2.0数据访问(3)-入门-母版页和站点导航
步骤3:创建站点地图
通过设计站点导航让访问者有一个快速直接的访问网站内的很多页面对于设计者来说是一个挑战。首先,要定义站点的导航结构,然后将其转化为可导航的用户操作接口元素,如菜单。最后,整个过程需要移除现存的页面,并添加能够管理和维护的新页面来代替。在asp.net2.0之前,开发者往往具有自己的网站导航结构,并维护它,将之转化为可导航的页面元素。然而,在asp.net2.0中,开发者可以借助于非常灵活的内置的网站导航系统来实现。

asp.net2.0的站点导航系统提供了开发者定义一个站点地图和通过一个可编程的API访问其信息的方式。asp.net的初衷是希望用XML文件存储站点地图的数据。但是搭建在供应者模型上的站点导航系统被扩展成另外一种表示方式,那就是序列化。

在本文中,我们使用第一种方式。可以通过以下步骤创建站点地图:右击项目,选择“添加新项”,选择SiteMap模板,使用默认名Web.sitemap。

asp.net2.0数据访问(3)-入门-母版页和站点导航
默认的站点地图文件是一个XML文件。注意:VS提供了站点地图节点元素结构的只能提示。站点地图文件必须以<siteMap>为根节点,而且必须要包含一个<siteMapNode>子节点。第一个<siteMapNode>元素可以包含任意多个<siteMapNode>子节点。

模仿文件系统的结构定义站点地图。也就是,为每一个文件夹(总共三个);为在这些文件夹中的每一个asp.net页面创建一个<siteMapNode>子元素。代码如下:
web.sitmap:
<?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 title="Basic Reporting"
url="~/BasicReporting/Default.aspx"
description="Basic Reporting Samples">
<siteMapNode url="~/BasicReporting/SimpleDisplay.aspx"
title="Simple Display"
description="Displays the complete contents
of a database table." />
<siteMapNode url="~/BasicReporting/DeclarativeParams.aspx"
title="Declarative Parameters"
description="Displays a subset of the contents
of a database table using parameters." />
<siteMapNode url="~/BasicReporting/ProgrammaticParams.aspx"
title="Setting Parameter Values"
description="Shows how to set parameter values
programmatically." />
</siteMapNode>

<siteMapNode title="Filtering Reports"
url="~/Filtering/Default.aspx"
description="Samples of Reports that Support Filtering">
<siteMapNode url="~/Filtering/FilterByDropDownList.aspx"
title="Filter by Drop-Down List"
description="Filter results using a drop-down list." />
<siteMapNode url="~/Filtering/MasterDetailsDetails.aspx"
title="Master-Details-Details"
description="Filter results two levels down." />
<siteMapNode url="~/Filtering/DetailsBySelecting.aspx"
title="Details of Selected Row"
description="Show detail results for a selected item in a GridView." />
</siteMapNode>

<siteMapNode title="Customized Formatting"
url="~/CustomFormatting/Default.aspx"
description="Samples of Reports Whose Formats are Customized">
<siteMapNode url="~/CustomFormatting/CustomColors.aspx"
title="Format Colors"
description="Format the grid&apos;s colors based
on the underlying data." />
<siteMapNode
url="~/CustomFormatting/GridViewTemplateField.aspx"
title="Custom Content in a GridView"
description="Shows using the TemplateField to
customize the contents of a field in a GridView." />
<siteMapNode
url="~/CustomFormatting/DetailsViewTemplateField.aspx"
title="Custom Content in a DetailsView"
description="Shows using the TemplateField to customize
the contents of a field in a DetailsView." />
<siteMapNode url="~/CustomFormatting/FormView.aspx"
title="Custom Content in a FormView"
description="Illustrates using a FormView for a
highly customized view." />
<siteMapNode url="~/CustomFormatting/SummaryDataInFooter.aspx"
title="Summary Data in Footer"
description="Display summary data in the grids footer." />
</siteMapNode>

</siteMapNode>

</siteMap>

站点地图定义了整个网站的导航结构(层次结构,描述了网站的不同部分。Web.sitemap中的每一个<siteMapNode>元素代表了网站导航的一部分。

asp.net2.0数据访问(3)-入门-母版页和站点导航
asp.net通过.net框架下的sitemap类显示站点地图的结构。该类有一个CurrentNode属性,返回用于当前访问部分的信息。属性RootNode返回站点地图的根。两个属性都返回SiteMapNode实例。这些实例都包含用了遍历站点地图结构的属性,如:ParentNode、ChildNodes、NextSibling、PreviousSibling等等。

步骤4:基于站点地图显示菜单
可以象asp.net1.X那样通过编程,在asp.net2.0中访问数据,或者直接通过新增的数据源控件。有一些内嵌的数据源控件如SqlDataSource来访问相关的数据库;ObjectDataSource来访问类提供的数据。当然,也可以创建自定义的数据源控件。

数据源控件在asp.net页面和底层数据之间起代理作用。为了演示控件的使用,我们将专门添加其它的WEB控件,而且将其绑定到数据源控件。可以通过将数据源控件的ID属性指定为Web控件的DataSourceID属性的值,来实现Web控件的数据源绑定。

asp.net专门提供了SiteMapDataSource控件来访问sitemap的数据。其中,TreeView和Menu控件往往作为网站中用户导航的接口。为了绑定sitemap的数据到其中任一控件,只需要在页面上添加SiteMapDataSource控件,并将选定的导航控件(TreeView或Menu)的DataSourceID属性指定就可以了。我们可以通过如下的标识语言在主页面中添加菜单控件。
<div id="navigation">
<asp:Menu ID="Menu1" runat="server"
DataSourceID="SiteMapDataSource1">
</asp:Menu>

<asp:SiteMapDataSource ID="SiteMapDataSource1" runat="server" />
</div>

下面的代码,演示了如何绑定SiteMapDataSource到Repeater控件。
<div id="navigation">
<ul>
<li><asp:HyperLink runat="server" ID="lnkHome"
NavigateUrl="~/Default.aspx">Home</asp:HyperLink></li>

<asp:Repeater runat="server" ID="menu"
DataSourceID="SiteMapDataSource1">
<ItemTemplate>
<li>
<asp:HyperLink runat="server"
NavigateUrl='<%# Eval("Url") %>'>
<%# Eval("Title") %></asp:HyperLink>
</li>
</ItemTemplate>
</asp:Repeater>
</ul>

<asp:SiteMapDataSource ID="SiteMapDataSource1"
runat="server" ShowStartingNode="false" />
</div>

SiteMapDataSource每次返回站点地图层次中的一级,从根节点开始,然后是下一级,依此类推。当绑定SiteMapDataSource到Repeater的时候,首先返回第一级的枚举,并为每一个SiteMapNode实例化ItemTemplate。通过Eval(属性名)来访问SiteMapNode的特定的属性,上述例子就是通过这种方式来访问SiteMapNode的Url和Title属性的。

上例中的Repeater将产生如下代码:
<li>
<a href="/Code/BasicReporting/Default.aspx">Basic Reporting</a>
</li>

<li>
<a href="/Code/Filtering/Default.aspx">Filtering Reports</a>
</li>

<li>
<a href="/Code/CustomFormatting/Default.aspx">
Customized Formatting</a>
</li>

这些sitemap的节点(Basic Reporting,Filtering Reports,Customized Formatting)包含了站点地图的第二级节点。通过设置SiteMapDataSource控件的ShowStartingNode属性为false,可以使SiteMapDataSource绕过根节点返回sitemap层次结构的第二级节点。

我们同样可以添加Repeater控件,来绑定显示第二级节点的SiteMapNodes。如:
<asp:Repeater runat="server" ID="menu" DataSourceID="SiteMapDataSource1">
<ItemTemplate>
<li>
<asp:HyperLink runat="server"
NavigateUrl='<%# Eval("Url") %>'>
<%# Eval("Title") %></asp:HyperLink>

<asp:Repeater runat="server"
DataSource='<%# ((SiteMapNode) Container.DataItem).ChildNodes %>'
<HeaderTemplate>
<ul>
</HeaderTemplate>

<ItemTemplate>
<li>
<asp:HyperLink runat="server"
NavigateUrl='<%# Eval("Url") %>'>
<%# Eval("Title") %></asp:HyperLink>
</li>
</ItemTemplate>

<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
</li>
</ItemTemplate>
</asp:Repeater>

上述两个Repeater产生了如下代码(部分省略):
<li>
<a href="/Code/BasicReporting/Default.aspx">Basic Reporting</a>
<ul>
<li>
<a href="/Code/BasicReporting/SimpleDisplay.aspx">
Simple Display</a>
</li>
<li>
<a href="/Code/BasicReporting/DeclarativeParams.aspx">
Declarative Parameters</a>
</li>
<li>
<a href="/Code/BasicReporting/ProgrammaticParams.aspx">
Setting Parameter Values</a>
</li>
</ul>
</li>

<li>
<a href="/Code/Filtering/Default.aspx">Filtering Reports</a>
...
</li>

<li>
<a href="/Code/CustomFormatting/Default.aspx">
Customized Formatting</a>
...
</li>

然后,使用CSS样式,使其具有以下效果:

asp.net2.0数据访问(3)-入门-母版页和站点导航
这个主页面中的菜单绑定到了Web.sitemap,这意味着,您对sitemap的任何修改,都将会马上应用到使用该主页面的所有的页面中。

禁用ViewState
所有的asp.net控件都可以选择是否保持状态到ViewState(通过序列化成一个隐藏域来实现)。View state可以用来在回传时,记住控件的状态,比如数据的绑定。由于viewstate保持了回传时信息,使得发送给客户端的标识代码很多,而且,如果监控不利的话,会使页面变得很臃肿。在这方面,GridView控件的额外的标识代码尤其明显。这对于局域网用户或宽带用户,也许可以忽略不计。但是如果您使用拨号上网,可能会使您的数据要延迟数秒。

为了更清楚ViewState的影响,访问一个页面并查看其源代码。当然也可以借助于page tracing工具。您会发现view state信息会被序列化为一个隐藏域(_VIEWSTATE)在<form>中的<div>中。view state只在web表单中使用。如果您的asp.net页面中没有类似的语法(<form runat=“server”)。就不会生成这样一个隐藏域。

自动生成的隐藏域使得页面的标识代码增加了1800个字节。这还只是将Repeater控件绑定到SiteMapDataSource(保持状态)。如果使用一个包含很多字段和记录的GridView,view state可能增加不止十倍这个数据。

可以在页面级或者控件级,通过设置EnableViewState属性为false来禁用view state。因此减少了生成的代码。当禁用view state后,每次回传都会重新绑定数据。在asp.net1.x中,要由开发者来实现此功能;在asp.net2.0中,如果需要,可以重新绑定web控件到datasource控件。

为了减少view state,设置Repeater控件的EnableViewState属性为false。
<asp:Repeater runat="server" ID="menu" DataSourceID="SiteMapDataSource1"
EnableViewState="False">
<ItemTemplate>
... <i>ItemTemplate contents omitted for brevity</i> ...
</ItemTemplate>
</asp:Repeater>

修改后,发现源文件的大小只有52bytes,节省了97%。在我们的整个系列中将禁用Web控件的ViewState属性。唯一的一次就是讨论view state的使用时,要启用它来实现特定的功能。

步骤5:添加面包屑式导航
接下来添加面包屑式导航来完成主页面。该导航告诉用户当前所谓的站点结构中的位置。在asp.net2.0中,可以很容易通过添加SiteMapPath控件来实现。不需要任何节点。
<span class="breadcrumb">
<asp:SiteMapPath ID="SiteMapPath1" runat="server">
</asp:SiteMapPath>
</span>

下图展示了用户正在访问的站点地图结构,所有的路径最终都回到根节点。

asp.net2.0数据访问(3)-入门-母版页和站点导航
步骤6:为每部分添加首页
我们的整个系列课程分成不同的组,为每一个组建立一个文件夹,把相应的页面放到文件夹中。另外,每一个文件夹都包含一个Default.aspx页面。在BasicReporting文件夹中通过Default.aspx页面链接到SimpleDisplay.aspx,DeclarativeParams.aspx和ProgrammaticParams.aspx。我们可以基于SiteMap类和web控件再添加其它的信息。

下面再次使用Repeater控件创建一个无序列表,但这次要显示课程的题目和描述信息。由于将应用这个到每一个Default.aspx页面,所以我们创建一个用户控件(user control)。在站点中创建一个UserControl的文件夹,添加一个用户控件命名为:SectionLevelTutorialListing.ascx,并添加如下代码:

asp.net2.0数据访问(3)-入门-母版页和站点导航
SectionLevelTutorialListing.ascx:
<%@ Control Language="VB" AutoEventWireup="true"
CodeFile="SectionLevelTutorialListing.ascx.vb"
Inherits="UserControls_SectionLevelTutorialListing" %>
<asp:Repeater ID="TutorialList" runat="server" EnableViewState="False">
<HeaderTemplate><ul></HeaderTemplate>
<ItemTemplate>
<li><asp:HyperLink runat="server"
NavigateUrl='<%# Eval("Url") %>'
Text='<%# Eval("Title") %>'></asp:HyperLink>
- <%# Eval("Description") %></li>
</ItemTemplate>
<FooterTemplate></ul></FooterTemplate>
</asp:Repeater>

SectionLevelTutorialListing.ascx.cs:
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

public partial class UserControls_SectionLevelTutorialListing : UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
// If SiteMap.CurrentNode is not null,
// bind CurrentNode ChildNodes to the GridView
if (SiteMap.CurrentNode != null)
{
TutorialList.DataSource = SiteMap.CurrentNode.ChildNodes;
TutorialList.DataBind();
}
}
}

在前面的示例中,我们直接绑定SiteMap到Repeater。但在SectionLevelTutorialListing用户控件中,通过编程来实现。在Page_Load事件处理程序中,首先要确认是第一次访问页面,而且页面的URL映射到sitemap的节点。如果使用用户控件的页面没有相关的<siteMapNode>入口,SiteMap.CurrentNode将返回null,就不会有数据绑定到Repeater。假设我们有一个CurrentNode,将会绑定ChildNodes的集合到Repeater。由于在站点地图里设置Default.aspx页面作为每一个部分的父节点,所以这个节点将显示链接和该部分的描述信息。

等Repeater创建完成后,打开Default.aspx的设计视图。拖动用户控件到页面中就可以了。

asp.net2.0数据访问(3)-入门-母版页和站点导航

asp.net2.0数据访问(3)-入门-母版页和站点导航
二、总结
创建完主页面和sitemap后,我们就为系列课程创建一致的布局和导航,无论添加多少页面,页面布局和导航都可以很轻松的实现。特别是,使用了主页面Site.master和站点地图Web.sitemap。我们就不需要写任何代码就能得到统一的布局和导航。

完成DAL、BLL和UIL后,我们将开始介绍通用数据显示方式。在随后的三篇文章中,将着重介绍基本数据显示--通过BLL在GridView,DetailsView和FormView中显示数据。

快乐编程!!!