如何在启用输出缓存时在 ASP.NET 中操作母版页控件






4.68/5 (13投票s)
这篇简短的文章展示了如何在 ASP.NET 中操作母版页控件,当启用输出缓存时。
目录
- 引言
- 最简单的解决办法
- 当控件定义了输出缓存时会发生什么?
- 那么解决方案是什么?
- 一个好的建议
- 问题:将控件添加回 Page.Controls 会导致服务器错误
- 问题:设置控件可见性以外的其他属性
- 结论
- 参考文献
开发平台
- Visual Studio 2010
- Microsoft .NET Framework 4.0
- IIS 5.1~7
引言
在这篇简短的文章中,我们将看到如何通过一些巧妙的技巧,从内容页操作 ASP.NET 网站母版页中的控件。这个主题似乎有点无聊,应该很简单,但如果没有非常出人意料的转折,我不会和社区讨论。
情况是这样的,我们有一个 ASP.NET 4.0 网站应用程序,带有一个母版页。在某些情况下,我们需要隐藏母版页的控件,特别是页眉和页脚。
为了演示想法和故事,我创建了一个简单的 ASP.NET 4.0 Web 应用程序在 Visual Studio 中。上面的解决方案资源管理器组件是截图,没什么特别的,只是在创建项目后截取的。
“Site.Master”网站布局 HTML 如下所示
<body>
<form runat="server">
<div class="page">
<uc1:Header ID="Header1" runat="server" />
<div class="main">
<asp:ContentPlaceHolder ID="MainContent" runat="server" />
</div>
<div class="clear">
</div>
</div>
<uc2:Footer ID="Footer1" runat="server" />
</form>
</body>
看起来很无辜,对吧?事实上,我们大多数网站模板都有一个页眉、主内容、页脚,采用通用风格。现在,需求是在其中一个内容页面中隐藏页眉和页脚,实际上是在几个页面中。这些页面是报表页面,没有必要显示页眉和页脚,因为这些页面可以打印。
最简单的解决办法
最简单的解决办法就是使用 `page.master` 对象中的 `FindControl` 方法,然后简单地隐藏它们。我们也可以将它们转换为自定义控件,然后设置属性。这在 99% 的情况下都会奏效,而且非常简单。在网上通过 Google 搜索可以找到,第一个结果就会解决问题。下面是实现最直接方法的代码。
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
var masterPage = Master;
if (masterPage != null)
{
masterPage.FindControl("Header1").Visible = false;
masterPage.FindControl("Footer1").Visible = false;
}
}
}
是的,就像这样,我们网站的页眉和页脚就会遵守可见性规则。现在,我们之所以在这里,是因为我们有一个高性能的网站,它没有视图状态,没有会话,什么都没有。所有登录和安全措施都依赖于 cookie 和每个请求。
此外,我们对页眉和页脚启用了输出缓存,“9000”。
当控件定义了输出缓存时会发生什么?
我敢肯定你们都知道可以缓存页面、用户控件的输出缓存,这是一个标准的 ASP.NET 属性,可以添加到任何页面或用户控件中,以在特定时间内缓存对象的输出,并带有一些参数或自定义。要做到这一点,我们必须在 HTML 中将 `<%@ OutputCache Duration="9999" VaryByParam="none" %>` 添加到控件或页面声明中。要了解更多信息,请访问 http://msdn.microsoft.com/en-us/library/hdxfb6cy(v=vs.71).aspx。
当我们在 Debug 模式下运行应用程序时,上面的代码会发生什么?页面第一次运行时没问题。如果我们再次尝试访问会怎样?只需在浏览器中按 Enter 或刷新浏览器页面,糟了!它不起作用,显示服务器错误,并说母版页没有这个控件,最终抛出服务器错误。
如果您检查控件是否存在,第一次会返回是,第二次会说不存在。这是正常的,因为我们试图修改的控件被缓存了,因此无法这样访问。
那么解决方案是什么?
解决方案是找到缓存的控件,然后对该控件进行操作,最后将其添加回控件,以便我们所做的任何更改都能反映在页面的最终输出中。下面是这部分代码。这次我们修改了代码并将其保存在母版页中,然后从内容页调用了一个方法。
母版页代码
using System;
using System.Web.UI;
using MasterPageControlExample.Controls;
namespace MasterPageControlExample
{
public partial class SiteMaster : MasterPage
{
protected void Page_Load(object sender, EventArgs e)
{
}
public void DisplayHeader(bool value)
{
if(Header1!=null)
Header1.Visible = value;
else
{
var cachedHeader = (PartialCachingControl) Page.LoadControl(@"Controls\Header.ascx");
Page.Controls.Add(cachedHeader);
if(cachedHeader.CachedControl !=null)
{
var header = cachedHeader.CachedControl as Header;
if (header != null) header.Visible = value;
}
}
}
public void DisplayFooter(bool value)
{
if (Footer1 != null)
Footer1.Visible = value;
else
{
var cachedFooter = (PartialCachingControl)LoadControl(@"Controls\Footer.ascx");
Page.Controls.Add(cachedFooter);
if (cachedFooter.CachedControl != null)
{
var footer = cachedFooter.CachedControl as Footer;
if (footer != null) footer.Visible = value;
}
}
}
}
}
内容页代码
using System;
namespace MasterPageControlExample
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
var masterPage = Master;
if (masterPage != null)
{
var siteMaster = (masterPage as SiteMaster);
if (siteMaster != null) siteMaster.DisplayHeader(false);
}
}
}
}
在上面的母版页代码中,我们首先通过简单地检查是否为 null 来判断控件是否存在。第一次时它不会是 null,可以直接设置属性。第二次及之后,直到控件在缓存过期后再次创建,代码的 `else` 块才会被执行。
在 `DisplayHeader` 或 `DisplayFooter` 方法的 `else` 块中,我们加载了控件。这次 `Page.LoadControl` 方法将返回一个 `PartialCachingControl` 对象。它必须再次添加到 `Page.Controls` 中,才能通过 `PartialCachingControl` 对象的 `CachedControl` 属性进行访问。最后,我们添加所需的属性值。
现在这段代码将在 100% 的情况下工作,因为我们已经解决了输出缓存的问题。
一个好的建议
即使您是初学者,可能也知道这一点,但还是分享出来,因为有些人可能没有实现过。我说的是 ASP.NET 网站的 `PageBase` 和 `ControlBase` 对象。想法就是重用一些重复的代码以及页面生命周期中的任何其他横切关注点。这里我建议创建一个 `BasePage.cs` 类,您将在其中封装显示页眉和页脚的代码,而在内容页中,您将只调用这些方法,这样您的公共代码就会集中在一个地方。
下面是 `BasePage.cs` 的代码
BasePage 代码
namespace MasterPageControlExample.Objects
{
public class BasePage:System.Web.UI.Page
{
protected void DisplayHeader(bool value)
{
var masterPage = Master;
if (masterPage != null)
{
var siteMaster = (masterPage as SiteMaster);
if (siteMaster != null) siteMaster.DisplayHeader(value);
}
}
protected void DisplayFooter(bool value)
{
var masterPage = Master;
if (masterPage != null)
{
var siteMaster = (masterPage as SiteMaster);
if (siteMaster != null) siteMaster.DisplayFooter(value);
}
}
}
}
内容页代码
using System;
using MasterPageControlExample.Objects;
namespace MasterPageControlExample
{
public partial class _Default : BasePage
{
protected void Page_Load(object sender, EventArgs e)
{
DisplayHeader(false);
}
}
}
现在看起来紧凑且有条理了,当我们操作一些缓存的控件时,它往往会产生一些固有的问题,下面讨论其中一些问题。
问题:将控件添加回 Page.Controls 会导致服务器错误
我们不能将缓存的控件添加到 `Page.Controls` 集合中,因为页眉和页脚等控件很可能包含一些包含服务器控件的标签,这将在将这些控件添加到页面时产生问题。
将页眉添加回 Page.Controls 的结果
那么解决方案是什么?我们必须添加一个占位符,对于这个母版页,标记需要更改,以便页眉和页脚将被声明式地声明在占位符内部。
修改后的母版页代码
<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Site.master.cs"
Inherits="MasterPageControlExample.SiteMaster" %>
<%@ Register Src="Controls/Header.ascx" TagName="Header" TagPrefix="uc1" %>
<%@ Register Src="Controls/Footer.ascx" TagName="Footer" TagPrefix="uc2" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head runat="server">
<title></title>
<link href="~/Styles/Site.css" rel="stylesheet" type="text/css" />
<asp:ContentPlaceHolder ID="HeadContent" runat="server">
</asp:ContentPlaceHolder>
</head>
<body>
<form runat="server">
<div class="page">
<asp:PlaceHolder ID="PlaceHolderHeader" runat="server">
<uc1:Header ID="Header1" runat="server" />
</asp:PlaceHolder>
<div>
<h2>
<a href="Default.aspx">Default.aspx</a><br />
<a href="About.aspx">About.aspx</a><br />
<a href="Report.aspx">Report.aspx</a><br />
</h2>
</div>
<div class="main">
<asp:ContentPlaceHolder ID="MainContent" runat="server" />
</div>
<div class="clear">
</div>
</div>
<asp:PlaceHolder ID="PlaceHolderFooter" runat="server">
<uc2:Footer ID="Footer1" runat="server" />
</asp:PlaceHolder>
</form>
</body>
</html>
请注意,在上面的代码中,我们添加了两个占位符来容纳页眉和页脚,并且页眉和页脚是包含在占位符内的用户控件。现在,如果我们的目的是仅修改可见性状态,那么有一个非常简单的办法。
情况是这样的,我们有三个内容页面
- 默认页面 [我们要显示页眉和页脚]
- 关于页面 [我们只想显示页眉并隐藏页脚]
- 报表页面 [我们要隐藏页眉和页脚]
此场景下的内容页代码
//first page
public partial class _Default : BasePage
{
protected void Page_Load(object sender, EventArgs e)
{
DisplayHeader(true);
DisplayFooter(true);
}
}
//second page
public partial class About : BasePage
{
protected void Page_Load(object sender, EventArgs e)
{
DisplayHeader(true);
DisplayFooter(false);
}
}
//third page
public partial class Report : BasePage
{
protected void Page_Load(object sender, EventArgs e)
{
DisplayHeader(false);
DisplayFooter(false);
}
}
母版页代码
public void DisplayHeader(bool value)
{
if(Header1!=null)
Header1.Visible = value;
else
{
Page.LoadControl(@"Controls\Header.ascx");
var cachingControl = PlaceHolderHeader.Controls[0] as StaticPartialCachingControl;
if (cachingControl != null) cachingControl.Visible = value;
}
}
public void DisplayFooter(bool value)
{
if (Footer1 != null)
Footer1.Visible = value;
else
{
LoadControl(@"Controls\Footer.ascx");
var cachingControl = PlaceHolderFooter.Controls[0] as StaticPartialCachingControl;
if (cachingControl != null) cachingControl.Visible = value;
}
}
请注意,在这种情况下,我将 `control[0]` 强制转换为占位符,因为占位符内只有一个控件。另请注意,您不能在此处设置自定义属性。因为它是一个 `StaticPartialCachingControl`。
问题:设置控件可见性以外的其他属性
嗯,这是文章的最后一部分。我们想设置缓存控件的其他属性,不幸的是,这并不容易,也没有简单的办法来设置缓存控件的属性。相反,我们必须加载控件,然后将其添加回 `Controls` 集合,但在更改所需属性之前。
目的是在 `About.aspx` 页面中更改站点标题。我将跳过其余的细节,列出母版页的代码。这将是不言自明的。
用于设置站点标题的母版页代码
public void SetSiteTitle(string siteTitle)
{
if (Header1 != null)
Header1.SiteTitle = siteTitle;
else
{
var cachedHeader = (PartialCachingControl)LoadControl(@"Controls\Header.ascx");
PlaceHolderHeader.Controls.Clear();
PlaceHolderHeader.Controls.Add(cachedHeader);
if (cachedHeader.CachedControl != null)
{
var header = cachedHeader.CachedControl as Header;
if (header != null) header.SiteTitle = siteTitle;
}
}
}
关于这篇小文章,我还有很多东西想列举,但有些事情留待将来。希望这篇文章能得到一些好的反馈。
结论
在这篇简短的文章中,我们看到了一个特定问题的最简单解决办法,然后是缓存控件时会发生什么,最后是一些使之完善的技巧。这些知识可以在网上找到,可以通过任何搜索引擎轻松搜索到,但是对于初学者来说,这可能会让人恐慌,我试图将这些想法按顺序组织起来,以便绝对的初学者能够一眼看懂。