母版页和 ASP.NET AJAX (C#)

作者 :Scott Mitchell

讨论使用 ASP.NET AJAX 和母版页的选项。 查看如何使用 ScriptManagerProxy 类;讨论如何加载各种 JS 文件,具体取决于是在母版页还是内容页中使用 ScriptManager。

简介

在过去的几年里,越来越多的开发人员一直在构建支持 AJAX 的 Web 应用程序。 支持 AJAX 的网站使用许多相关的 Web 技术来提供响应速度更快的用户体验。 借助 Microsoft ASP.NET AJAX 框架,创建支持 AJAX 的 ASP.NET 应用程序非常简单。 ASP.NET AJAX 内置于 ASP.NET 3.5 和 Visual Studio 2008 中;它还以单独下载的形式提供给 ASP.NET 2.0 应用程序。

使用 ASP.NET AJAX 框架生成已启用 AJAX 的网页时,必须向使用该框架的每个页面添加一个 ScriptManager 控件 。 顾名思义,ScriptManager 管理启用 AJAX 的网页中使用的客户端脚本。 ScriptManager 至少会发出 HTML,指示浏览器下载构成 AJAX 客户端库 ASP.NET JavaScript 文件。 它还可用于注册自定义 JavaScript 文件、启用脚本的 Web 服务和自定义应用程序服务功能。

如果网站使用母版页 () ,则不一定需要将 ScriptManager 控件添加到每个内容页;相反,可以将 ScriptManager 控件添加到母版页。 本教程介绍如何将 ScriptManager 控件添加到母版页。 它还介绍如何使用 ScriptManagerProxy 控件在特定内容页中注册自定义脚本和脚本服务。

注意

本教程不介绍如何使用 ASP.NET AJAX 框架设计或构建已启用 AJAX 的 Web 应用程序。 有关使用 AJAX 的详细信息,请参阅 ASP.NET AJAX 视频教程,以及本教程末尾的“进一步阅读”部分中列出的那些资源。

检查 ScriptManager 控件发出的标记

ScriptManager 控件发出标记,指示浏览器下载构成 ASP.NET AJAX 客户端库的 JavaScript 文件。 它还会将一些内联 JavaScript 添加到初始化此库的页面。 以下标记显示添加到包含 ScriptManager 控件的页面呈现输出的内容:

<script src="/ASPNET_MasterPages_Tutorial_08_CS/WebResource.axd?d=T8EVk6SsA8mgPKu7_sBX5w2 t=633363040378379010" type="text/javascript"></script>

<script src="/ASPNET_MasterPages_Tutorial_08_CS/ScriptResource.axd?d=SCE1TCh8B24VkQIU5a8iJFizuPBIqs6Lka7GEkxo-6ROKNw5LVPCpF_pmLFR-R-p_Uf42Ahmr_SKd8lwgZUWb2uPJmfX0X_H6oLA50bniyQ1 t=633465688673751480" type="text/javascript"></script>

<script type="text/javascript">
//<![CDATA[
if (typeof(Sys) === 'undefined') throw new Error('ASP.NET Ajax client-side framework failed to load.');
//]]>
</script>

<script src="/ASPNET_MasterPages_Tutorial_08_CS/ScriptResource.axd?d=SCE1TCh8B24VkQIU5a8iJFizuPBIqs6Lka7GEkxo-6ROKNw5LVPCpF_pmLFR-R-phT96yZPngppiP_VXlN4Vz2RuVtvwDiQzF9xt42dUCiYjL0UylAJoyYzStwvObx0U0 t=633465688673751480" type="text/javascript"></script>

<script type="text/javascript">
//<![CDATA[
Sys.WebForms.PageRequestManager._initialize('ScriptManager1', document.getElementById('form1'));
Sys.WebForms.PageRequestManager.getInstance()._updateControls([], [], [], 90);
//]]>
</script>

<script type="text/javascript">
//<![CDATA[
Sys.Application.initialize();
//]]>
</script>

标记 <script src="url"></script> 指示浏览器在 URL 处下载并执行 JavaScript 文件。 ScriptManager 发出三个此类标记:一个引用文件 WebResource.axd,另一个引用文件 ScriptResource.axd。 这些文件实际上并不作为网站中的文件存在。 相反,当其中任一文件的请求到达 Web 服务器时,ASP.NET 引擎会检查 querystring 并返回相应的 JavaScript 内容。 这三个外部 JavaScript 文件提供的脚本构成了 ASP.NET AJAX 框架的客户端库。 ScriptManager 发出的其他 <script> 标记包括初始化此库的内联脚本。

对于使用 ASP.NET AJAX 框架的页面,ScriptManager 发出的外部脚本引用和内联脚本至关重要,但对于不使用框架的页面则不需要。 因此,你可能认为最好只将 ScriptManager 添加到使用 ASP.NET AJAX 框架的页面。 这已经足够了,但如果你有许多页面使用框架,你最终会将 ScriptManager 控件添加到所有页面 - 至少可以说是一个重复的任务。 或者,可以将 ScriptManager 添加到母版页,然后将此必要的脚本注入到所有内容页中。 使用此方法时,无需记住将 ScriptManager 添加到使用 ASP.NET AJAX 框架的新页面,因为它已包含在母版页中。 步骤 1 逐步讲解如何将 ScriptManager 添加到母版页。

注意

如果计划在母版页的用户界面中包含 AJAX 功能,则别无选择 - 必须在母版页中包含 ScriptManager。

将 ScriptManager 添加到母版页的一个缺点是,无论是否需要, 上述脚本都会在每个 页面中发出。 这显然会导致那些通过母版页 (包含 ScriptManager 的页面浪费带宽) 但未使用 ASP.NET AJAX 框架的任何功能。 但到底浪费了多少带宽呢?

  • 上面显示的 ScriptManager (发出的实际内容) 总计略高于 1KB。
  • 但是, 元素引用的 <script> 三个外部脚本文件包含大约 450KB 未压缩的数据;在使用 gzip 压缩的网站中,总带宽可减少近 100KB。 但是,这些脚本文件由浏览器缓存一年,这意味着它们只需下载一次,然后就可以在网站上的其他页面中重复使用。

在最佳情况下,缓存脚本文件时,总成本为 1KB,这可以忽略不计。 但是,在最坏的情况下(即尚未下载脚本文件并且 Web 服务器未使用任何形式的压缩)的带宽命中约为 450KB,这可以通过宽带连接将任意位置添加到用户通过拨号调制解调器最多一分钟的时间。 好消息是,由于外部脚本文件由浏览器缓存,因此这种最坏的情况很少发生。

注意

如果在母版页中放置 ScriptManager 控件仍然感到不舒服,请考虑使用 Web 窗体 (<form runat="server"> 母版页中的标记) 。 使用回发模型的每个 ASP.NET 页必须恰好包含一个 Web 窗体。 添加 Web 窗体会添加其他内容:许多隐藏的窗体字段、 <form> 标记本身,以及用于从脚本启动回发的 JavaScript 函数(如有必要)。 对于不回发的页面,此标记是不必要的。 可以通过从母版页中删除 Web 窗体并将其手动添加到需要 Web 窗体的每个内容页来消除这种无关的标记。 但是,在母版页中使用 Web 窗体的好处大于将其不必要地添加到某些内容页的缺点。

步骤 1:将 ScriptManager 控件添加到母版页

每个使用 ASP.NET AJAX 框架的网页必须恰好包含一个 ScriptManager 控件。 由于此要求,通常将单个 ScriptManager 控件放在母版页上,以便所有内容页都自动包含 ScriptManager 控件。 此外,ScriptManager 必须在任何 ASP.NET AJAX 服务器控件(如 UpdatePanel 和 UpdateProgress 控件)之前。 因此,最好将 ScriptManager 放在 Web 窗体中任何 ContentPlaceHolder 控件之前。

Site.master打开母版页并将 ScriptManager 控件添加到 Web 窗体中的页面,但在元素 (之前<div id="topContent">,请参阅图 1) 。 如果使用 Visual Web Developer 2008 或 Visual Studio 2008,则 ScriptManager 控件位于“AJAX 扩展”选项卡的“工具箱”中。如果使用 Visual Studio 2005,则需要首先安装 ASP.NET AJAX 框架并将控件添加到工具箱。 访问 ASP.NET AJAX Wiki 以获取适用于 ASP.NET 2.0 的框架。

将 ScriptManager 添加到页面后,将其 IDScriptManager1 更改为 MyManager

将 ScriptManager 添加到母版页

图 01:将 ScriptManager 添加到母版页 (单击以查看全尺寸图像)

步骤 2:从内容页使用 ASP.NET AJAX 框架

将 ScriptManager 控件添加到母版页后,现在可以向任何内容页添加 ASP.NET AJAX 框架功能。 让我们创建一个新的 ASP.NET 页,用于显示 Northwind 数据库中随机选择的产品。 我们将使用 ASP.NET AJAX 框架的计时器控件每 15 秒更新一次此显示,显示新产品。

首先在名为 ShowRandomProduct.aspx的根目录中创建一个新页。 不要忘记将此新页面绑定到 Site.master 母版页。

向网站添加新 ASP.NET 页面

图 02:向网站添加新 ASP.NET 页面 (单击以查看全尺寸图像)

回想一下,在 母版页中指定标题、元标记和其他 HTML 标头教程中 ,我们创建了一个名为 BasePage 的自定义基页类,该类生成了未显式设置的页面标题。 转到 ShowRandomProduct.aspx 页面的代码隐藏类,使其派生自 BasePage (而不是 System.Web.UI.Page) 。

最后,更新 文件以 Web.sitemap 包含本课程的条目。 在 “母版到内容页交互” 课程的 下面 <siteMapNode> 添加以下标记:

<siteMapNode url="~/ShowRandomProduct.aspx" title="Master Pages and ASP.NET AJAX" />

<siteMapNode> 元素的添加反映在课程列表 (见图 5) 。

显示随机选择的产品

返回到 ShowRandomProduct.aspx。 从Designer,将一个 UpdatePanel 控件从“工具箱”拖动到MainContent“内容”控件中,并将其 ID 属性设置为 ProductPanel。 UpdatePanel 表示屏幕上可通过部分页面回发异步更新的区域。

我们的第一个任务是在 UpdatePanel 中显示有关随机选择的产品的信息。 首先将 DetailsView 控件拖动到 UpdatePanel 中。 将 DetailsView 控件的 ID 属性设置为 ProductInfo 并清除其 HeightWidth 属性。 展开 DetailsView 的智能标记,并从“选择数据源”下拉列表中选择将 DetailsView 绑定到名为 RandomProductDataSource的新 SqlDataSource 控件。

将 DetailsView 绑定到新的 SqlDataSource 控件

图 03:将 DetailsView 绑定到新的 SqlDataSource 控件 (单击以查看全尺寸图像)

将 SqlDataSource 控件配置为通过 NorthwindConnectionString (连接到 Northwind 数据库,该 (我们在 内容页中的与母版页交互 教程中创建) 。 配置 select 语句时,请选择指定自定义 SQL 语句,然后输入以下查询:

SELECT TOP 1 ProductName, UnitPrice
FROM Products
ORDER BY NEWID()

TOP 1句中的SELECT关键字 (keyword) 仅返回查询返回的第一条记录。 函数NEWID() (GUID) 生成新的全局唯一标识符值,并可用于ORDER BY子句中以随机顺序返回表的记录。

配置 SqlDataSource 以返回单个随机选择的记录

图 04:配置 SqlDataSource 以返回单个随机选择的记录 (单击以查看全尺寸图像)

完成向导后,Visual Studio 将为上述查询返回的两列创建 BoundField。 此时,页面的声明性标记应如下所示:

<asp:UpdatePanel ID="ProductPanel" runat="server">
 <ContentTemplate>
 <asp:DetailsView ID="ProductInfo" runat="server" AutoGenerateRows="False" 
 DataSourceID="RandomProductDataSource">
 <Fields>
 <asp:BoundField DataField="ProductName" HeaderText="ProductName" 
 SortExpression="ProductName" />
 <asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice" 
 SortExpression="UnitPrice" />
 </Fields>
 </asp:DetailsView>
 <asp:SqlDataSource ID="RandomProductDataSource" runat="server" 
 ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>" SelectCommand="SELECT TOP 1 ProductName, UnitPrice FROM Products ORDER BY NEWID()"></asp:SqlDataSource>
 </ContentTemplate>
</asp:UpdatePanel>

图 5 显示了 ShowRandomProduct.aspx 通过浏览器查看时的页面。 单击浏览器的“刷新”按钮以重新加载页面;应会看到 ProductName 新随机选择的记录的 和 UnitPrice 值。

显示随机产品的名称和价格

图 05:显示随机产品名称和价格 (单击以查看全尺寸图像)

每 15 秒自动显示一个新产品

ASP.NET AJAX 框架包含计时器控件,该控件在指定时间执行回发;在回发时引发计时器的 Tick 事件。 如果将 Timer 控件放置在 UpdatePanel 中,它将触发部分页面回发,在此期间,我们可以将数据重新绑定到 DetailsView 以显示随机选择的新产品。

为此,请从工具箱拖动计时器并将其放入 UpdatePanel 中。 将计时器的 IDTimer1 更改为 ProductTimer ,将其 Interval 属性从 60000 更改为 15000。 属性 Interval 指示两次回发之间的毫秒数;将其设置为 15000 会导致计时器每隔 15 秒触发一次部分页面回发。 此时,计时器的声明性标记应如下所示:

<asp:UpdatePanel ID="ProductPanel" runat="server" onload="ProductPanel_Load">
 <ContentTemplate>
 ...

 <asp:Timer ID="ProductTimer" runat="server" Interval="15000">
 </asp:Timer>
 </ContentTemplate>
</asp:UpdatePanel>

为计时器的 Tick 事件创建事件处理程序。 在此事件处理程序中,我们需要通过调用 DetailsView 的 DataBind 方法将数据重新绑定到 DetailsView。 这样做会指示 DetailsView 从其数据源控件中重新检索数据,这将选择并显示新的随机选择的记录 (就像通过单击浏览器的“刷新”按钮) 重新加载页面时一样。

protected void ProductTimer_Tick(object sender, EventArgs e)
{
    ProductInfo.DataBind();
}

就是这么简单! 通过浏览器重新访问页面。 最初,将显示随机产品的信息。 如果你耐心地watch屏幕,你会注意到,15 秒后,有关新产品的信息会神奇地取代现有显示器。

为了更好地查看此处发生的情况,让我们向 UpdatePanel 添加一个 Label 控件,用于显示显示器的上次更新时间。 在 UpdatePanel 中添加标签 Web 控件,将其 ID 设置为 LastUpdateTime,并清除其 Text 属性。 接下来,为 UpdatePanel 的 Load 事件创建事件处理程序,并在 Label 中显示当前时间。 (每次完整或部分页面回发时都会触发 UpdatePanel 的事件 Load 。)

protected void ProductPanel_Load(object sender, EventArgs e)
{
    LastUpdateTime.Text = "Last updated at " + DateTime.Now.ToLongTimeString();
}

完成此更改后,页面将包含当前显示的产品加载时间。 图 6 显示了首次访问时的页面。 图 7 显示了计时器控件“勾选”15 秒后的页面,并且 UpdatePanel 已刷新以显示有关新产品的信息。

页面加载时显示随机选择的产品

图 06:随机选择的产品显示在页面加载 (单击以查看全尺寸图像)

每 15 秒显示一个随机选择的新产品

图 07:每 15 秒显示一个新的随机选择的产品 (单击以查看全尺寸图像)

步骤 3:使用 ScriptManagerProxy 控件

除了包含 ASP.NET AJAX 框架客户端库所需的脚本外,ScriptManager 还可以注册自定义 JavaScript 文件、对已启用脚本的 Web 服务的引用,以及自定义身份验证、授权和配置文件服务。 通常,此类自定义特定于特定页面。 但是,如果在母版页的 ScriptManager 中引用自定义脚本文件、Web 服务引用或身份验证、授权或配置文件服务,则它们将包含在网站 的所有 页面中。

若要逐页添加与 ScriptManager 相关的自定义项,请使用 ScriptManagerProxy 控件。 可以将 ScriptManagerProxy 添加到内容页,然后从 ScriptManagerProxy 注册自定义 JavaScript 文件、Web 服务引用或身份验证、授权或配置文件服务;这具有为特定内容页注册这些服务的效果。

注意

一个 ASP.NET 页只能有一个 ScriptManager 控件。 因此,如果 ScriptManager 控件已在母版页中定义,则无法将 ScriptManager 控件添加到内容页。 ScriptManagerProxy 的唯一用途是为开发人员提供一种在母版页中定义 ScriptManager 的方法,但仍能够逐页添加 ScriptManager 自定义项。

若要查看 ScriptManagerProxy 控件的运行情况,让我们在 中 ShowRandomProduct.aspx 增加 UpdatePanel,以包含一个使用客户端脚本暂停或恢复计时器控件的按钮。 Timer 控件有三种客户端方法,可用于实现此所需功能:

  • _startTimer() - 启动计时器控件
  • _raiseTick() - 导致 Timer 控件“滴答”,从而在服务器上回发并引发其 Tick 事件
  • _stopTimer() - 停止计时器控件

让我们创建一个 JavaScript 文件,其中包含一个名为 的 timerEnabled 变量和一个名为 的 ToggleTimer函数。 变量 timerEnabled 指示计时器控件当前是已启用还是禁用;它默认为 true。 函数 ToggleTimer 接受两个输入参数:对“暂停/恢复”按钮的引用和 Timer 控件的客户端 id 值。 此函数切换 的值 timerEnabled,获取对 Timer 控件的引用,根据) 的值 timerEnabled 启动或停止计时器 (,并将按钮的显示文本更新为“Pause”或“Resume”。 只要单击“暂停/恢复”按钮,就会调用此函数。

首先在名为 Scripts的网站中创建一个新文件夹。 接下来,将一个新文件添加到名为 TimerScript.js JScript 文件类型的 Scripts 文件夹。

将新的 JavaScript 文件添加到 Scripts 文件夹

图 08:将新的 JavaScript 文件添加到 Scripts 文件夹 (单击以查看全尺寸图像)

已将新的 JavaScript 文件添加到网站

图 09:已将新的 JavaScript 文件添加到网站 (单击以查看全尺寸图像)

接下来,将以下 scrip 添加到 TimerScript.js 文件:

var timerEnabled = true;
function ToggleTimer(btn, timerID)
{
    // Toggle the timer enabled state
    timerEnabled = !timerEnabled;

    // Get a reference to the Timer
    var timer = $find(timerID);

    if (timerEnabled)
    {
        // Start timer
        timer._startTimer();

        // Immediately raise a tick
        timer._raiseTick();

        btn.value = 'Pause';
    }
    else
    {
        // Stop timer
        timer._stopTimer();

        btn.value = 'Resume';
    }
}

我们现在需要在 中 ShowRandomProduct.aspx注册此自定义 JavaScript 文件。 ShowRandomProduct.aspx返回到 并将 ScriptManagerProxy 控件添加到页面;将其ID设置为 MyManagerProxy。 若要注册自定义 JavaScript 文件,请在Designer中选择 ScriptManagerProxy 控件,然后转到属性窗口。 其中一个属性标题为 Scripts。 选择此属性将显示 ScriptReference 集合编辑器如图 10 所示。 单击“添加”按钮以包含新的脚本引用,然后在 Path 属性中输入脚本文件的路径: ~/Scripts/TimerScript.js

向 ScriptManagerProxy 控件添加脚本引用

图 10:向 ScriptManagerProxy 控件添加脚本引用 (单击以查看全尺寸图像)

添加脚本引用后,ScriptManagerProxy 控件的声明性标记将更新为包含 <Scripts> 单个 ScriptReference 条目的集合,如以下标记代码片段所示:

<asp:ScriptManagerProxy ID="MyManagerProxy" runat="server">
 <Scripts>
 <asp:ScriptReference Path="~/Scripts/TimerScript.js" />
 </Scripts>
</asp:ScriptManagerProxy>

条目 ScriptReference 指示 ScriptManagerProxy 在其呈现的标记中包含对 JavaScript 文件的引用。 也就是说,通过在 ScriptManagerProxy 中注册自定义脚本, ShowRandomProduct.aspx 页面呈现的输出现在包含另一个 <script src="url"></script> 标记: <script src="Scripts/TimerScript.js" type="text/javascript"></script>

现在可以从页面中的ToggleTimer客户端脚本ShowRandomProduct.aspx调用 中TimerScript.js定义的函数。 在 UpdatePanel 中添加以下 HTML:

<input type="button" id="PauseResumeButton" 
    value="Pause" 
    onclick="ToggleTimer(this, '<%=ProductTimer.ClientID %>');" />

这将显示一个带有文本“暂停”的按钮。 每当单击该按钮时,将调用 JavaScript 函数 ToggleTimer ,并传入对按钮的引用以及计时器控件的 id 值, (ProductTimer) 。 请注意用于获取 id Timer 控件值的语法。 <%=ProductTimer.ClientID%>发出 Timer 控件的 ClientID 属性的值ProductTimer。 在 内容页中的控件 ID 命名教程中 ,我们讨论了服务器端 ID 值与生成的客户端 id 值之间的差异,以及如何 ClientID 返回客户端 id

图 11 显示了首次通过浏览器访问时的此页面。 计时器当前正在运行,每 15 秒更新一次显示的产品信息。 图 12 显示了单击“暂停”按钮后的屏幕。 单击“暂停”按钮将停止计时器,并将按钮的文本更新为“恢复”。 用户单击“恢复”后,产品信息将刷新 (,) 每 15 秒继续刷新一次。

单击“暂停”按钮以停止计时器控件

图 11:单击“暂停”按钮可停止计时器控件 (单击以查看全尺寸图像)

单击“恢复”按钮重启计时器

图 12:单击“恢复”按钮重启计时器 (单击以查看全尺寸图像)

总结

使用 ASP.NET AJAX 框架生成已启用 AJAX 的 Web 应用程序时,每个已启用 AJAX 的网页必须包含 ScriptManager 控件。 为了简化此过程,我们可以将 ScriptManager 添加到母版页,而不必记住将 ScriptManager 添加到每个内容页。 步骤 1 演示了如何将 ScriptManager 添加到母版页,而步骤 2 介绍了如何在内容页中实现 AJAX 功能。

如果需要将自定义脚本、对已启用脚本的 Web 服务的引用,或自定义的身份验证、授权或配置文件服务添加到特定内容页,请将 ScriptManagerProxy 控件添加到内容页,然后在其中配置自定义项。 步骤 3 检查了如何使用 ScriptManagerProxy 在特定内容页中注册自定义 JavaScript 文件。

编程愉快!

深入阅读

有关本教程中讨论的主题的详细信息,请参阅以下资源:

关于作者

Scott Mitchell 是多本 ASP/ASP.NET 书籍的作者,4GuysFromRolla.com 的创始人,自 1998 年以来一直从事 Microsoft Web 技术工作。 Scott 担任独立顾问、培训师和作家。 他的最新书是 山姆斯在24小时内 ASP.NET 3.5。 可以在 上联系 mitchell@4GuysFromRolla.com 斯科特,也可以通过他的博客在 联系 http://ScottOnWriting.NET

特别感谢

本教程系列由许多有用的审阅者审阅。 有兴趣查看我即将发布的 MSDN 文章? 如果是这样,请在 mitchell@4GuysFromRolla.com