窍门程序回顾
在变化越来越快的世界中,总是存在窍门程序。现在,它们是开发人员的一种聪明、前沿的创造。将来的某一天,它们有可能成为主流代码或最佳实践方式,并集成到产品或过程中。窍门程序的成功标志是,有一天它变成了一种标准。
本章主要介绍已成功的窍门程序。我们将回顾ASP.NET v1.1中已得到广泛认可的一组窍门程序。它们非常有效,Microsoft已在ASP.NET v2.0中添加了对它们的支持。本章并不是重新编写已发明的窍门程序,而主要探讨先驱和他们的工作,正是他们出色的工作使Microsoft在ASP.NET v2.0中添加了对窍门程序的支持。对于已转而使用ASP.NET 2.0的开发人员来说,要学习的是如何将这些窍门程序集成到.NET Framework中和相关的Visual Studio 2005支持。了解以前的窍门程序如何影响ASP.NET的当前版本,将使我们更加欣赏窍门程序,并激励我们开发出能正面影响ASP.NET未来版本的窍门程序。
1.1 由ASP.NET 2.0替代的向导窍门程序
向导是引导用户完成某个过程的用户界面工具。这里讨论的是添加到应用程序中的向导。开发人员已使用它们多年,例如安装程序,把复杂的操作简化为一系列步骤的工具等。它们传统上是客户端桌面应用程序的一个标准部分,但在Web应用程序中以窍门程序的面目出现。
1.1.1 ASP.NET 向导先驱
在早期的ASP.NET v1.1中,John Peterson编写的向导窍门程序是作为一个范例在ASP101.com(asp101.com/samples/wizard_aspx.asp)上发布的,称为“向导的ASP.NET版本(多页面窗体)”。这是从以前经典的ASP 3.0的实现更新而来的。在ASP.NET 2.0向导宣布支持它后,Tom Blanchard在123aspx.com/redir.aspx?res=32798上撰写了文章“CodeSnip: Simulating the ASP.NET 2.0 Wizard Control with ASP.NET 1.x”。一些人还使用另一个已有的解决方案:User Interface Process(UIP) Application Block上的Wizard Navigator,它是由Microsoft Patterns and Practices Group在http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ dnpag2/html/cabctp.asp上创建的。
提示:
Microsoft的在线链接停了许多,但搜索MSDN Architecture Center的Patterns and Practices资源页面,仍能找到UI Application Block。
1.1.2 ASP.NET v2.0中的向导
向导在ASP.NET 2.0中不再是一个窍门程序,它们很容易使用,在Visual Studio 2005上得到了很好的支持。下一节将介绍如何实现向导。
ASP.NET 2.0向导实现为控件。要使用它们,只需把一个新的向导控件添加到可视化设计器中,设置其属性即可。本节描述如何通过一个范例菜单选择应用程序来实现ASP.NET 2.0向导。在这个过程中要进行许多选择,向导将根据输入参数管理导航。下面的步骤说明了如何创建范例应用程序:
(1) 创建一个新的Web项目。为此,选择File | New | Web Site,再选择ASP.NET Web Site模板,把目录位置改为WizardDemo,
(2) 单击OK按钮,Visual Studio 2005就会创建一个新项目。
(3) 如果当前在HTML视图上,如所示,就单击编辑器左下角的Design链接,进入设计视图。
(4) 在设计视图中,找到工具箱,它位于IDE的左边。把鼠标指针停放在Toolbox标签上,就可以打开它。单击工具箱的Standard区域,选择Wizard控件。把一个Wizard控件拖放到窗体上,
(5) 选择View | Properties Window,显示Properties窗口。在这个窗口中,把HeaderText属性设置为Menu Selector。
(6) 扩展Wizard控件,使其大小大约为400×200。
(7) Wizard控件在第一次添加到Web窗体上时,显示了两个链接,分别称为Step 1和Step 2。选择Wizard控件中的Step 1。
(8) 在Wizard控件中选择编辑区域,输入“What is your Eating Preference?”。
(9) 从工具箱中把一个RadioButtonList控件拖放到Wizard控件的编辑区域,在Action List上选择Edit Items。在ListItemCollection编辑器上给两个新项的Text属性添加Meat Eater和Vegetarian项。
(10) 在Wizard控件中选择Step 2。
(11) 在编辑区域输入“Please Select Main Course:”。
(12) 把一个RadioButtonList控件从工具箱拖放到Wizard控件的编辑区域。选择Action List中的Edit Items,在ListItemCollection编辑器中为三个新项的Text属性添加Chicken、Fish和Steak项。
(13) 在Wizard Tasks列表中单击Add/Remove WizardSteps,给Wizard控件添加Step 3。
(14) 选择刚才添加到Wizard控件中的新链接Step 3。
(15) 在编辑区域中输入“Please Select Main Course:”。
(16) 把一个RadioButtonList控件从工具箱拖放到Wizard控件的编辑区域。选择Action List中的Edit Items,在ListItemCollection编辑器中为三个新项的Text属性添加Bread、Salad和Veggie Tray项。
(17) 在Wizard Tasks列表中单击Add/Remove WizardSteps,给Wizard控件添加Step 4。
(18) 选择刚才添加到Wizard控件中的新链接Step 4。
(19) 在编辑区域中输入“Please Select Beverage:”。
(20) 把一个RadioButtonList控件从工具箱拖放到Wizard控件的编辑区域。选择Action List中的Edit Items,在ListItemCollection编辑器中为三个新项的Text属性添加Coffee、Water和Wine项。
这将为包含多个步骤的向导创建HTML。程序清单1-1列出了这个页面的HTML,其中包含了本节后面进行的修改。程序清单1-1中的HTML是我们在执行第1~20步时,由Visual Studio 2005自动生成的。在Visual Studio 2005编辑器的底部选择HTML选项卡,从设计视图切换到HTML视图。此时,将看到程序清单1-1中的代码。
程序清单1-1 包含多个步骤的ASP.NET v2.0 Wizard控件
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs"
Inherits="_Default" %>
<!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>Wizard Demo</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Wizard ID="Wizard1" runat="server"
ActiveStepIndex="0" HeaderText="Menu Selector"
Height="200px" Width="400px"
OnFinishButtonClick="Wizard1_FinishButtonClick"
OnNextButtonClick="Wizard1_NextButtonClick">
<WizardSteps>
<asp:WizardStep runat="server" Title="Step 1">
What is your eating preference?<br />
<br />
<asp:RadioButtonList ID="RadioButtonList1"
runat="server">
<asp:ListItem>Meat Eater</asp:ListItem>
<asp:ListItem>Vegetarian</asp:ListItem>
</asp:RadioButtonList>
</asp:WizardStep>
<asp:WizardStep runat="server" Title="Step 2">
Please select main course:<br />
<br />
<asp:RadioButtonList ID="RadioButtonList2"
runat="server">
<asp:ListItem>Chicken</asp:ListItem>
<asp:ListItem>Fish</asp:ListItem>
<asp:ListItem>Steak</asp:ListItem>
</asp:RadioButtonList>
</asp:WizardStep>
<asp:WizardStep runat="server" Title="Step 3">
Please select main course:<br />
<br />
<asp:RadioButtonList ID="RadioButtonList3"
runat="server">
<asp:ListItem>Bread</asp:ListItem>
<asp:ListItem>Salad</asp:ListItem>
<asp:ListItem>Veggie Tray</asp:ListItem>
</asp:RadioButtonList>
</asp:WizardStep>
<asp:WizardStep runat="server" Title="Step 4">
Please select beverage:<br />
<br />
<asp:RadioButtonList ID="RadioButtonList4"
runat="server">
<asp:ListItem>Coffee</asp:ListItem>
<asp:ListItem>Water</asp:ListItem>
<asp:ListItem>Wine</asp:ListItem>
</asp:RadioButtonList>
</asp:WizardStep>
</WizardSteps>
</asp:Wizard>
</div>
</form>
</body>
</html>
从程序清单1-1中的步骤可以看出,用户要么是一位荤食者,要么是一位素食者。他们应只看到对应其选项的菜单项。
为了指定不同的菜单,Wizard控件必须根据用户在Step 1中做出的选择,导航到不同的步骤。ASP.NET的Wizard控件支持事件,我们将使用事件根据用户的饮食喜好来确保显示相应的步骤。程序清单1-2说明,在用户单击Next按钮时,如何正确导航。单击Properties窗口上的闪电图标,并双击NextButtonClick事件,就会生成事件处理程序框架,该处理程序的默认名称是Wizard1_NextButtonClick。在Wizard1_NextButtonClick事件处理程序中输入算法,如程序清单1-2所示。
程序清单1-2 修改向导的顺序进程
protected void Wizard1_NextButtonClick(
object sender, WizardNavigationEventArgs e)
{
if (Wizard1.ActiveStepIndex == 0)
{
if (RadioButtonList1.SelectedValue == "Vegetarian")
{
Wizard1.ActiveStepIndex = 2;
}
}
if (Wizard1.ActiveStepIndex == 1)
{
Wizard1.ActiveStepIndex = 3;
}
}
捕获了NextButtonClick事件后,如程序清单1-2所示,就可以控制Wizard控件的导航顺序。这个事件在单击Next按钮时,在当前页面的环境下调用。因此,首先检查ActiveStepIndex,确定当前页面,该页面用一个基于0的整数表示。所有的步骤控件都可以在回送过程中访问,所以很容易获得它们的值。在这里,如果用户的饮食喜好是Vegetarian,代码就把ActiveStepIndex设置为Step 3页面,即索引2。
如果用户的饮食喜好是Meat Eater,Wizard控件一般将导航到索引为1的Step 2上。此时,单击Next按钮,会使用户进入索引为3的Step 4上,选择一种饮料。导航到Step 3上的素食者在单击Next按钮后,一般会进入Step 4。
Wizard控件到达末尾后,Next按钮将被Finish按钮替代。此时可以捕获Finish按钮的事件,该事件在单击Finish按钮时执行,如程序清单1-3所示。
程序清单1-3 处理Finish按钮事件
protected void Wizard1_FinishButtonClick(
object sender, WizardNavigationEventArgs e)
{
if (RadioButtonList1.SelectedValue == "Vegetarian")
{
Response.Write(
"Your meal will be " +
RadioButtonList3.SelectedValue +
" and " +
RadioButtonList4.SelectedValue);
}
else
{
Response.Write(
"Your meal will be " +
RadioButtonList2.SelectedValue +
" and " +
RadioButtonList4.SelectedValue);
}
}
在程序清单1-3中,我们主要感兴趣的是,用户的饮食喜好是Meat Eater,还是Vegetarian,以便从相应的控件中提取值。代码通过Response.Write,根据用户在运行向导时是选择Meat Eater还是Vegetarian,输出选中的菜单项。
在添加步骤、处理事件和修改属性的过程中,可以看出ASP.NET v2.0的Wizard控件有多么复杂。这是一个非常有用的窍门程序,目前已转化为一个完整的产品特性。
Master Page可以为网站的多个页面开发出具有统一外观和操作方式的可视化模板。在ASP.NET v1.1中,开发人员需要使用几种不同的技术完成类似的任务,包括继承自一个公共的页面基类的用户控件和一组使用模板的Master Page。本节将介绍如何在ASP.NET v1.1中实现Master Page模板窍门程序的资源,接着描述如何把它们实现为ASP.NET v2.0中的一个主要特性。
1.2.1 ASP.NET v1.1中的Master Page模板
在ASP.NET v1.1中,与ASP.NET v2.0的Master Page最接近的技术是模板管理技术。Paul Wilson在wilsondotnet.com上撰写了几篇有关页面模板的文章。2003年10月,他给ASP联盟发表了“Page Templates: Introduction”一文(http://authors.aspalliance.com/ PaulWilson/Articles/?id=14),描述了用户控件和页面继承技术。而比较有趣的是他给ASP联盟发表的文章“MasterPages: Introduction”(http://authors.aspalliance.com/PaulWilson/ Articles/?id=13)。对于许多开发人员来说,这是ASP.NET v2.0中的Master Page的第一次亮相。Paul Wilson的文章介绍了如何用ASP.NET v1.1实现相同的功能。
在ASP.NET v1.1中实现模板化有其他可靠的方式。在Philipp Sumi的Code Project文章“A Component-Based Template Engine for ASP.NET”(codeproject.com/aspnet/sumitemplate controls.asp)中,展示了在ASP.NET v1.x中使用Master Page模板的另一种方法。还有其他例子,但Paul Wilson和Philipp Sumi的文章是可以在ASP.NET v1.1中使用的可靠参考。
1.2.2 ASP.NET v2.0中的Master Page
在ASP.NET v2.0中,Master Page从一个窍门程序成长为一个重要特性。它们内置于ASP.NET中,拥有新的页面指令、占位符和控件,可以为应用程序或网站创建出统一的外观和操作方式。另外,它们还在Visual Studio 2005中获得了良好的支持。
1.2.3 实现Master Page
在ASP.NET v2.0中,Master Page实现为一个Web页面,其中包含内容页面可以使用的默认外观和操作方式。多个内容页面只需提供合并到Master Page中的内容标记,即可使用这个Master Page的统一外观和操作方式。本节的示例将使用VS 2005创建一个Master Page和简单的内容页面。
首先,创建一个新的Web项目,删除自动创建的Default.aspx页面。后面在创建使用Master Page的内容页面时,将再次创建Default.aspx。
(1) 要创建Master Page,在Solution Explorer中右击Web项目,选择Add New Item,再选择Master Page,给它指定名称Company.master,然后单击OK按钮。这将创建一个新的Master Page。
(2) 如果Master Page在HTML视图中,就单击编辑器左下角的Design按钮,把它切换到设计视图。选择Layout | Insert Table,给Company.master添加一个新表。
(3) 在Insert Table对话框中,选择Template,再从下拉列表中选择“Header, footer and side”。
(4) 表添加到页面上后,选择最上面一行的单元格,输入“My Company Header”,选择第二行的第一个单元格,输入“Menu Goes Here”,再选择第三行(最后一行)的单元格,输入“Copyright (C) 2006 My Company, All Rights Reserved”。
(5) 第一次创建Master Page时,它会自动添加一个ContentPlaceHolder控件。Company. master也是如此。把ContentPlaceHolder控件拖放到刚才插入Company.master的表中第二行的第二个单元格中。
创建和修改Company.master后,就可以单击编辑器左下角的HTML按钮,查看自动生成的HTML代码了。程序清单1-4列出了为Company.master进行上述修改后的HTML。
程序清单1-4 格式化定制页面元素的Master Page:Company.master
<%@ Master Language="C#" AutoEventWireup="true"
CodeFile="Company.master.cs" Inherits="Company" %>
<!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>My Company</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<table border="0" cellpadding="0" cellspacing="0"
style="width: 100%; height: 100%">
<tr>
<td colspan="2" style="height: 200px">
<h1>My Company Header</h1>
</td>
</tr>
<tr>
<td style="width: 200px">
Menu Goes Here</td>
<td>
<asp:contentplaceholder id="ContentPlaceHolder1" runat="server">
</asp:contentplaceholder>
</td>
</tr>
<tr>
<td colspan="2" style="height: 200px">
Copyright (C) 2006 My Company, All Rights Reserved
</td>
</tr>
</table>
</div>
</form>
</body>
</html>
在程序清单1-4中,首先要注意,在页面的顶部包含一个Master指令。如codefile和inherits特性所述,可以给Master Page添加一个代码文件。该页面的大多数内容都是HTML格式化标记。
这个页面的另一个相关部分是contentplaceholder元素。可以在这里放置默认内容,但内容页面将使用id指定其内容显示在什么地方,这将覆盖默认的内容。
现在可以将这个Master Page用于任意内容页面了。创建内容页面的过程与创建其他Web窗体非常相似。在Solution Explorer中右击Web项目,选择Add New Item,再选择Web Form,接受推荐的名称Default.aspx。选中Select Master Page选项,最后单击OK按钮。程序清单1-5列出了修改后的新内容页面。
程序清单1-5 使用Master Page的内容页面: Default.aspx
<%@ Page
Language="C#"
MasterPageFile="~/Company.master"
AutoEventWireup="true"
CodeFile="Default.aspx.cs"
Inherits="_Default"
Title="Content Page" %>
<asp:Content ID="Content1"
ContentPlaceHolderID="ContentPlaceHolder1"
Runat="Server">
<h2>This is my content.</h2>
</asp:Content>
程序清单1-5中的两个有趣的地方是MasterPageFile特性和Content元素。Page指令包含一个新特性MasterPageFile,它指定要使用的Master Page的文件名。
在程序清单1-5中,显然内容页面没有像一般Web窗体那样的HTML标记。相反,用ContentPlaceHolderID特性指定contentplaceholder的id(在Master Page中),就可以添加一个Content元素,指定在Master Page的什么地方放置内容标记。只有Content元素中的标记才显示在Master Page中。
由于没有一般的HTML标记,例如Head,所以要用Head标题元素中的内容设置Page指令的Title特性。
上面演示了在ASP.NET v2.0中使用Master Page的简单和优雅之处。Master Page在VS 2005中也得到了支持,所以它是比ASP.NET v1.1窍门程序更容易的解决方案。
1.3 URL重写功能
URL重写功能就是接受带有有效命名约定的URL,把它们转化为查询字符串。需要有效命名约定的两个原因是:将信息组织到逻辑层次结构中,以及隐藏查询字符串参数。本节将说明URL重写功能如何改进用户界面,描述实现URL重写功能的新旧方式,并给出一些代码来演示这个概念。
注意:
本节还添加了一些代码,来演示n层体系结构和数据绑定过程中的最佳实践方式,而不是使用较简单的数据源控件。
1.3.1 为什么要重写URL
看看博客是如何按时间组织的,就可以明白分层组织的含义。从用户的角度来看,下面的查询字符串是很难理解的:
http://www.someblogsite.com/username/?y=2005&m=01&d=31
上面的查询字符串返回2005年1月1日的博客项,这并不容易确定。我们可以修改代码,使其参数更有意义,如下:
http://www.someblogsite.com/username/?year=2005&month=01&day=31
无论查询字符串的参数如何表示,一般用户都很难理解它。最好用富有层次感的表示方式编写查询字符串,如下:
http://www.someblogsite.com/username/2005/January/31
对于站点的一般访问者,上面的URL不是很难理解。例如,如果用户删除了日期,就会得到1月的所有记录。
即使没有自然的层次结构或干脆没有参数,只要URL有一个有意义的名称,而不是使用查询参数,用户还是较容易理解它们的。
1.3.2 ASP.NET v1.1的窍门程序
在ASP.NET v1.x中执行URL重写功能的一个很好的资源是Scott Mitchell撰写的MSDN文章“URL Rewriting in ASP.NET”,它位于msdn.microsoft.com/library/default.asp?url= /library/en-us/dnaspp/html/urlrewriting.asp。在这篇文章中,Scott解释了如何通过HTTP模块和HTTP处理程序执行URL重写功能,并说明了它们的使用场合。他还建立了一个可重用的URL重写引擎,通过配置文件使用正则表达式。
1.3.3 ASP.NET v2.0 的替代品
在ASP.NET v2.0中,是通过urlMappings配置元素支持重写URL功能的。给web.config添加一个新项,来映射URL,如下所示:
<urlMappings enabled="true">
<add url="~/Articles/AspDotNet/UrlRewriting"
mappedUrl="~/Articles.aspx?cat=1&id=16" />
</urlMappings>
用户看到的是url特性,而mappedUrl特性描述了实际请求的页面。在上面的urlMappings元素中,假定有一个文章页面根据类别和文章标识符动态返回文章。于是,url特性会显示首选的用户界面,但mappedUrl特性会显示实际的页面和请求的参数。
以前笔者编写过一个示例应用程序,来说明如何使用这个功能。这是文章的一个变体,介绍了上述概念,但它是根据年份和月份来解释的。例如,文章应用程序允许选择年份,再选择月份。每个页面上都显示了可读的URL,我们可以利用该URL,简单地修改页面地址,来导航应用程序。程序清单1-6显示了文章应用程序的初始页面。
程序清单1-6 使用可读的URL标识年份的主页: Default.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs"
Inherits="_Default" %>
<!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>.NET Article Archive</title>
</head>
<body>
<form id="form1" runat="server">
<h1>.NET Article Archive</h1>
<p>
Pick a Year:
</p>
<p>
<asp:HyperLink ID="HyperLink1" runat="server"
NavigateUrl="~/2006">2006</asp:HyperLink><br />
<asp:HyperLink ID="HyperLink2" runat="server"
NavigateUrl="~/2005">2005</asp:HyperLink>
</p>
</form>
</body>
</html>
HyperLink元素的NavigateUrl特性包含可读的URL,该URL要重写为查询字符串参数。同样,在用户选择一个年份后,就会看到如程序清单1-7所述的年份页面。
程序清单1-7 带有可读URL的年份页面: YearView.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="YearView.aspx.cs"
Inherits="YearView" %>
<!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>Articles for the Year</title>
</head>
<body>
<form id="form1" runat="server">
<h1>
<asp:Label ID="lblTitle"
runat="server"
Text="Articles for the Year #">
</asp:Label>
</h1>
<p>
<asp:Panel ID="pnlMonths" runat="server" Height="50px"
Width="125px">
<asp:HyperLink ID="hypJanuary" runat="server"
NavigateUrl="~/YEAR/01">January</asp:HyperLink><br />
<asp:HyperLink ID="hypFebruary" runat="server"
NavigateUrl="~/YEAR/02">February</asp:HyperLink><br />
<asp:HyperLink ID="hypMarch" runat="server"
NavigateUrl="~/YEAR/03">March</asp:HyperLink><br />
<asp:HyperLink ID="hypApril" runat="server"
NavigateUrl="~/YEAR/04">April</asp:HyperLink><br />
<asp:HyperLink ID="hypMay" runat="server"
NavigateUrl="~/YEAR/05">May</asp:HyperLink><br />
<asp:HyperLink ID="hypJune" runat="server"
NavigateUrl="~/YEAR/06">June</asp:HyperLink><br />
<asp:HyperLink ID="hypJuly" runat="server"
NavigateUrl="~/YEAR/07">July</asp:HyperLink><br />
<asp:HyperLink ID="hypAugust" runat="server"
NavigateUrl="~/YEAR/08">August</asp:HyperLink><br />
<asp:HyperLink ID="hypSeptember" runat="server"
NavigateUrl="~/YEAR/09">September</asp:HyperLink><br />
<asp:HyperLink ID="hypOctober" runat="server"
NavigateUrl="~/YEAR/10">October</asp:HyperLink><br />
<asp:HyperLink ID="hypNovember" runat="server"
NavigateUrl="~/YEAR/11">November</asp:HyperLink><br />
<asp:HyperLink ID="hypDecember" runat="server"
NavigateUrl="~/YEAR/12">December</asp:HyperLink>
</asp:Panel>
</p>
</form>
</body>
</html>
程序清单1-7显示了HyperLink元素,其NavigateUrl特性设置为可读的URL。它为一年中的每个月包含一层,其中数字对应月份的顺序。为了正确处理这些URL,应编辑Web窗体YearView的代码文件,如程序清单1-8所示。
程序清单1-8 通过参数化的URL读取页面的查询字符串参数: YearView.aspx.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 YearView : System.Web.UI.Page
{
string year; // passed in query string
protected void Page_Load(object sender, EventArgs e)
{
// we'll use this in multiple methods
year = Request.QueryString["year"];
// set page title
SetTitle();
// configure months to refer to proper page
SetMonths();
}
/// <summary>
/// configure months to refer to proper page
/// </summary>
private void SetMonths()
{
foreach (Control ctrl in pnlMonths.Controls)
{
HyperLink monthLink = ctrl as HyperLink;
if (monthLink != null)
{
monthLink.NavigateUrl =
monthLink.NavigateUrl.Replace("YEAR", year);
}
}
}
/// <summary>
/// set page title
/// </summary>
private void SetTitle()
{
lblTitle.Text = "Articles for the Year " + year;
}
}
程序清单1-8中的Page_Load方法从查询字符串中提取year参数,使之可用于后续的方法。SetTitle方法使用这个值重写有正确年份的页面标题。
另外还要注意程序清单1-8中HyperLink控件的NavigateUrl属性,它们在URL的年份位置上都包含“YEAR”文本。这会使代码更富有动感,因为根据年份,这些NavigateUrl属性必须重写。这就是程序清单1-8中SetMonths方法的作用。程序清单1-8中的代码故意把每个HyperLink控件放在一个面板中,以便在代码中使用其Controls集合。因此,在代码文件中,SetMonths方法可以迭代Controls集合,用一个简单的string.Replace方法调用来设置年份。为了使这个内置的URL重写功能可用于这个应用程序,web.config文件中有一个urlMapping元素,如程序清单1-9所示。
程序清单1-9 web.config文件中的urlMapping元素可以在ASP.NET v2.0中重写URL
<?xml version="1.0"?>
<configuration>
<system.web>
<urlMappings>
<add url="~/2006"
mappedUrl="~/YearView.aspx?year=2006"/>
<add url="~/2006/01"
mappedUrl="~/MonthView.aspx?year=2006&month=01"/>
<add url="~/2006/02"
mappedUrl="~/MonthView.aspx?year=2006&month=02"/>
<add url="~/2005"
mappedUrl="~/YearView.aspx?year=2005"/>
<add url="~/2005/01"
mappedUrl="~/MonthView.aspx?year=2005&month=01"/>
<add url="~/2005/02"
mappedUrl="~/MonthView.aspx?year=2005&month=02"/>
</urlMappings>
<compilation debug="true"/>
</system.web>
</configuration>
在程序清单1-9的每个add元素中,可重写的URL转化为mappedUrl,mappedUrl是实际的地址,也是发送给页面的查询字符串。发音符号(~)表示每个页面相关的应用程序目录。必须使用&替代&符号,将参数分隔开。这里省略了月份,以缩短程序清单。
要查看文章列表,用户选择了他感兴趣的月份。程序清单1-10演示了MonthView.aspx的执行过程。在程序清单1-10中使用了GridView控件,是因为这里需要为多个数据行格式化输出,并把它绑定到ObjectDataSource控件上。GridView控件是ASP.NET v2.0中的新增控件,它替代了DataGrid控件。
程序清单1-10 使用GridView读取查询参数:MonthView.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="MonthView.aspx.cs"
Inherits="MonthView" %>
<!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>Articles for the Month</title>
</head>
<body>
<form id="form1" runat="server">
<h1>
Requested Articles:</h1>
<br />
<asp:GridView ID="GridView1" runat="server"
AutoGenerateColumns="False" DataSourceID="ArticlesODS">
<Columns>
<asp:BoundField DataField="Year"
HeaderText="Year" SortExpression="Year" />
<asp:BoundField DataField="Month"
HeaderText="Month" SortExpression="Month" />
<asp:BoundField DataField="Title"
HeaderText="Title" SortExpression="Title" />
<asp:BoundField DataField="Content"
HeaderText="Content" SortExpression="Content" />
</Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ArticlesODS" runat="server"
SelectMethod="GetArticles" TypeName="Articles">
<SelectParameters>
<asp:QueryStringParameter DefaultValue="2006"
Name="year" QueryStringField="year"
Type="String" />
<asp:QueryStringParameter DefaultValue="01"
Name="month" QueryStringField="month"
Type="String" />
</SelectParameters>
</asp:ObjectDataSource>
</form>
</body>
</html>
为了提倡设计良好的n层体系结构,这里使用业务对象和正式的数据访问层来实现文章的管理。因此在MonthView.aspx页面中使用了ObjectDataSource控件。很容易把查询字符串参数直接映射到Articles类的GetArticles方法上,如程序清单1-11所示。
程序清单1-11 Articles类包含一组article对象: articles.cs
using System;
using System.Collections.Generic;
/// <summary>
/// List of Articles
/// </summary>
public class Articles : List<Article>
{
public List<Article> GetArticles(string year, string month)
{
ArticleData dal = new ArticleData();
dal.GetArticles(this, year, month);
return this;
}
}
程序清单1-11中的Articles类利用C#编程语言中新增的泛型特性,创建了一个强类型化的Article对象集合。通过继承List<Article>,可以获得泛型的优势,给类型指定更容易理解的名称。程序清单1-12列出了用ArticleData类表示的数据访问层。
程序清单1-12 ArticleData类用数据源中的新文章填充当前的列表:ArticleData.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Web;
/// <summary>
/// Summary description for ArticleData
/// </summary>
public class ArticleData
{
public void GetArticles(List<Article> articles, string year, string
month)
{
DataSet dsArticles = new DataSet();
dsArticles.ReadXml(HttpContext.Current.Server.MapPath("Articles
.xml"));
DataView dvArticles = new DataView(dsArticles.Tables["article"]);
dvArticles.RowFilter =
"year = '" + year + "' " +
"and month = '" + month + "'";
Article currArticle = null;
IEnumerator articleRows = dvArticles.GetEnumerator();
while (articleRows.MoveNext())
{
DataRowView articleRow = (DataRowView)articleRows.Current;
currArticle = new Article(
(string)articleRow["year"],
(string)articleRow["month"],
(string)articleRow["title"],
(string)articleRow["content"]);
articles.Add(currArticle);
}
}
}
这个类把一个XML文件(参见程序清单1-13)加载到DataSet中。它根据所传送的年份和月份参数,使用DataView过滤结果。Article类是一个业务对象,所以最终在UI层中绑定到GridView上。当然,UI层不需要知道数据是来自于XML文件或通过ADO.NET组件进行了处理。它很容易在以后进行修改,以满足新的要求。另外,GridView可以在Generic集合中显示业务对象,因此,我们通过articles参数把数据作为List<Article>传送回。
程序清单1-13 文章数据通过XML文件来表示: Articles.xml
<?xml version="1.0" encoding="utf-8" ?>
<articles>
<article>
<year>2005</year>
<month>01</month>
<title>Title1</title>
<content>This is the text of Title1.</content>
</article>
<article>
<year>2005</year>
<month>02</month>
<title>Title2</title>
<content>This is the text of Title2.</content>
</article>
<article>
<year>2005</year>
<month>02</month>
<title>Title3</title>
<content>This is the text of Title3.</content>
</article>
<article>
<year>2006</year>
<month>01</month>
<title>Title4</title>
<content>This is the text of Title4.</content>
</article>
<article>
<year>2006</year>
<month>01</month>
<title>Title5</title>
<content>This is the text of Title5.</content>
</article>
<article>
<year>2006</year>
<month>02</month>
<title>Title6</title>
<content>This is the text of Title6.</content>
</article>
</articles>
程序清单1-12中的代码把一个XML文件(参见程序清单1-13)用作数据源,完成了一个非常简单的实践。ArticlesData类使用该数据填充Article对象,如程序清单1-14所示。
程序清单1-14 Article类是一个业务对象,它要绑定到UI层的GridView上: Article.cs
using System;
using System.Data;
using System.Configuration;
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;
/// <summary>
/// Represents an Article
/// </summary>
public class Article
{
private string m_year;
public string Year
{
get { return m_year; }
set { m_year = value; }
}
private string m_month;
public string Month
{
get { return m_month; }
set { m_month = value; }
}
private string m_title;
public string Title
{
get { return m_title; }
set { m_title = value; }
}
private string m_content;
public string Content
{
get { return m_content; }
set { m_content = value; }
}
public Article(string year, string month, string title, string content)
{
Year = year;
Month = month;
Title = title;
Content = content;
}
}
Articles类中的数据通过属性表示为其公共接口,具有封装特性。这将允许在需要时改变底层实现方式,包括添加业务规则和验证逻辑。
注意:
如果使用数据源控件来替代上述代码中的ObjectDataSource控件,就不能添加业务规则了。不使用数据源控件是为了简化这个例子中的代码,它还有另一个好处。正确使用Object DataSource控件,可以设计应用程序,使其具备灵活性和可维护性。
使用ASP.NET v2.0的URL映射特性,似乎有很大的优势,但它有一个缺陷:不能使用正则表达式。本节中的文章示例使用正则表达式,将得到更好的效果。注意,urlMappings元素包含的代码非常类似,只能通过年份或月份来区分。通过一个正则表达式,用参数映射年份或月份,可以更好地实现这个功能。如果使用Scott Mitchell的URL重写引擎,配置将如下所示:
<RewriterConfig>
<Rules>
<!-- Rules for Blog Content Displayer -->
<RewriterRule>
<LookFor>~/(\d{4})/(\d{2})/Default\.aspx</LookFor>
<SendTo><![CDATA[~/YearView.aspx?year=$1&month=$2]]></SendTo>
</RewriterRule>
<RewriterRule>
<LookFor>~/(\d{4})/Default\.aspx</LookFor>
<SendTo>~/YearView.aspx?year=$1</SendTo>
</RewriterRule>
</Rules>
</RewriterConfig>
这是一个简单的一次性的配置。但在Visual Studio 2005实现方式(参见程序清单1-9中配置文件的urlMapping元素)中,必须为每个年份和每个月份更新该配置文件。这是不切实际的,因为这个文件会变大,且需要手动干预。对于有正则表达式的URL重写功能,建议使用Scott Mitchell文章中提及的窍门程序。ASP.NET v2.0的URL映射特性只能在最简单的情况下使用。
1.4 小结
本章介绍了ASP.NET v1.1的先驱发明的几个窍门程序。在Microsoft把这些窍门程序转变为ASP.NET v2.0的主要产品特性时,这些人有很大的影响。Wizard窍门程序变成Wizard控件。模板、继承和用户控件Master Page窍门程序变成ASP.NET v2.0中的Master Page。URL重写窍门程序影响了ASP.NET v2.0中的URL映射,在需要正则表达式时,URL重写窍门程序一直以来都是实现URL重写功能的首选方法。