ASP.NET Web Forms 4.0 简介

概述

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 文件夹。

 

 

图 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")%>&nbsp;(<%#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;

    width:150px;

}

#ShopCartExpanded div.priceColumn {

    float:right;

    width: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。

图 2

查看默认页面

2.            查看页面的源代码,确定静态 ClientID 模式(已在练习 1 中完成)的 asp:Panels 使用与服务器控件相同的 ID 呈现。为此,在浏览器中右键单击页面,选择 View Source。

注意:此时将打开一个文字编辑器,其中显示了呈现的 Default.aspx 页面的源代码。

3.            在页面的源代码中找到 Id 分别为  Id ShopCartCollapsed 和 ShopCartExpanded 的 <div> 元素。这两个 <div> 元素代表购物车的展开或折叠视图。

注意:ASP.NET 已将 asp:Panel 服务器控件呈现给 <div> 元素,保留了唯一标识它们的 ID。

图 3

查看所呈现的使用静态 ClientID 的 <div> 元素

4.            单击产品旁边的加号 ( ) 将它们添加到购物车中。请注意购物车的折叠视图如何更新新产品的信息。

 

图 4

向购物车添加产品

5.            展开和折叠购物车。为此,单击页面右上方的绿色面板。

注意:由于您已经知道了调用这些 <div> 元素的方式,因此可以实现购物车的展开和折叠。在本例中,除了直接使用 ID 选择器应用 CSS 样式之外,您还从 Java Script 中引用 <div> 元素以动画方式展开和折叠它们。

 

图 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)。

 

图 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。

 

图 8

查看默认页面

2.            单击页眉上的 Components 链接浏览组件类别。Web 浏览器将重定向到以下地址 http://localhost:50000/Components。将出现以下输出。默认将显示页面 1。

 

图 9

查看组件类别的产品

注意:此路由将添加的 Category 路由映射到 Global.asax 中的 RouteTable。

3.            要浏览 Components 类别的其他页面,单击页面底部的页面链接。例如,如果单击 12,则会在 Web 浏览器中重定向到地址 http://localhost:50000/Components/12。

 

图 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”消息。

 

图 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)添加了相同的产品,以便比较页面大小。

 

图 14

向购物车下单

3.            单击 CATEGORIES 页眉的链接浏览一个或多个类别。

注意:确保浏览两个应用程序(分别使用和未使用粒度化 ViewState)的相同类别,以便比较页面大小。

 

图 15

浏览 Component 类别

4.            打开本练习的初始解决方案(未使用粒度化 ViewState),并执行 1 到 3 步。

注意:两个浏览器所呈现的页面应该没有太大差异。细微差异在于各页面存储 ViewState 的方式上。使用粒度化 ViewState 的页面在保存 ViewState 时所用空间较小,因此可以向用户呈现更加轻量级的 html。

5.       查看两个页面的 html 源代码,可以发现两个页面的已编码的 ViewState 隐藏字段在长度上存在差异。为此,在浏览器的各页面中单击鼠标右键,并选择 View Source。找到下图所示标记,比较其长度。

注意:下图显示了两个页面的 html 源代码。上方的 html 源代码属于粒度化 ViewState 页面,而下方的源代码属于页面级 ViewState。

在本例中,页面相对较小,两个页面在大小上的差异大约是 2KB(页面总大小的

10%)。在较大的页面中,差异会更加明显,会对应用程序的总体性能造成不利影响。

 

图 16

查看两个 ViewState 值之间的差异(上–粒度化 View State;下–页面级)

               

下一步

总结

               

总结

在本实验中,您了解了 Web Forms 4.0 中的一些新特性和增强功能,比如用于删除客户端 ID 中的无用信息的 ClientIDMode 属性、用于启用 URL 路由的 PageRouteHanlder 类以及实现粒度化 ViewState 控制的 ViewStateMode 属性。