Site Compass - ASP.NET 版的 Cookie Crumb 导航实现






4.77/5 (16投票s)
2004年11月13日
11分钟阅读

74946

1483
使用 ASP .NET 会话来跟踪和显示用户在您网站上的导航
引言
虽然 Cookie Crumb 导航可能听起来不熟悉,但它是你在互联网上几乎随处可见的东西。如果没有它,成千上万的用户会在成千上万的网站中迷路。事实上,Code Project 本身就使用了类似的东西。看看任何文章的顶部,你都会看到类似这样的东西
All Topics, C#, .NET >> C# Control >> Beginners
基本概念非常简单: 当用户浏览网站时,会显示先前访问过的页面的列表。这使用户能够轻松返回他们之前在网站内访问过的页面。
实现这个想法有很多方法。Code Project 版本似乎使用了硬编码的值,并假设用户沿着特定的路径访问了该项目。本文使用 ASP.NET 会话数据和 Web 自定义控件来跟踪用户访问他们正在查看的页面的实际路径,但也可以定义特定的路径。
一切的开始
这个项目最初只是一个下午的尝试,基于 ASP.NET 创建一个 Web 自定义控件,遵循 MSDN 的这篇文章
经过几个下午的努力,这个项目变成了一个可以在现实世界中使用的东西,所以我决定将其发布为我的第一篇 Code Project 文章。
演示项目
如果你想快速上手,以后再深入研究技术细节,你需要这样做:
- 下载演示项目。
- 将 zip 文件的内容解压到你的 IIS Web 根目录。
- 在 IIS 管理器中,为 SiteCompassTest 目录设置一个应用程序名称。
- 在浏览器中访问 http://yourserver/SiteCompassTest/WebForm1.aspx。
- 点击链接,看看 Site Compass 发挥作用。
开发 Site Compass
将控件添加到 Visual Studio
将控件添加到网页非常简单。在 VS.NET 中,你可以使用“添加/删除工具箱项”,然后选择控件的 DLL 文件。控件将出现在工具箱中,你可以将其添加到页面。控件属性
外观
控制 Site Compass 显示方式的属性。CompassStyle
HorizonatalHTML - 生成传统的水平样式路径
VerticalHTML - 生成垂直列表路径
DHTMLMenu - 使用 DHTML 菜单。
如果选择 DHTMLMenu 选项,你需要在 HEAD 部分手动添加对 JavaScript 的引用,类似这样:
<script src="siteCompassDHTML.js" type="text/javascript"></script>
CssClass
渲染时,Site Compass 包含在一个 DIV 标签内。此属性指示用于此 DIV 的 CSS 类的名称。项目中包含 5 个示例 CSS 类。这不适用于 DHTML 菜单罗盘样式。
CssClassLink
这指示用于 Site Compass 中链接的 CSS 类的名称。这不适用于 DHTML 菜单罗盘样式。
CurrentPageIndicator
这是显示在当前页前面的指示符字符。在演示项目中,我使用了 *。
ItemSeperator
这是罗盘项之间的分隔符。
PostFixCode
这是你想在 closing DIV 标签之前立即添加的任何 HTML 代码或文本。它包含在锚点标签中。
PreFixCode
这是你想在 opening DIV 标签之后立即添加的任何 HTML 代码或文本。它包含在锚点标签中。
行为
常规行为属性CaseSensitiveUrlComparison
渲染 Site Compass 时,它会将当前页面项的值与会话中已有的项进行比较。如果找到匹配项,则删除之后的所有项。如果将控件设置为区分大小写比较,则 page1.htm 和 Page1.htm 将被视为不同的页面。
DivName
这是分配给包含 Site Compass 的 DIV 的 name 属性。如果你想使用 JavaScript 引用它,这会很有用。
LinkLastItem
这表示罗盘中的最后一项是否是 href 链接。
MaxItems
这表示 Site Compass 中要显示的最大项数。如果总项数超过此值,则隐藏前面的项。如果用户回溯,旧项将重新出现。
模式
Normal - 渲染标准的网站罗盘。
DisplayOnly - 不将当前页面添加到罗盘,仅显示当前会话中的内容
OverrideTarget
这允许你用此属性覆盖所有罗盘项的目标属性。
SuppressIcons
允许你阻止显示任何罗盘项图标。
罗盘项属性
这是存储在会话中的信息DisplayTitle
你想在 Site Compass 中显示有关此页面的文本。
IconAlt
罗盘项图标的替代文本属性。
IconPath
罗盘项图标图像的根相对路径。
LinkTarget
罗盘项链接的目标。
杂项属性
SessionKey
这是一个唯一的密钥,用于标识驱动 SiteCompass 的会话数据。使用此属性,你可以在单个页面上拥有多个罗盘,它们都可以跟踪不同的内容——只要每个罗盘都有不同的 SessionKey 值。
CSS
源文件和演示存档都附带一个 CSS 文件来驱动控件。此样式表包含两个组件:
- DIV 和 HREF 链接样式
- DHTML 菜单样式
你可以随意修改这些样式,但请小心对待 DHTML 菜单样式,因为“有趣的事情”可能会发生。(有关此菜单的更多信息,请参阅文章底部的致谢链接。)如果你选择不使用 DHTML 菜单,则可以删除此样式表部分。
另一个需要考虑的问题是,控件包含用户访问过的一系列链接。所以这部分几乎无用。
A.YourLinkStyleName:link
{
TEXT-DECORATION : none;
COLOR : aqua;
}
以防万一你需要,你可以通过在 ASPX 页面的 head 中添加以下代码来将样式表添加到你的页面:
<link title="errata" media="screen" href="StyleSheet1.css" type="text/css" rel="stylesheet">
公共函数
public void GenerateCompass_HTML(HtmlTextWriter output, bool designer)
public void GenerateCompass_DHTMLMenu(HtmlTextWriter output, bool designer)
这两个函数之所以是 public,只是为了提供控件的设计时支持。基本上,这是两个渲染函数,用于为设计和运行时生成 HTML 输出。
public void ResetCompass()
此函数将重置罗盘并删除会话中当前的所有项。
public void ResetCompass(ArrayList compassData)
此函数将重置罗盘,并将给定的罗盘项 ArrayList 作为驱动罗盘的数据添加。
public void ResetCompass(CompassItem item)
将罗盘重置为仅包含给定的罗盘项。
public CompassItem GetCompassItemAt(int index)
检索 ArrayList 中给定索引处的罗盘项。
需要注意的一些事项。
当控件渲染时,其全部 HTML 代码都包含在 <DIV> 标签内。如上所示,有各种属性可以让你控制这一点。
当一个页面被添加到控件会话数据时,它使用 Page.Request.RawUrl
来获取相对于服务器根目录的当前页面路径。这也会检索分配给 URL 的任何查询字符串。因此,对于控件而言,page1.htm 和 page1.htm?id=2 是不同的页面。
它是如何工作的,或技术细节
当用户查看带有 Site Compass 控件的页面时,会收集某些信息并存储在用户的会话中。这些数据来源于两个位置:
Page
参数。- 您分配给控件的属性。
SiteCompassItem
)的对象中,然后放入一个 ArrayList 中,以便能够保持接收的顺序。当 ASP.NET 引擎请求控件生成其 HTML 时,它会将当前页面与会话中已有的页面进行比较。如果页面存在,则删除之后的所有项。如果不存在,则将其添加到当前项的末尾。然后使用此列表生成 HTML 代码,在浏览器中渲染罗盘。代码
我在这里没有引入任何惊人的新想法或概念,所以代码相对容易理解,并且我努力确保它在每个阶段都已完全注释。话虽如此,这里有几个值得一提的有趣点。比较对象
控件的核心是它能够检查页面是否已存在。控件的第一个版本只是使用一些连接的字符串来存储其数据,但这对我来说似乎有点过时,所以我寻找了一个更好的解决方案。最后,我创建了自己的自定义类来保存罗盘项数据,但这导致了一个有趣的问题。
例如,看看这段代码:
private bool TestMe()
{
testClass tc1 = new testClass("abc", "def", "ghi");
testClass tc2 = new testClass("123", "456", "789");
testClass tc3 = new testClass("xxx", "yyy", "zzz");
testClass tc4 = new testClass("abc", "def", "ghi"); //same values tc1
ArrayList array1 = new ArrayList();
array1.Add(tc1);
array1.Add(tc2);
array1.Add(tc3);
return array1.Contains(tc4);
}
使用这个类:class testClass
{
public string abc;
public string def;
public string ghi;
public testClass(string abc, string def, string ghi)
{
this.abc = abc;
this.def = def;
this.ghi = ghi;
}
}
TestMe()
函数将返回 false。要解决这个问题,你需要重写类的 Equals(object obj)
函数,因为 ArrayList.Contains(..)
使用它来比较对象。
在这个控件中,我使用了以下重写来实现对象比较:
public static bool _caseSensitiveUrlComparison = false;
...
...
...
public override bool Equals(object obj)
{
CompassItem compareObject = obj as CompassItem;
if (compareObject == null)
{
return false;
}
if (_caseSensitiveUrlComparison)
{
return this.ToString() == compareObject.ToString();
}
else
{
return this.ToString().ToLower() == compareObject.ToString().ToLower();
}
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public override string ToString()
{
return this.DisplayTitle + this.RawUrl + this.IconPath + this.IconAlt + this.Target;
}
ToString()
函数简单地获取所有类变量字符串并将它们连接成一个长字符串。当 ArrayList.Contains(..)
调用 .Equals(..)
时,它使用此字符串进行比较。
静态变量 _caseSensitiveUrlComparison
允许我在调用 ArrayList.Equals(..)
函数之前设置其行为。
例如,这段代码负责删除不需要的罗盘项。
//set the value of the static variable that
//indicates if object searches are case
//sensitive
CompassItem.CaseSensitiveUrlComparison = this.CaseSensitiveUrlComparison;
//check if the newItem exists in the current
//compass data this is possible as CompassItem
//has an overriden .equals()
if (compassData.Contains(newItem))
{
int index = compassData.IndexOf(newItem) + 1;
int count = compassData.Count - index;
compassData.RemoveRange(index, count);
}
覆盖默认属性
当你使用上面链接的 MSDN 文章中描述的方法创建自定义控件时,你会获得各种默认属性。在这个项目中,有几个属性我不想显示,因为它们与控件无关、未被使用或不重要:
隐藏这些属性只需设置 Browsable(false)
属性。
例如
[Browsable(false)]
public override FontInfo Font
{
get { return base.Font; }
}
有关此内容的更多信息,请参阅:
如果你查看代码,我隐藏的属性都与样式有关。隐藏它们的原因是我对生成的 HTML 代码质量(HTML 验证)完全满意,尤其是在与 HtmlTextWriter
结合使用时,所以我决定让控件完全由样式表驱动。
Visual Studio 设计器支持
没有什么比开发一个几乎没有设计器支持的控件更令人讨厌的了。此控件具有完整的设计器支持(DHTMLMenu 样式除外),因为它会生成虚拟条目,让你在页面中查看控件时看到它。
这在本文档顶部的 MSDN 文章中有描述,但我认为我应该在这里介绍它,因为我使用了运行时控件渲染器来生成设计时控件。
我创建了以下类,它与控件类位于同一个命名空间中:
public class SiteCompassDesigner : System.Web.UI.Design.ControlDesigner
{
public override string GetDesignTimeHtml()
{
//create the writers to handled the output
StringWriter sw = new StringWriter();
HtmlTextWriter tw = new HtmlTextWriter(sw);
//get and instance of the object calling the class
SiteCompass scc = (SiteCompass) Component;
//switch on the users selected style to generate
//the necessary designer code.
switch (scc.CompassStyle)
{
case SiteCompass.CompassStyles.HorizonatalHTML:
case SiteCompass.CompassStyles.VerticalHTML:
scc.GenerateCompass_HTML(tw, true);
break;
case SiteCompass.CompassStyles.DHTMLMenu:
scc.GenerateCompass_DHTMLMenu(tw, true);
break;
}
//return the StringWriter string to VS
return sw.ToString();
}
}
重写的 GetDesignTimeHtml()
函数在 Visual Studio 打开设计视图中的 ASPX 页面时被调用。你返回的任何字符串都将显示在你将控件放置在页面上的位置。你甚至可以做一些非常简单的事情,比如:
public class SiteCompassDesigner : System.Web.UI.Design.ControlDesigner
{
public override string GetDesignTimeHtml()
{
return "<b>My Cool Control</b>"
}
}
但这不是很酷!
在 MSDN 文章的例子中,它在 GetDesignTimeHtml()
函数中生成一个标签并返回其 HTML。我不想为运行时和设计时支持编写两个版本的罗盘渲染器。
根据 Site Compass 的样式,将使用以下 2 个渲染器之一:
public void GenerateCompass_HTML(HtmlTextWriter output, bool designer)
public void GenerateCompass_DHTMLMenu(HtmlTextWriter output, bool designer)
如你所见,每个渲染器都接受相同的参数。在运行时模式下,ASP.NET 引擎会调用:
protected override void Render(HtmlTextWriter output)
output
参数是当前页面的 HtmlTextWriter
。控件然后将其自身的 HTML 添加到编写器中,并将其传递回引擎进行进一步的代码添加和处理。我的 Render()
函数调用上面描述的渲染器,并将 designer
参数设置为 false
。这意味着数据从会话中检索并用于生成罗盘。在设计时没有会话,因此当 designer
为 true
时,代码会生成一个虚拟的项目列表,并为其生成 HTML 代码,该代码通过前面提到的 GetDesignTimeHtml()
函数传递回 Visual Studio。
可能已知的 Issues
- 查询字符串将始终用于比较,未来版本可能会有处理查询字符串的功能。
Possible Enhancements
- 能够设置属性驱动垂直列表的列表标签,目前控件只是在每个项后简单添加一个 <BR>。
- 覆盖并暴露更多
ArrayList
函数以增强开发人员控制。 - 跟踪一个项目但不显示它的能力
- 增强
.Equals()
覆盖的比较选项 - 使用
HtmlTextWriter
的.Indent
和.WriteLine()
提高输出代码的可读性
DHTML 菜单致谢
作为一个异想天开的想法,我内置了将控件渲染为 DHTML 菜单的功能。虽然这看起来是个好主意,但我在 DHTML 的黑暗艺术方面功力不足,所以我必须感谢那个教程的来源,它让我能够为 Compass 菜单样式生成代码:
请允许我在你的 DHTML 神功面前鞠躬。
历史
v1.0.0.0 - 这篇文章,这段代码就这样,伙计们,感谢收看。
还有一件事:不幸的是,我没有地方托管这个项目的可运行示例。 如果有任何人能提供一些带宽和服务器空间,请联系我。