ASP.NET 页面模板 - 使用继承






4.76/5 (124投票s)
2002年9月20日
8分钟阅读

1425785

11781
一个演示如何创建使用面向对象继承的页面模板的教程。
引言
任何开发过商业网站的人都遇到过为网站创建模板的问题。对于大多数网站来说,所有页面都有很大一部分 HTML 是相同或相似的。典型网站布局的页眉、导航和页脚元素几乎出现在每个页面上。
一些开发人员会将这些标记放在每个页面的源文件中。任何维护过这种方式构建的网站的人都知道,进行全站更改是多么困难。您最终会依赖大量的搜索和替换操作,这在不创建需要手动调整才能修复的标记错误的情况下很难做到。
在传统的 ASP 编程中,就像许多其他基于服务器的编程环境一样,这通常通过包含文件来解决。页面被划分为逻辑部分,代表页眉、左导航、正文和页脚元素。为每个公共元素创建一个单独的包含文件,并将正文部分放在实际的 ASP 页面中。然后,页面包含适当的文件来构建页面的外观和感觉。这比以前的方法有了显著的改进,但仍然会带来一些维护问题。
首先,单个 ASP 文件必须包含包含正确支持文件所需的代码。这使得每个页面的内容与网站的模板紧密耦合。这也意味着当您向网站添加新页面时,您必须记住以正确的顺序设置所有正确的包含文件。
当您决定对网站进行重大的外观和感觉更改时,会出现另一个问题。如果您幸运的话,您也许可以在包含文件中进行所有更改。然而,大多数时候,包含文件和 ASP 页面之间的紧密耦合意味着您最终也必须编辑每个 ASP 页面。
随着 ASP.NET 的推出,开发人员获得了一套强大的新工具来帮助解决这些问题。ASP.NET 使用面向对象的开发范例。实际上,这意味着每个页面都是一个派生自 System.Web.UI.Page
的类。此类为 Web 开发人员提供了许多服务,包括缓存、呈现、响应和请求访问等。
那么问题来了:在创建网站时,我们如何最好地利用 ASP.NET 的面向对象特性?有没有比使用包含文件更好的方法来创建模板?
Web 用户控件
开发人员在开始使用 ASP.NET 时注意到的第一件事是引入了一种新的控件样式:Web 用户控件。用户控件允许开发人员将常见的 HTML 或服务器端代码块封装到可以在许多不同页面上重用的组件中。
用户控件不是使用 #include 指令在页面中使用的。相反,它们要么作为自定义标记(带有 Register 指令)放在 ASPX 文件中,要么通过 LoadControl
语句从服务器端代码中加载。
本文不会详细介绍创建和使用用户控件的细节;有很多其他资源涵盖了该主题。然而,它们比旧的包含文件方法有所改进,值得在此提及。
尽管如此,它们并未解决上述所有问题。例如,如果您将页面页眉封装在用户控件中,您仍然必须记住在网站的每个页面上使用它。如果您决定需要将另一个用户控件添加到您网站的另一部分,那么您将不得不再次编辑每个页面才能嵌入该用户控件。
此外,根据您网站页面的布局,您可能不得不在每个页面上放置一些格式或定位标记,以确保用户控件正好位于您想要的页面位置。这些标记在每个页面上都是通用的,我们应该能够找到一种方法让该代码只存在于一个地方。
简单的页面继承
如果您熟悉面向对象编程,那么您可能已经看到了一些熟悉的东西。当您有一段在多个类中都出现的代码时,常见的做法是通过创建基类来重构该代码,并将该代码“向上”移动到继承链的一个级别。为什么不在这里这样做呢?
由于所有 ASPX 页面都派生自 System.Web.UI.Page
,我们可以创建一个介于我们的页面类和 Page 类之间的基类。这个类将负责生成我们网站中使用的模板。然后,我们的页面类将派生自基类,并且只包含其特定任务所需的标记和服务器控件。为了说明这一点,让我们创建一个名为 PageBase
的示例基类,然后从中派生一个页面类。(本文中的所有示例均用 C# 编写,但您应该能够轻松地将其转换为 VB.NET 或任何其他 .NET 语言。)
using System;
using System.Web.UI;
public class PageBase : System.Web.UI.Page
{
private string _pageTitle;
public string PageTitle
{
get { return _pageTitle; }
set { _pageTitle = value; }
}
protected override void Render(HtmlTextWriter writer)
{
// First we will build up the html document,
// the head section and the body section.
writer.Write( @"
<html>
<head>
<title>" + PageTitle + @"</title>
</head>
<body>" );
// Then we allow the base class to render the
// controls contained in the ASPX file.
base.Render( writer );
// Finally we render the final tags to close
// the document.
Writer.Write( @"
</body>
</html>" );
}
}
让我们来看看这个基类中展示的一些有趣的点。首先,我们公开了一个名为 PageTitle 的属性,它将包含页面的标题。此属性的内容被写入页面的 <title>
部分。还要注意字符串文字前面使用的 '@' 符号。虽然在此示例中并非必需,但 '@' 符号是 C# 标记,表示后面的字符串文字不应展开转义序列。如果我们不这样做,我们就必须转义我们的斜杠。如果您在 VB.NET 中工作,则无需担心这一点。
现在我们将创建一个派生自 PageBase
的 ASPX 页面。我将使用代码隐藏页面,因此我们有两个文件。第一个文件扩展名为 .ASPX,包含页面的标记。
<%@ Page language="c#" Codebehind="SimplePageInheritance.aspx.cs" AutoEventWireup="false"
Inherits="SimplePageInheritance" %>
<form id="SimplePageInheritance" method="post" runat="server">
<h1>This is Page 1</h1>
<p>
This page demonstrates Simple Page Inheritance where the content is rendered
using the base class' Render() method. You cannot use Server Controls that
postback in the base class, they can only be used in the .ASPX page itself.
</p>
</form>
请注意,没有用于“标准”内容的标记,如 <html>
、<head>
和 <body>
,因为它们将在基类中呈现。代码隐藏类定义了在 Page 声明中引用的 SimplePageInheritance
类。
public class SimplePageInheritance : PageBase
{
public SimplePageInheritance()
{
PageTitle = "Simple Page Inheritance";
}
}
很酷吧?这个解决方案解决了前面提到的基于包含文件的模板的所有问题。然而,在某些情况下,它并没有解决所有问题。对于那些情况,我们需要高级页面继承。
高级页面继承
简单页面继承系统最大的问题是您不能在模板基类中放置任何会导致回发的服务器控件。原因是与 <form> 标记的位置有关。在简单页面继承中, <form>
标记包含在派生的 ASPX 页面中。因此,由基类呈现的任何标记都将位于 ASPX 文件标记之前或之后。例如,如果我们想要一个搜索系统包含在模板中,可能会出现这种情况。
我们可以通过多种方式解决此问题。在本例中,我们将 <form>
标记从 ASPX 文件移至基类。我们也不能仅仅将 <form> 标记呈现为字符串文字,因为在 ASPX 文件中,表单实际上是在标记发送到浏览器之前在服务器上处理的。因此,我们必须创建一个 Form 对象并将其插入到页面的 Controls 集合中。
using System;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
public class AdvancedPageInheritanceBase : System.Web.UI.Page
{
protected override void OnInit(System.EventArgs e)
{
BuildPage( GenerateHtmlForm() );
base.OnInit(e);
}
protected void BuildPage( HtmlForm form )
{
////////////////////////////////////////////////////////
// Build the page and include the generated form
this.Controls.AddAt( 0, new LiteralControl( @"
<html>
<head>
<title>" + PageTitle + @"</title>
</head>
<body>
") );
this.Controls.Add( form );
this.Controls.Add( new LiteralControl( @"
</body>
</html>
"));
}
private HtmlForm GenerateHtmlForm()
{
HtmlForm form = new HtmlForm();
AddSearch(form);
AddControlsFromDerivedPage(form);
return form;
}
private void AddSearch( HtmlForm form )
{
searchBox = new TextBox();
Button searchButton = new Button();
searchButton.Text = "Search";
searchButton.Click +=
new EventHandler( this.OnSearchButtonClicked );
form.Controls.Add( searchBox );
form.Controls.Add( searchButton );
form.Controls.Add( new LiteralControl("<br>") );
}
protected void OnSearchClick( object sender, EventArgs e )
{
// Do the search here
}
private void AddControlsFromDerivedPage(HtmlForm form)
{
int count = this.Controls.Count;
for( int i = 0; i<count; ++i )
{
System.Web.UI.Control ctrl = this.Controls[0];
form.Controls.Add( ctrl );
this.Controls.Remove( ctrl );
}
}
}
请记住,我曾说过有很多方法可以解决 <form>
标记放置问题。在此解决方案中,我们创建了一个 HtmlForm
控件,用 ASPX 页面的内容填充它,然后将其插入模板中。
这个系统效果很好,并且允许我们创建与模板完全无关的 ASPX 页面。以下是 ASPX 文件
<%@ Page language="c#" Codebehind="AdvancedPageInheritance.aspx.cs"
AutoEventWireup="false"
Inherits="PageInheritanceSample.AdvancedPageInheritance" %>
<h1>Advanced Page Inheritance</h1>
<p>This demonstrates the Advanced Page Inheritance Technique.</p>
代码隐藏文件与简单页面继承示例中使用的文件没有区别。请注意,我们的 ASPX 文件中没有 <form> 标记。
性能
我惊讶地发现,使用页面继承几乎没有性能差异。使用 Microsoft Application Center Test,我对三种不同版本的相似页面结构进行了页面加载测试
- 第一个页面使用用户控件封装了页眉、左导航和页脚部分。它没有使用任何页面继承。
- 第二个页面具有相同的外观和感觉以及相同的内容,但使用了简单页面继承中概述的方法。
- 第三个页面具有相似的外观和感觉(它包含一个像示例代码中的搜索框),并使用了高级页面继承中概述的方法。
下表显示了在我开发工作站上连续加载每个页面 5 分钟后的结果。正如您所见,差异很小。
页面 | 请求数 | 平均响应时间 (毫秒) | 每秒平均请求数 |
---|---|---|---|
WebUserControl.aspx | 16,693 | 10.53 | 55.64 |
SimplePageInheritance.aspx | 16,636 | 10.54 | 55.45 |
AdvancedPageInheritance.aspx | 16,965 | 10.20 | 56.55 |
结论
开发易于维护的网站和 Web 应用程序一直是一个挑战。随着时间的推移,所使用的技术已经从几乎没有发展到现代的面向对象技术,例如本文介绍的技术。这些技术可以由任何具有基本面向对象编程理解能力的人使用,并且几乎肯定会帮助您生成结构更好、更易于维护的代码。