母版页和 ASP.NET AJAX (VB)

作者 :Scott Mitchell

讨论使用 AJAX 和母版页 ASP.NET 选项。 查看使用 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 控件发出标记,指示浏览器下载构成 AJAX 客户端库 ASP.NET 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 引擎会检查查询字符串并返回相应的 JavaScript 内容。 这三个外部 JavaScript 文件提供的脚本构成了 AJAX 框架的客户端库 ASP.NET。 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 下载页,获取 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 页 (单击以查看全尺寸图像)

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

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

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

<siteMapNode> 元素的添加反映在“课程”列表中, (请参阅图 5) 。

显示随机选择的产品

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

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

将 DetailsView 绑定到新的 SqlDataSource 控件

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

配置 SqlDataSource 控件以通过 NorthwindConnectionString 我们在内容页[SKM2] 与母版页交互教程中创建的 (连接到 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 Sub ProductTimer_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles ProductTimer.Tick
 ProductInfo.DataBind()
End Sub

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

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

Protected Sub ProductPanel_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles ProductPanel.Load
 LastUpdateTime.Text = "Last updated at " & DateTime.Now.ToLongTimeString()
End Sub

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

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

图 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() - 导致计时器控件“刻度”,从而在服务器上回发并引发其 Tick 事件
  • _stopTimer() - 停止计时器控件

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

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

将新的 JavaScript 文件添加到脚本文件夹

图 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 控件,然后转到属性窗口。 其中一个属性标题为脚本。 选择此属性将显示 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 命名[SKM3] 教程中,我们讨论了服务器端 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 担任独立顾问、培训师和作家。 他的最新书是 山姆自学 ASP.NET 在24小时内3.5。 可在 上或通过他的博客http://ScottOnWriting.NET联系 mitchell@4GuysFromRolla.com Scott。

特别感谢

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