概述
WebForms 4.0 提供了一些有针对性的增强,还包括一些新特性。本实验将讨论以下特性:
• 客户端 ID:开发人员现在可以管理会对所呈现客户端 ID 造成影响的控件 ID。Control 类现在提供了一个新的 ClientIDMode 属性,可用于在确定是否需要在呈现时重构客户端 ID 时,指定运行时的行为。这将删除客户端 ID 中以前的无用信息。
• URL 路由:WebForms 4.0 引入了一个新的 PageRouteHandler 类,它将 URL 路由集成到了 Web Form Pages 中。ASP.NET 中的 URL 路由允许您在网站中使用不需要映射到具体文件的 URL。由于 URL 不需要映射到文件,因此您可以在 Web 应用中使用描述用户操作的 URL,使其能更加轻松地被用户理解。在 URL 路由中,您将定义一些包含值的占位符的 URL 模式,这些占位符将在处理 URL 请求时发挥作用。在运行时,URL 中应用程序名称后面的部分将根据您所定义的 URL 模式解析为离散的值。
• View State: WebForms 4.0 为 View State 提供了更加粒度化的控制。开发人员现在可以禁用页面上的 View State,并在特定的服务器控件上启用它,还可以在某控件上禁用它,而在其子控件上启用它。
目标
在本次动手实验中,您将学习如何:
• 控制服务器控件 ClientId
• 实现双向路由支持
• 在应用程序和页面级控制 View State
系统要求
您必须拥有以下工具才能完成本实验:
• Microsoft Visual Studio 2010 Beta 2
• .Net Framework 4
• Microsoft SQL Server 2008(速成版或更高版本)
安装
使用 Configuration Wizard 验证本实验的所有先决条件。要确保正确配置所有内容,请按照以下步骤进行:
注意:要执行安装步骤,您需要使用管理员权限在命令行窗口中运行脚本。
1. 如果之前没有执行,运行 Training Kit 的 Configuration Wizard。为此,运行本实验的 Setup 文件夹下的 CheckDependencies.cmd 脚本。安装先决条件中没有安装的软件(如有必要请重新扫描),并完成向导。
注意:为了方便,本实验中管理的许多代码都可用于 Visual Studio 代码片段。CheckDependencies.cmd 文件启动 Visual Studio 安装程序文件安装该代码片段。
2. 本实验依赖于 Assets 文件夹中的 AdventureWorksLT.mdf 数据库。您需要将此数据库文件复制到各练习的 App_Data 文件夹中(除非重用整个实验的 Web 应用程序项目)。
练习
本次动手实验由以下练习组成:
1. 控制服务器控件 ClientId
2. 实现双向路由支持
3. 在应用程序和页面级控制 View State
完成本实验的估计时间:90 分钟。
注意:各练习都随带了初始解决方案(作为开始)。这些解决方案中有些代码片段是空缺的,我们将通过每个练习填写完整。因此,如果直接运行,初始解决方案将无法运行。
在每个练习中,您都可以找到 End 文件夹,其中包括完成练习后应该得到的解决方案。如果需要其他帮助来完成练习,您可以使用该解决方案作为指南。
下一步
练习 1:控制服务器控件 ClientID
练习 1:控制服务器控件 ClientID
在本练习中,您将学习如何通过框架控制由 ASP.NET 服务器控件生成的客户端 ID。过去,框架会修改客户端 ID,让它能唯一标识各控件。这有时会让您使用标记定义的 ID,或者出现类似于下面的情况: "ctl00_MasterPageBody_ctl01_Textbox1"。
修改客户端 ID 属性可以确保各元素都被唯一标识。但是,对于需要执行客户端脚本任务的开发人员来说,这会带来非常大的麻烦。如果您使用过 ASP.NET,那么肯定遇到过这种问题。问题在于,您在运行时之前不知道客户端 ID 究竟是什么,因此难以执行任何客户端脚本任务。此外,修改页面、添加删除控件都会导致生成不同的客户端 ID。
如果您使用过 ASP.NET,则应该知道解决此方法的技巧。每个控件都有一个只读的 ClientID 属性,用于提供唯一的客户端 ID。您可以在动态添加脚本时使用它,或者更加常见的用法是,使用内联代码(早期的 ASP 样式)将该值提供给客户端脚本。
JavaScript
<script type="text/javascript">
function DoSomething(){
alert('<%= Control.ClientID %>');
}
</script>
为了解决这种问题,ASP.NET 4.0 提供了四种 ClientID“模式”,可满足用户从已有行为到全面控制的一切需求。需依照 ClientIDMode 模式来修改控件 ID 属性,然后使用它作为客户端 ID。
这四种模式分别是:
• 旧式:如果控件层次结构未设定 ClientIDMode,则默认使用此模式。这会造成客户端 ID 表现 2.0 版本框架中的行为(3.0 和 3.5 未修改此代码路径)。这种模式将生成一个类似于 "ctl00_MasterPageBody_ctl01_Textbox1" 的 ID。
• 继承:这是所有控件的默认行为。这需要获取父控件中的 ClientIDMode 值。您不需要为每个控件都设置此模式,因为它是默认的。仅当 ClientIDMode 被修改并且新的期望行为是继承父控件时,需要使用它。
• 静态:此模式的行为与其名称所表达的含义完全一样;它将客户端 ID 设置为静态。这表示您对此 ID 的设置将反映到客户端 ID 上。请注意,这意味着如果在重复控件中使用了静态 ClientIDMode,则开发人员需要负责确保客户端 ID 的唯一性。
• 可预测:在需要以可预测的方式确保框架的唯一性时将使用此模式。这种模式最常应用于数据绑定控件。框架会遍历控件层次结构,为提供的 ID 添加其父控件 ID 作为前缀,直到它到达层次结构中 ClientIDMode 为静态的控件。当控件位于数据绑定控件中时,会在所提供的 ID 中添加一个标识该实例的值作为后缀。ClientIDRowSuffix 属性用于控制将用作后缀的值。此模式将生成一个类似于 "Gridview1_Label1_0" 的 ID。
有关更多信息,请访问 http://weblogs.asp.net/asptest/archive/2009/01/06/asp-net-4-0-clientid-overview.aspx。
注意:要验证每个步骤是否正确执行,建议在每次任务结束时构建解决方案。
任务 1 –为 ASP.NET 控件分配静态 ClientID
在本任务中,您将为 Web 应用程序中的一些 ASP.NET 控件启用 Static ClientID 模式。通过此操作,您将能够从客户端代码以及在后续步骤中的 CSS 中无缝引用它们。
1. 以管理员身份打开 Microsoft Visual Studio 2010。右键单击 Start | All Programs | Microsoft Visual Studio 2010 | Microsoft Visual Studio 2010 并选择 Run as Administrator。
2. 打开 %TrainingKitInstallationFolder%\Labs\AspNetWebForms4\Source\Ex01-ClientId\begin\ 下的解决方案文件 WebFormsSampleApp.sln。
注意:实验场景包括一个页面,其中列出 AdventureWorksLT 数据库中按类别筛选的产品,允许用户将它们添加到购物车,并随后通过单击 Check Out 提交它们。必须将 AdventureWorksLT.mdf 文件从 \AspNetWebForms4\Source\Assets 文件夹复制到此项目的 App_Data 文件夹。
![](http://i.msdn.microsoft.com/ee851721.image002(zh-cn).jpg)
![](http://i.msdn.microsoft.com/ee851721.image003(zh-cn).png)
图 1
在 Solution Explorer 中浏览 Web 应用程序
3. 在 Source 模式下打开 ShoppingCart 用户控件。为此,在 Solution Explorer 中,右键单击 UserControls 下方的 ShoppingCart.ascx 文件,然后选择 View Markup。
注意:这是供用户下订单的购物车。这个用户控件在呈现时将能够通过客户端脚本利用 ClientID 属性(Static 模式)来进行展开和折叠。
4. 在 ShopCartCollapsed ASP.NET 面板中启用 ClientID Static 模式。为此,将当前 ShopCartCollapsed asp:Panel 定义替换为以下突出显示的代码。
注意:此面板将作为使用与服务器控件相同的 Id(在本例中为 ShopCartCollapsed)的 div 呈现给客户端。
ASP.NET
<asp:Panel ID="ShopCartCollapsed" ClientIDMode="Static" runat="server">
5. 对 ShopCartExpanded ASP.NET 面板重复上述步骤。
ASP.NET
<asp:Panel ID="ShopCartExpanded" ClientIDMode="Static" runat="server">
任务 2 –为 ASP.NET 控件分配可预测 ClientID
在本任务中,您将为从数据库中检索的产品列表项分配 Predictable ClientID 模式,将产品 ID 设置为 ClientIDRowSuffix。
注意:以前,ASP.NET 会生成唯一的 ID 来防止 ID 冲突,而最可能发生这类冲突的地方在数据绑定控件内部。可预测模式用于在使用数据绑定控件时解决此问题。
可预测模式输出遵循 [Prefix]_[ID]_[Suffix] 模式,其中各参数的含义如下:
- Prefix:包含明确的 ID/ClientID 的所有父控件的列表,使用下划线分隔
- ID:重复的项服务器控件 Id
- Suffix:可选自动增加数值,用于重复项目(仅在使用 IDataKeysControl 时适用。)通过设置数据绑定服务器控件的 ClientIDRowSuffix 属性来分配此参数(不在重复项中)。如果未设置此属性或此属性不可用,则使用行索引代替它。
只有实现了新 IDataKeysControl 接口的控件才支持设置 ClientIDRowSuffix 属性(目前由 GridView 和 ListView实现)。此界面允许设置子元素的 ClientIDRowSuffix,它的值将由各行的数据键决定。
1. 为 ListView (显示购物车中的项目)指定 ClientIDRowSuffix 属性。为此,在 Source 模式下打开 ShoppingCart.ascx,找到 ShoppingCartItemsLists ListView 并将当前控件定义替换为以下突出显示的代码。
注意:ProductId 是类的一个属性,它的项将是重复的 (ShoppingCartItem),并将在绑定数据源时自动插入到数据键集合中。
ASP.NET
<asp:ListView ID="ShoppingCartItemsLists" runat="server" ClientIDMode="Static" ClientIDRowSuffix="ProductId">
注意:可以采用三种方式来使用可预测模式,各模式都可以通过 ClientIDRowSuffix 属性来定义,该属性用于指定各实例的后缀。
1- 未定义 ClientIDRowSuffix。这也是没有数据键集合的数据绑定控件的行为(比如 Repeater 控件)。为了构造 ClientId,ASP.NET 会在 ID 后缀中添加行索引。
2- 定义了 ClientIDRowSuffix。它将在数据绑定服务器控件的数据键集合中查找该值,然后将该值添加到 ID 的后缀中。
3- 定义了 ClientIDRowSuffix,但使用了一个复合值,而不是一个值。它的行为与一个值相同,但会将合并后的值添加到 ID 的后缀中。(比如 ClientIDRowSuffix="ID, Name")。
2. 将购物车项绑定到 ShoppingCartItemLists 控件。为此,打开 ShoppingCart.ascx.cs 代码隐藏文件,并将以下突出显示的代码添加到 ShoppingCartControl 类中的 Page_PreRender方法底部。
注意:如果浏览 ShoppingCartItem 类,您会看到用于设置 ListView 的 ClientIDRowSuffix 属性的 ProductId 属性。
(代码片段– Web Forms 4.0 实验– Page_PreRender 方法)
C#
protected void Page_PreRender(object sender, EventArgs e)
{
ShoppingCart cart = ShoppingCartFactory.GetInstance();
ExpandedItemsCountLabel.Text = cart.TotalItems.ToString();
CollapsedItemsCountLabel.Text = cart.TotalItems.ToString();
ExpandedTotalLabel.Text = cart.Subtotal.ToString("c");
CollapsedTotalLabel.Text = cart.Subtotal.ToString("c");
this.ShopCartExpandedEmpty.Visible = cart.TotalItems == 0;
this.ShopCartExpandedNonEmpty.Visible = cart.TotalItems != 0;
ShoppingCartItemsLists.DataSource = cart.Items;
ShoppingCartItemsLists.DataBind();
}
3. 为购物车 ListView 的子元素启用 Predictable ClientId 模式。为此,将当前包含在 ShoppingCartItemLists ListView 中的 Quantity 和 TotalPrice asp:Labels 定义替换为以下突出显示的代码。
注意:这些标签将作为 div 显示给客户端,各个 div 与购物车中的项目一一对应。例如,Quantity 标签的 ClientId 将类似于“ctrl0_Quantity_12”,其中 12 是 ProductId 而 ctrl0 是父控件 id。
ASP.NET
<asp:ListView ID="ShoppingCartItemsLists" runat="server" ClientIDMode="Static" ClientIDRowSuffix="ProductId">
<ItemTemplate>
<asp:Panel ID="ShoppingCartItem" ClientIDMode="Static" runat="server">
<div class="productColumn">
<asp:Label ID="Quantity" ClientIDMode="Predictable" runat="server">
<%#Eval("ProductName")%> (<%#Eval("Quantity")%>)</asp:Label>
</div>
<div class="priceColumn">
<asp:Label ID="TotalPrice" ClientIDMode="Predictable" runat="server">
<%# string.Format(System.Globalization.CultureInfo.CurrentUICulture, "{0:c}", Eval("TotalPrice"))%></asp:Label>
</div>
</asp:Panel>
</ItemTemplate>
</asp:ListView>
任务 3 –为 ASP.NET 控件分配继承 ClientID
在本任务中,您将为包含在购物车中的 Panel 服务器控件分配 Inherit ClientID 模式。这将允许控件从实现了 INamingContainer 接口的第一个父服务器控件(在本例中为 ShoppingCart 用户控件)继承 ClientIdMode。
注意:INamingContainer 接口标识一个容器控件,该控件在 Page 对象的控件层次结构中创建一个新 ID 命名空间。任何实现了此接口的控件都将创建一个新的命名空间,其中所有子控件 ID 属性在整个应用程序中都必须是唯一的。这仅仅是一个标记接口。
有关更多信息,请参见 INamingContainer 接口。
1. 定义父 ShoppingCart 用户控件的 ClientIDMode,它将由子 Panel 服务器控件继承。为此,在 Source 模式下打开 UI.Master 页面,并将当前的 ShoppingCart1 用户控件定义替换为以下突出显示的代码。
ASP.NET
...
<uc1:ShoppingCart ID="ShoppingCart1" runat="server" ClientIDMode="Static" />
...
注意:如果未设置 ClientIDMode,那么它的所有子控件都继承自默认的 ClientIDMode,即 Legacy。
2. 在子 Panel 服务器控件中启用 Inherit ClientId 模式。为此,在 Source 模式下打开 ShoppingCart.ascx,并将当前的 ShopCartExpandedNonEmpty asp:Panel 定义替换为以下突出显示的代码。
ASP.NET
...
<asp:Panel ID="ShopCartExpandedNonEmpty" ClientIDMode="Inherit" runat="server">
<p class="items">
<span>Your cart items:</span>
</p>
...
注意:还可以采用另外两种方法来设置 ClientIdMode:
- At Page level:
为当前页面中的所有控件定义默认 ClientIdMode。例如:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" ClientIdMode="Static"%>
- At Web Config level:
还可以在机器或应用程序级别在 config 部分设置 ClientIdMode。例如,这将为应用程序中所有页面中的所有控件定义默认 ClientIdMode:
<system.web>
<pages clientIdMode="Predictable"></pages>
</system.web>
任务 4 - 通过 CSS 和 Java 脚本定位静态 ClientID
在本任务中,您将利用静态 ClientID 模式,通过 Java Script 使用 ClientID 来操作所呈现的控件,并将 CSS 样式直接应用于 Id,而不是创建不必要的 CssClass。
1. 通过静态 ClientID 向控件应用样式。为此,打开 Styles 文件夹下的 main.css 文件,并添加以下突出显示的代码:注意:ShopCartCollapsed 和 ShopCartExpanded ID 属性将通过应用 ID 选择器而不是类选择器来引用。基本差异在于,类选择器适用于页面上的一个或多个元素,而 ID 选择器仅处理一个元素。有关更多信息,请访问 http://www.w3.org/TR/CSS21/selector.html#id-selectors。
CSS
#ShopCartCollapsed {
display:none;
background-color:#00FF80;
padding:10px 15px;
height:10px;
background:url(../Images/shopcart_collapsed_bg.png) no-repeat top left;
}
#ShopCartCollapsed p.summary span {
position:relative; top:-4px;
}
#ShopCartCollapsed a.checkoutLink {
position:absolute; top:6px; right:8px;
}
#ShopCartExpanded {
display:none;
padding:10px 15px;
height:90px;
background:url(../Images/shopcart_bg.gif) no-repeat top left;
}
#ShopCartExpanded p.items {
font-size:11px;
color: #666;
}
#ShopCartExpanded ul.items {
height:60px;overflow:auto;
margin:0; padding:0; list-style:none;
}
#ShopCartExpanded ul.items li {
display:inline; margin:0; padding: 0;
}
#ShopCartExpanded div.ShoppingCartItem {
display:block;
}
#ShopCartExpanded div.productColumn {
float:left;
150px;
}
#ShopCartExpanded div.priceColumn {
float:right;
auto;
}
#ShopCartExpanded p.summary span {
position:relative; top:+6px;
}
#ShopCartExpanded p.empty {
text-align:center;
font-weight:bold; color:#50d48f;
padding-top:15px;
}
#ShopCartExpanded a.checkoutLink {
position:absolute; top:89px; right:8px;
}
2. 使用 JQuery 实现必要的 Java Script 代码,为购物车创建效果。为此,打开 Scripts 文件夹下的 shoppingCart.Effects.js 文件,并执行以下步骤:
a. 将以下突出显示的代码添加到 CollapseCart 函数中:
JS
function CollapseCart(withAnimation) {
if (withAnimation) {
$("#ShopCartExpanded").hide();
$("#ShopCartCollapsed").show("slow");
}
else {
$("#ShopCartExpanded").css("display", "none");
$("#ShopCartCollapsed").css("display", "block");
}
$("#ShoppingCartState").val("collapsed");
}
b. 将以下突出显示的代码添加到 ExpandCart 函数中:
JS
function ExpandCart(withAnimation) {
if (withAnimation) {
$("#ShopCartCollapsed").hide();
$("#ShopCartExpanded").show("slow");
}
else {
$("#ShopCartCollapsed").css("display", "none");
$("#ShopCartExpanded").css("display", "block");
}
$("#ShoppingCartState").val("expanded");
}
c. 将以下突出显示的代码添加到 $(document).ready 函数中:
JS
$(document).ready(function() {
// Preload expanded Shopping Cart background image
$("<img>").attr("src", "Images/shopcart_bg.gif");
$("#ShopCartCollapsed").click(function() { ExpandCart(true) });
$("#ShopCartExpanded").click(function() { CollapseCart(true) });
if ($("#ShoppingCartState").val() == "expanded") {
ExpandCart(false);
}
else {
CollapseCart(false);
}
});
3. 在 Default.aspx 中添加一个对 ShoppingCart.Effects.js 的引用。为此,在 Source 模式下打开 Default.aspx,将以下突出显示的代码添加到第一个 asp:Content 标记
内。
ASP.NET
...
<%@ MasterType TypeName="WebFormsSampleApp.Master.UI" %>
<asp:Content ContentPlaceHolderID="HeadContentPlaceHolder" runat="server">
<link type="text/css" rel="Stylesheet" media="screen" href="/Styles/products.css" />
<script type="text/javascript" src="/Scripts/shoppingCart.Effects.js"></script>
</asp:Content>
...
下一步
练习 1:验证
练习 1:验证
为了验证是否正确执行了练习 1 中的所有步骤,执行以下步骤:
验证 1
在此验证中,您将了解如何从 CSS 和 Java Script 中定位 Static ClientID。您将看到如何将样式应用于购物车,如何通过美仑美焕的效果展开和折叠它。所有这些都需要通过在客户端代码中引用所呈现的控件静态 id 来实现。
1. 启动 WebFormsSampleApp 项目的一个新实例。为此,在 Solution Explorer 中右键单击 WebSite 项目,指向 Debug 并选择 Start New Instance。
注意:如果出现 Debugging Not Enabled 对话框,选择 Modify the Web.config file to enable debugging 并单击 OK。
![](http://i.msdn.microsoft.com/ee851721.image005(zh-cn).jpg)
图 2
查看默认页面
2. 查看页面的源代码,确定静态 ClientID 模式(已在练习 1 中完成)的 asp:Panels 使用与服务器控件相同的 ID 呈现。为此,在浏览器中右键单击页面,选择 View Source。
注意:此时将打开一个文字编辑器,其中显示了呈现的 Default.aspx 页面的源代码。
3. 在页面的源代码中找到 Id 分别为 Id ShopCartCollapsed 和 ShopCartExpanded 的 <div> 元素。这两个 <div> 元素代表购物车的展开或折叠视图。
注意:ASP.NET 已将 asp:Panel 服务器控件呈现给 <div> 元素,保留了唯一标识它们的 ID。
![](http://i.msdn.microsoft.com/ee851721.image007(zh-cn).jpg)
图 3
查看所呈现的使用静态 ClientID 的 <div> 元素
4. 单击产品旁边的加号 ( ) 将它们添加到购物车中。请注意购物车的折叠视图如何更新新产品的信息。
![](http://i.msdn.microsoft.com/ee851721.image009(zh-cn).jpg)
图 4
向购物车添加产品
5. 展开和折叠购物车。为此,单击页面右上方的绿色面板。
注意:由于您已经知道了调用这些 <div> 元素的方式,因此可以实现购物车的展开和折叠。在本例中,除了直接使用 ID 选择器应用 CSS 样式之外,您还从 Java Script 中引用 <div> 元素以动画方式展开和折叠它们。
![](http://i.msdn.microsoft.com/ee851721.image011(zh-cn).jpg)
图 5
展开和折叠购物车
验证 2
在此验证中,通过检查客户端的源代码,您将看到 Predictable 和 Inherit ClientID 是如何呈现的。
1. 确保购物车中包含一些产品。
2. 检查默认页面的源代码,了解 Predictable ClientID 是如何呈现的。为此,执行以下步骤:
a. 在页面的源代码中找到一个使用 Id ShopCartExpandedNonEmpty 的 <div> 元素。
注意:此元素表示包含 ListView 的 asp:Panel,ListView 的它的 ClientIDSuffix(设置为 ProductId) 需要分配给子元素。在呈现页面时,ListView 迭代其项目,并使用之前介绍的可预测模式替换它们的 id。
还需注意,ShopCartExpandedNonEmpty 是应用了 Inherit ClientID 模式的面板。事实是,此 <div> 元素的 id 保留了原始 asp:Panel id,这表示该控件继承了为 Static 的父控件的 ClientIDMode。
b. 查看 ListView 生成的所有 ShoppingCartItem,并查看显示购物车中各产品的 Quantity 和 TotalPrice 信息的 <span> 元素(以前为 asp:Labels)。
![](http://i.msdn.microsoft.com/ee851721.image013(zh-cn).jpg)
图 6
查看生成的可预测 ClientID
下一步
练习 2:实现双向路由支持
练习 2:实现双向路由支持
在本练习中,您将了解如何利用公共 ASP.NET Routing 引擎,它可以帮助您自定义应用程序公开的 URL。此外,您将使用新的表达式生成器来生成基于路由定义的动态 URL,从而缓解对固定静态链接的需求。此特性提供了全面的类支持,允许您为 Web 窗体页面定义任何自定义路
由。
通过使用 ASP.NET Routing 和全新的双向支持,用户可以解除 URL 与物理 Web 窗体的关联,从而实现更加友好的 URL 并利用强大的搜索引擎发现并使用它们。
注意:要验证每个步骤是否正确执行,建议在每次任务结束时构建解决方案。
任务 1 –在应用程序中启用 ASP.NET 路由
在此任务,您将在 Web Forms 中启用 ASP.NET Routing 引擎,这需要添加 UrlRouting HTTP Module 并创建路由来指定所匹配的 URL 模式。
1. 以管理员身份打开 Microsoft Visual Studio 2010。右键单击Start | All Programs | Microsoft Visual Studio 2010 | Microsoft Visual Studio 2010 并选择 Run as Administrator。
2. 打开 %TrainingKitInstallationFolder%\Labs\AspNetWebForms4\Source\Ex01-ClientId\begin\ 下的解决方案文件 WebFormsSampleApp.sln。
注意:也可以继续使用上一个练习完成时获得的解决方案。
3. 在 Web.cofig 文件中,添加 UrlRouting HTTP 模块。在 <httpModules> 节点中添加以下突出显示的元素。
Web.config
...
<system.web>
...
<httpmodule>
<add name="RoutingModule" type="System.Web.Routing.UrlRoutingModule"/>
<add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
</httpmodule>
...
<system.web>
...
注意: UrlRoutingModule 类是一个基本 HTTP Module,用于将传入 HTTP 请求与 ASP.NET 应用程序中的路由匹配。该模块迭代所有已定义的路由,搜索 URL 模式与 HTTP 请求匹配的已定义路由。当模块找到匹配的路由之后,它会检索该路由的 IRouteHandler 对象。该模块从路由处理程序获取 IHttpHandler 对象,并使用它作为当前请求的处理程序。
有关更多信息,请参见 UrlRoutingModule 类。
4. 在 Global.asax,将默认创建的所有命名空间命令替换为以下代码。
C#
using System;
using System.Web.Routing;
5. 指定 Default.aspx 页面将要处理的 {category} 和 {category}/{page} 路由。为此,将以下粗体显示的代码添加到 Application_Start 方法中。
(代码片段– Web Forms 4.0 实验– Application_Start 方法)
C#
protected void Application_Start(object sender, EventArgs e)
{
RouteTable.Routes.Add("Category", new Route("{category}", new PageRouteHandler("~/Default.aspx")));
RouteTable.Routes.Add("CategoryAndPage", new Route("{category}/{page}", new PageRouteHandler("~/Default.aspx")));
}
注意: RouteTable 类是 ASP.NET Routing 引擎的主要类之一。它是存储为应用程序定义的 URL 路由的集中位置。
您可以向 RouteTable 添加路由,方法是指定一个唯一标识它们的名称,并创建 RouteBase 类的具体实现,在本例中为 Route 类。
路由是用于处理请求的 URL 模式,并且还可以用于动态构建 URL。Route 类允许您指定如何在 ASP.NET 应用程序中处理路由。您为各 URL 模式创建一个 Route 对象,它将映射到可处理与该模式对应的请求的类。
以上代码使用新的 PageRouteHandler 类匹配对一个页面的传入请求。这个类支持集成 Web Forms 与 ASP.NET Routing。
有关更多信息,请参见 RouteTable、RouteBase 和 Route 类。
任务 2 –使用 RouteUrlExpressionBuilder 修改导航链接
在本任务中,您将修改应用程序的导航链接,以使用前面的任务中定义的路由。您将利用新的 RouteUrlExpressionBuilder,方法是为应用程序添加双向路由支持。这意味着您将能够生成基于路由定义的动态 URL,从而能更加轻松地管理 ASP.NET 页面中注册的所有路由,而不需要编写固定静态链接。
6. 在应用程序中启用 RouteUrlExpressionBuilder。为此,在 Web.cofig 文件中,在<compilation> 节点中添加以下突出显示的 <expressionBuilders> 节点。
Web.config
...
<system.web>
...
<compilation debug="true" targetFramework="4.0" />
<expressionBuilders>
<add expressionPrefix="RouteUrl" type="System.Web.Compilation.RouteUrlExpressionBuilder"/>
</expressionBuilders>
</compilation>
...
<system.web>
...
注意:表达式生成器解析声明性表达式,并创建代码来检索绑定到控件属性的值。在非编译的场景中,支持非编译特性的表达式生成器将在运行时计算表达式。
7. 修改代码,在 Default.aspx 页面中生成类别导航链接,以使用新定义的路由。为此,在 Solution Explorer 中,双击 Default.aspx 并将所有类别 HyperLink 控件的 NavigateUrl 属性替换为以下值。
Default.aspx
...
<ul id="categoryTabs">
<li>
<asp:HyperLink runat="server"
NavigateUrl="<%$ RouteUrl:RouteName=Category, category=Bikes %>"
OnLoad="CategoryLink_Load" Text="Bikes" />
<asp:HyperLink runat="server"
NavigateUrl="<%$ RouteUrl:RouteName=Category, category=Components %>"
OnLoad="CategoryLink_Load" Text="Components" />
<asp:HyperLink runat="server"
NavigateUrl="<%$ RouteUrl:RouteName=Category, category=Clothing %>"
OnLoad="CategoryLink_Load" Text="Clothing" />
<asp:HyperLink runat="server"
NavigateUrl="<%$ RouteUrl:RouteName=Category, category=Accessories %>"
OnLoad="CategoryLink_Load" Text="Accessories"/>
</li>
</ul>
...
注意: 当页面解析器遇到使用字符串 <%$ %> 分隔的表达式时,它会根据字符串中的表达式创建一个表达式生成器。字符串中冒号 (:) 之前的部分是前缀,它在 Web.config 中定义。在本例中,RouteUrlExpressionBuilder 的前缀是 RouteUrl。
然后,RouteUrlExpressionBuilder 根据冒号 (:) 右侧的参数生成之前在 RouteTable 中注册的路由,在本例中为 Category 路由。
8. 修改代码,在 Default.aspx.cs 代码隐藏文件中生成类别导航链接,以使用新定义的路由。为此,在 Solution Explorer 中右键单击 Default.aspx,选择 View Code 并将 CreatePagerLinks 方法的最后两行替换为以下突出显示的代码。
注意:数据库在检索大量结果时,可以方便地将结果划分为不同的页面。页面链接是根据所检索的结果量动态生成的。
本例中要创建的路由类型为 CategoryAndName。举例来说,/Products/3 是一个可行的链接,其中 Products 是类别, 3 是要检索的页面数。
(代码片段– Web Forms 4.0 实验–创建分页器链接)
C#
private void CreatePagerLinks()
{
for (int i = 1; i <= this.TotalPages; i++)
{
HyperLink link = new HyperLink() { Text = i.ToString() };
if (i == this.SelectedPage)
{
link.CssClass = "currentPage";
}
PagerPanel.Controls.Add(link);
string expression = String.Format("RouteName={0}, category={1}, page={2}", "CategoryAndPage", this.SelectedCategoryName, i);
link.NavigateUrl = RouteUrlExpressionBuilder.GetRouteUrl(this, expression);
}
}
注意:通过调用 GetRouteUrl 静态方法,您可以直接在代码隐藏文件中使用 RouteUrlExpressionBuilder。这样,您可以为路由的参数动态赋值。
任务 3 –检索路由参数值
在本任务中,您将在每次提交返回时检索类别名称和页面索引参数。当您现在使用路由时,这些参数将不再出现在 QueryString 集合中。您将使用 Page 类中新定义的 RouteData 属性,它包含一个键-值集合,其中包括路由的所有参数。
1. 打开 Default.aspx.cs 代码隐藏文件。为此,在 Solution Explorer 中右键单击 Default.aspx 并选择 View Code。
2. 在 GetCategoryName 和 GetPageIndex 方法中将 Request.QueryString 集合替换为 RouteData.Values 集合。
C#
...
private string GetCategoryName()
{
string category = RouteData.Values["category"] as string;
AdventureWorksRepository repository = new AdventureWorksRepository();
if (category != null)
{
return category;
}
return repository.GetCategories()[0].Name;
}
private int GetPageIndex()
{
string page = RouteData.Values["page"] as string;
if (page != null)
return Convert.ToInt32(page);
return 1;
}
...
注意:RouteData 属性的键值集合包含从 URL 中解析的值。
有关更多信息,请参见 RouteData 类及其成员。
任务 4 –使用 RouteValueExpressionBuilder 检索路由值
在此任务中,您将学习如何直接从 ASP.NET 页面获取路由参数的值。为了展示此特性,您将添加一些消息到 Default.aspx 中,用于在未找到所请求的产品时显示,或者在请求页面不在范围内时显示。您将利用新的 RouteValueExpressionBuilder 从当前路由获取这些值,并使用友好的消息警告用户。
3. 在 Web.cofig 文件中,将 RouteValueExpressionBuilder 添加到 <expressionBuilders> 节点中。
Web.config
...
<system.web>
...
<compilation debug="true" targetFrameworkMoniker=".NETFramework,Version=v4.0">
<expressionBuilders>
<add expressionPrefix="RouteUrl" type="System.Web.Compilation.RouteUrlExpressionBuilder"/>
<add expressionPrefix="RouteValue" type="System.Web.Compilation.RouteValueExpressionBuilder" />
</expressionBuilders>
</compilation>
...
<system.web>
...
4. 使用 RouteValue 表达式生成器为 Default.aspx 页面中不存在的类别以及页面编号添加消息。为此,在 Source 视图中打开 Default.aspx , 并将 PageIndexOverflowPanel 和 NoProductsFoundPanel 面板的内容替换为以下内
容。
注意:如果查看代码隐藏文件,您会看到一个 ApplyProductsFilter() 方法,其中包含将相应面板设置为可见的逻辑。
Default.aspx
...
<asp:Panel ID="PageIndexOverflowPanel" runat="server" Visible="false">
<div class="noResults">
The <strong><asp:Literal runat="server" Text="<%$ RouteValue:category%>" /></strong> category does not have the page <strong><asp:Literal ID="Literal1" runat="server" Text="<%$ RouteValue:page%>" /></strong>.
</div>
</asp:Panel>
<asp:Panel ID="NoProductsFoundPanel" runat="server" Visible="false">
<div class="noResults">
No products were found matching the <strong><asp:Literal runat="server" Text="<%$ RouteValue:category%>" /></strong> category you have selected.
</div>
</asp:Panel>
...
注意:RouteValueExpressionBuilder 允许您通过在注册路由时定义的名称获取路由参数。这个名称是冒号 (:) 右侧的值。例如,可以将表达式 <%$ RouteValue:category%> 理解为返回当前路由中名称 category 为的参数的值。
下一步
练习 2:验证
练习 2:验证
为了验证是否正确执行了练习 1 中的所有步骤,执行以下步骤:
验证 1
在此验证中,您将使用更新的类别和分页器链接导航 WebFormsSample 应用应用程序,查看最新引入的路由。
1. 启动 WebFormsSampleApp 项目的一个新实例。为此,在 Solution Explorer 中右键单击 WebSite 项目,指向 Debug 并选择 Start New Instance。
注意:如果出现 Debugging Not Enabled 对话框,选择 Modify the Web.config file to enable debugging 并单击 OK。
![](http://i.msdn.microsoft.com/ee851721.image017(zh-cn).jpg)
图 8
查看默认页面
2. 单击页眉上的 Components 链接浏览组件类别。Web 浏览器将重定向到以下地址 http://localhost:50000/Components。将出现以下输出。默认将显示页面 1。
![](http://i.msdn.microsoft.com/ee851721.image019(zh-cn).jpg)
图 9
查看组件类别的产品
注意:此路由将添加的 Category 路由映射到 Global.asax 中的 RouteTable。
3. 要浏览 Components 类别的其他页面,单击页面底部的页面链接。例如,如果单击 12,则会在 Web 浏览器中重定向到地址 http://localhost:50000/Components/12。
![](http://i.msdn.microsoft.com/ee851721.image021(zh-cn).jpg)
图 10
在 Components 类别的页面 12 中查看产品
注意:此路由将添加的 CategoryAndPage 路由映射到 Global.asax 中的 RouteTable。
验证 2
在此验证中,您将使用 RouteValueExpressionBuilder 检查生成的类别不存在和页面消息不存在错误您将请求两个页面,一个是不存在的 Category,另一个是不在页面索引范围内的页面。
1. 在浏览器中键入包含不存在类别 Url。例如,请求 http://localhost:50000/NonExisting 页面。您将看到“No products were found matching the NonExisting category you have selected”消息。
![](http://i.msdn.microsoft.com/ee851721.image023(zh-cn).jpg)
图 11
未找到类别消息
2. 在浏览器中键入包含超出页面索引范围的 Url。例如,请求 http://localhost:50000/Components/18 页面(Components 类别仅包含 17 个页面)。您将看到“The Components category does not have the page 18”消息。
图 12
页面未找到消息
下一步
练习 3:粒度化 View State
练习 3:粒度化 View State
WebForms 4.0 在 Control 类中包含了新的 ViewStateMode 属性,从而为 View State 提供了更加粒度化的控制。粒度化控制的 View State 意味着您可以在页面级启用它,且仅将它用于所需的控件。而不用在大量位置启用和禁用它。因此您可以更加轻松地说:我希望为页面禁用它,为这三个控件启用它。
任务 1 –禁用控件上的 ViewState
在此任务中,您将在 Default.aspx 中在页面级禁用 ViewState,并在 ShoppingCart.ascx 中在控件级禁用 ViewState。这样有助于在未来的步骤中实现粒度化的 ViewState 控制。
1. 以管理员身份打开 Microsoft Visual Studio 2010。右键单击 Start | All Programs | Microsoft Visual Studio 2010 | Microsoft Visual Studio 2010 并选择 Run as Administrator。
2. 打开 %TrainingKitInstallationFolder%\Labs\AspNetWebForms4\Source\Ex01-ClientId\begin\ 下的解决方案文件 WebFormsSampleApp.sln。
注意:也可以继续使用上一个练习完成时获得的解决方案。
您可以直接使用验证部分中的这个初始解决方案,因此可以考虑在这个初始解决方案的副本上操作,以便保留原解决方案的完整性。
3. 在 Default.aspx 中在 Page 级禁用 ViewState。为此,在 Markup 模式下打开 Default.aspx,将以下突出显示的代码添加到 <% Page %> 命令中。
注意:这将会禁用页面中所有子控件的 ViewState。在稍后的步骤中,您将了解如何利用粒度化 ViewState,仅为页面中需要的控件启用它。
ASP.NET
<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/UI.master" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebFormsSampleApp._Default" EnableViewState="false" %>
4. 为购物车用户控件禁用 ViewState。为此,在 Markup 模式下打开 ShoppingCart.ascx(位于 UserControls 文件夹),并将以下突出显示的代码添加到 <% Control %> 命令中。
注意:为此控件启用 ViewStateMode 会造成呈现页面时的负载过大。要避免此问题,您可以为此控件及其所有子控件禁用 ViewStateMode,并在 Session 中处理它们。
ASP.NET
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="ShoppingCart.ascx.cs" Inherits="WebFormsSampleApp.UserControls.ShoppingCartControl"
EnableViewState="false" %>
任务 2 –在子控件上启用粒度化 ViewState
在此任务中,您将更改在 ViewState 中存储一些值的方式,比如说当前类别名称、当前所选页面以及页面总数。将这些值保存在 ViewState中是非常有用的,比如当您向购物车添加项时,以及在回发后保留准确的页面状态(相同类别,相同页面编号)时。
您将创建三个隐藏控件,分别对应于各个值,并且将通过为它们启用 ViewState 来利用粒度化 ViewState。因此,它们是唯一存储在 Default.aspx 页面 ViewState 中的字段,从而减小了页面的总大小,并提高了应用程序的性能。
注意:目前为止,这些值都存储在 Default.aspx 页面的 ViewState 中,但由于之前的任务禁用了此功能,此控件的 ViewState 集合将不再可用。
1. 添加三个新的隐藏字段,以替换存储在 Page 的 ViewState 中的值。为此,在 Markup 模式下打开 Default.aspx,将以下突出显示的代码添加到第二个 <asp:Content> 元素中。
注意:虽然您将使用这些隐藏字段的 ViewState 来保留回发之间的值,但也可以通过 ASP.NET Routing 根据 URL 来获取它们(另请参见 练习 2:实现双向路由支持)。但是,考虑到实用性,您将使用隐藏字段方法来展示新的 ViewStateMode 特性。
ASP.NET
...
<asp:Content ID="BodyContent" ContentPlaceHolderID="MainContent" runat="server">
<asp:HiddenField ID="CategoryNameState" runat="server" ViewStateMode="Enabled" />
<asp:HiddenField ID="TotalPagesState" runat="server" ViewStateMode="Enabled" />
<asp:HiddenField ID="SelectedPageState" runat="server" ViewStateMode="Enabled" />
<asp:HiddenField ID="ShoppingCartState" ClientIDMode="Static" runat="server" />
...
2. 在存储购物车状态的隐藏字段中启用 ViewState (指示展开或折叠)。为此,将以下突出显示的代码添加到 ShoppingCartState HiddenField 声明中。
ASP.NET
<asp:HiddenField ID="ShoppingCartState" ClientIDMode="Static" runat="server"
ViewStateMode="Enabled"/>
任务 3 –检索代码中的 ViewState 值
在此任务中,您将更改 SelectedCategoryName、TotalPages 和 SelectedPage 属性在代码中获取/设置其值的方式。您还将强制在每次回发时重载产品 ListView,这样做的原因在于禁用了该控件的 ViewState。
1. 将 SelectedCategoryName、TotalPages 和 SelectedPage 属性的 getter 和 setter 实现从 ViewState 替换为在隐藏字段中获取/设置值。为此,打开 Default.aspx.cs 文件,使用以下突出显示的代码替换当前属性定义。
(代码片段– Web Forms 4.0 实验– GranularViewState 属性 )
C#
public string SelectedCategoryName
{
get
{
if (this.CategoryNameState.Value == null)
{
this.CategoryNameState.Value = "Bikes";
}
return this.CategoryNameState.Value;
}
set
{
this.CategoryNameState.Value = value;
}
}
public int TotalPages
{
get
{
if (TotalPagesState.Value == null)
{
TotalPagesState.Value = "0";
}
return Convert.ToInt32(TotalPagesState.Value);
}
set
{
TotalPagesState.Value = Convert.ToString(value);
}
}
public int SelectedPage
{
get
{
if (this.SelectedPageState.Value == null)
{
this.SelectedPageState.Value = "1";
}
return Convert.ToInt32(this.SelectedPageState.Value);
}
set
{
this.SelectedPageState.Value = Convert.ToString(value);
}
}
2. 在每次回发时重载产品 ListView。为此,打开 Default.aspx.cs 文件,并在 Page_Load 方法中将 ApplyProductsFilter 方法调用移到 if (!PostBack) 条件子句外部。最终的方法应如下所示:
注意:虽然 Default.aspx 中的 ViewStateMode 已在页面级启用,但它的所有子控件在保存 ViewState 时都默认包含了产品 ListView。这是在回发时不需要重载产品的原因。这会显著增加呈现给用户的页面大小。
(代码片段– Web Forms 4.0 实验– Page_Load 方法)
C#
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
this.SelectedCategoryName = GetCategoryName();
this.SelectedPage = GetPageIndex();
}
ApplyProductsFilter();
CreatePagerLinks();
}
注意:在本例中,您将在每次回发时重载 ListView (通过数据库),因为没有为此控件存储 ViewState。
在更加实际的场景中,您应该确定利用更多负载来为产品的 ListView 存储 ViewState 方便,还是重新在数据库中检索值方便。
下一步
练习 3:验证
练习 3:验证
为了验证是否正确执行了练习 1 中的所有步骤,执行以下步骤:
验证 1
在此验证中,您将了解如何在应用中采用粒度化 ViewState 控件来减小页面大小。您将提交一些订单,并浏览一些类别,最后将比较使用粒度化 ViewState 的页面以及其他在页面级启用了 ViewState 的页面在呈现后的大小差异。对于后者,您将直接使用本练习的初始解决方案,它并没有实现粒度化 ViewState。
1. 启动 WebFormsSampleApp 项目的一个新实例。为此,在 Solution Explorer 中右键单击 WebSite 项目,指向 Debug 并选择 Start New Instance。
注意:如果出现 Debugging Not Enabled 对话框,选择 Modify the Web.config file to enable debugging 并单击 OK。
图 13
查看默认页面
2. 单击产品旁边的加号 ( ) 将它们添加到购物车中。
注意:确保为两个应用程序(分别使用和不使用 ViewState)添加了相同的产品,以便比较页面大小。
![](http://i.msdn.microsoft.com/ee851721.image028(zh-cn).jpg)
图 14
向购物车下单
3. 单击 CATEGORIES 页眉的链接浏览一个或多个类别。
注意:确保浏览两个应用程序(分别使用和未使用粒度化 ViewState)的相同类别,以便比较页面大小。
![](http://i.msdn.microsoft.com/ee851721.image030(zh-cn).jpg)
图 15
浏览 Component 类别
4. 打开本练习的初始解决方案(未使用粒度化 ViewState),并执行 1 到 3 步。
注意:两个浏览器所呈现的页面应该没有太大差异。细微差异在于各页面存储 ViewState 的方式上。使用粒度化 ViewState 的页面在保存 ViewState 时所用空间较小,因此可以向用户呈现更加轻量级的 html。
5. 查看两个页面的 html 源代码,可以发现两个页面的已编码的 ViewState 隐藏字段在长度上存在差异。为此,在浏览器的各页面中单击鼠标右键,并选择 View Source。找到下图所示标记,比较其长度。
注意:下图显示了两个页面的 html 源代码。上方的 html 源代码属于粒度化 ViewState 页面,而下方的源代码属于页面级 ViewState。
在本例中,页面相对较小,两个页面在大小上的差异大约是 2KB(页面总大小的
10%)。在较大的页面中,差异会更加明显,会对应用程序的总体性能造成不利影响。
![](http://i.msdn.microsoft.com/ee851721.image032(zh-cn).jpg)
图 16
查看两个 ViewState 值之间的差异(上–粒度化 View State;下–页面级)
下一步
总结
总结
在本实验中,您了解了 Web Forms 4.0 中的一些新特性和增强功能,比如用于删除客户端 ID 中的无用信息的 ClientIDMode 属性、用于启用 URL 路由的 PageRouteHanlder 类以及实现粒度化 ViewState 控制的 ViewStateMode 属性。