在ASP.net 2.0中,提供了一个MasterPage的功能,它可以让我们很方便的完成页面的整体结构相同的网站,而且后期修改界面的时候只要修改一下MasterPage即可,无需一个个界面进行修改,这样就大大的方便了开发人员。
可惜的是,在ASP.net 2.0以前的版本中,并不包含MasterPage的特性。虽然现在使用ASP.net 2.0或以上版本的开发者越来越多,但是常常由于项目周期长等原因,还有很大数量的开发人员使用ASP.net 1.1进行开发(比如我自己)。所以,虽然ASP.net 2.0发布这么长时间了,我们这些可怜的人还是无法应用。这不,.net 3.5都出来了,呵呵,感觉自己越来越落后了。
网上看到过别人在.net 1.1下实现类似MasterPage的功能,但感觉都不是很直观,而且用法和.net 2.0下的MasterPage相差比较大。前天翻开Spring.net来看,发现它的Web框架实现了.net 1.1下的MasterPage,而且他的用法和.net 2.0下的用法一样。自己也照着葫芦画了个瓢,不敢私藏,特拿出来分享。
1 主要原理
根据ASP.net 2.0的MasterPage应用,我们知道:
1. MasterPage里面包含ContentPlaceHolder控件用做为具体内容的容器
2. 使用母版页的页面包含Content控件提供具体内容
3. 页面展现时,会将MasterPage的内容展现出来,并把Content控件下的内容填充到ContentPlaceHolder中。
仿照ASP.net 2.0,对应到ASP.net 1.1下面应该是:
1. 做两个自定义控件,分别叫做ContentPlaceHolder和Content
2. MasterPage是一个用户控件(ascx),里面包含ContentPlaceHolder控件做为具体内容的容器
3. 使用母版页的页面包含Content控件提供具体内容
4. 页面展现时,会将MasterPage的内容展现出来,并把Content控件下的内容填充到ContentPlaceHolder中。
是啊,就这么简单,没啥特别的东西,但没看到Spring.net之前,怎么就没想过去实现这么个MasterPage呢?
2 ContentPlaceHolder和Content控件
前面说了,ContentPlaceHolder是内容的容器,Content控件是具体的内容,他们的代码如下:
Content.cs:
1using System.Web.UI;
2using System.Web.UI.WebControls;
3namespace ZSoft.Web.UI.WebControls
4{
5 /**//// <summary>
6 /// 内容控件
7 /// </summary>
8 [PersistChildren(true)]
9 [ParseChildren(false)]
10 public class Content : Control
11 {
12 private string contentPlaceHolderID;
13
14 public string ContentPlaceHolderID
15 {
16 get { return contentPlaceHolderID;}
17 set { contentPlaceHolderID = value;}
18 }
19 }
20}
21
可以看到,Content控件包含一个属性ContentPlaceHolderID,用以指向母版页的ContentPlaceHolder控件。
ContentPlaceHolder.cs:
1using System.Web.UI;
2using System.Web.UI.WebControls;
3
4namespace ZSoft.Web.UI.WebControls
5{
6 /**//// <summary>
7 /// 内容容器
8 /// </summary>
9 [PersistChildren(true)]
10 [ParseChildren(false)]
11 public class ContentPlaceHolder : WebControl
12 {
13 private Content content;
14
15 public Content Content
16 {
17 get { return content;}
18 set { content = value;}
19 }
20
21 protected override void Render(HTMLTextWriter writer)
22 {
23 //如果Content控件不为null,则展现Content控件的内容,否则,展现自己的内容
24 if(content != null)
25 {
26 content.RenderControl(writer);
27 }
28 else
29 {
30 base.Render (writer);
31 }
32 }
33 }
34}
35
ContentPlaceHolder控件拥有一个属性Content,指向一个Content控件实例,在下面的代码中你会看到它是何时被赋值的。同时,ContentPlaceHolder重写了Control控件的Render方法,当它拥有一个Content控件的实例的时候,展现Content控件的内容,否则,展现自己的内容(用于展现默认内容)。
注意PersistChildren(true)和ParseChildren(false),这两句指定了这两个控件是可以包含子控件的,这非常重要,因为不管是ContentPlaceHolder,还是Content控件,都需要拥有子控件(ContentPlaceHolder用子控件来表示默认的内容,Content用子控件表示具体的要替换的内容)。
上面说到,ContentPlaceHolder控件拥有一个属性Content,指向一个Content控件实例,那么,这个实例是什么时候被赋值的呢?
我们知道,MasterPage应该是一个用户控件,并且ContentPlaceHolder控件是包含在MasterPage控件里的,所以,我们应该在MasterPage里去初始化ContentPlaceHolder的Content属性。在页面初始化时,根据页面的MasterPageFile属性,加载MasterPage控件,然后初始化该控件里的ContentPlaceHolder。这样,我们就需要另外两个类,MasterPage基类和Page基类,分别对应母版控件和使用母版的页面。
3 Page和MasterPage
Page.cs:
1using System;
2using System.Web.UI;
3
4namespace ZSoft.Web.UI
5{
6 /**//// <summary>
7 /// 页面基类
8 /// </summary>
9 public class Page : System.Web.UI.Page
10 {
11 private string masterPageFile;
12 private MasterPage master;
13
14 /**//// <summary>
15 /// 母版的路径
16 /// </summary>
17 public string MasterPageFile
18 {
19 get { return masterPageFile;}
20 set { masterPageFile = value;}
21 }
22
23 /**//// <summary>
24 /// 是否有母版
25 /// </summary>
26 public bool HasMaster
27 {
28 get { return this.MasterPageFile != null || this.master != null;}
29 }
30
31 protected override void OnInit(EventArgs e)
32 {
33 if(HasMaster)
34 {
35 //加载母版并初始化母版
36 master = (MasterPage)LoadControl(MasterPageFile);
37 master.Initialize(this);
38 }
39 base.OnInit (e);
40 }
41
42 protected override void Render(HTMLTextWriter writer)
43 {
44 //有母版的时候,展现母版的内容
45 if(HasMaster && master != null)
46 {
47 master.RenderControl(writer);
48 }
49 else
50 {
51 base.Render (writer);
52 }
53 }
54 }
55}
56
Page类重写了OnInit方法,并在OnInit时,调用MasterPage类的Initialize方法初始化母版。另外,它重写了Render方法,当母版存在的时候,展现母版的内容。
MasterPage.cs:
1using System;
2using System.Web.UI;
3using ZSoft.Web.UI.WebControls;
4
5namespace ZSoft.Web.UI
6{
7 /**//// <summary>
8 /// 母版(for ASP.net 1.1 only)
9 /// </summary>
10 public class MasterPage : UserControl
11 {
12 /**//// <summary>
13 /// 初始化母版
14 /// </summary>
15 /// <param name="childPage"></param>
16 public void Initialize(Page childPage)
17 {
18 this.ID = "MasterPage";
19 for(int i = 0; i < childPage.Controls.Count; i++)
20 {
21 if(childPage.Controls[i] is Content)
22 {
23 Content content = childPage.Controls[i] as Content;
24
25 ContentPlaceHolder holder = (ContentPlaceHolder)this.FindControl(content.ContentPlaceHolderID);
26
27 if(holder == null)
28 {
29 throw new ArgumentException("在母版页中未找到Content PlaceHolder " + content.ContentPlaceHolderID);
30 }
31
32 holder.Content = content;
33 }
34 }
35
36 childPage.Controls.AddAt(0,this);
37 }
38 }
39}
40
在母版的初始化方法里,它遍历了子页面的第一层控件来寻找Content控件,然后根据Content控件实例的ContentPlaceHolderID属性,从自身找到相对应的ContentPlaceHolder控件,然后把Content控件的实例赋值给ContentPlaceHolder控件,从而达到初始化的目的,最后,母版把自己做为一个控件,加到子控件里(childPage.Controls.AddAt(0,this),这句话非常重要,少了这句会带来PostBack时的异常。
注意,上面的初始化方法,只是遍历了子页面的第一层控件来寻找Content控件,这就要求我们的子页面(即使用母版的页面)的Content控件不能放在runat=server的Form内了,因为如果控件位于runat=server的form内,页面的第一层控件里就遍历不到Content控件了,因为他们属于HTMLForm控件的子控件。当然,如果您非要在子控件的Content控件外层放置一个runat=server的form的话,那就要修改一下上面的这段代码了。
到这里为止,这个MasterPage的功能就被我们实现了,代码比较简单,下面简单介绍一下如何使用。
4 如何使用
它的使用方法和ASP.net 2.0下的MasterPage使用方法一样。
首先我们定义一个母版页,后台代码继承与上面定义的基类MasterPage:
1<%@ Control Language="c#" AutoEventWireup="false" Codebehind="Master.ascx.cs" Inherits="TaskManager.TestMaster.Master"
2 TargetSchema="http://schemas.microsoft.com/intellisense/ie5" %>
3<%@ Register Assembly="ZSoft.Web" TagPrefix="zsoft" Namespace="ZSoft.Web.UI.WebControls" %>
4<html>
5<head>
6 <title>
7 <zsoft:ContentPlaceHolder ID="Title" runat="server" />
8 </title>
9</head>
10<body>
11 <form id="Form1" runat="server">
12 <table width="100%" border="1">
13 <tr>
14 <td>
15 <h1>
16 <zsoft:ContentPlaceHolder ID="Head" runat="server">
17 Default Head
18 </zsoft:ContentPlaceHolder>
19 </h1>
20 </td>
21 </tr>
22 <tr>
23 <td>
24 <zsoft:ContentPlaceHolder ID="Content" runat="server" />
25 </td>
26 </tr>
27 <tr>
28 <td align="center">
29 the footer
30 </td>
31 </tr>
32 </table>
33 </form>
34</body>
35</html>
36
在这个母版里,定义了三个ContentPlaceHolder,分别表示页面的Title,Head和Content。
然后定义一个使用该模板的子页面(后台代码继承与上面定义的基类Page):
1<%@ Page Language="c#" Codebehind="WebForm1.aspx.cs" AutoEventWireup="false" Inherits="TaskManager.TestMaster.WebForm1" %>
2
3<%@ Register Assembly="ZSoft.Web" TagPrefix="zsoft" Namespace="ZSoft.Web.UI.WebControls" %>
4<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
5<html>
6<head>
7 <title>WebForm1</title>
8 <meta name="GENERATOR" content="Microsoft Visual Studio .NET 7.1">
9 <meta name="CODE_LANGUAGE" content="C#">
10 <meta name="vs_defaultClientScript" content="JavaScript">
11 <meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5">
12</head>
13<body ms_positioning="GridLayout">
14 <zsoft:Content ID="Content1" ContentPlaceHolderID="Title" runat="server">
15 Hello
16 </zsoft:Content>
17 <zsoft:Content ID="Content2" ContentPlaceHolderID="Head" runat="server">
18 ASP.net 1.1中的母版页
19 </zsoft:Content>
20 <zsoft:Content ID="Content3" ContentPlaceHolderID="Content" runat="server">
21 <asp:TextBox ID="TxtContent" runat="server" zvalidate="notnull(sdfsdf)" zbind="Content"></asp:TextBox>
22 <asp:Button ID="BtnSave" runat="server" OnClick="SaveClick" Text="submit" zCausesValidation="true" />
23 </zsoft:Content>
24</body>
25</html>
26
这个页面定义了三个Content控件,分别对应与母版的三个ContentPlaceHolder控件。在该页面的后台代码类的OnInit方法里,加入this.MasterPageFile = "Master.ascx";用以指定母版文件,如下:
1 override protected void OnInit(EventArgs e)
2 {
3 this.MasterPageFile = "Master.ascx";
4
5 //
6 // CODEGEN: 该调用是 ASP.NET Web 窗体设计器所必需的。
7 //
8 InitializeComponent();
9 base.OnInit(e);
10 }
11
关于这个MasterPageFile属性值的指定,这里是在OnInit方法里硬编码赋值的,您也可以通过额外的方式(如配置文件,Spring的依赖注入等)来实现以提高灵活性,当然这些不属于我们讨论的内容。
我还尝试着扩展Page指令,使MasterPageFile属性可以像ASP.net 2.0那样,通过Page指令来设置,如:
1<%@ Page Language="c#" MasterPageFile="Master.ascx" Inherits="TaskManager.TestMaster.WebForm1" %>
但不幸的是,.net 1.1并不能像2.0那样可以在Page指令里指定Page中属性的值,最终放弃了这个想法。如果哪位朋友知道如何扩展.net 1.1下的Page指令,希望能告诉我,不胜感激。
当然,如何能像VS 2005的窗体设计器那样支持MasterPage,有兴趣的朋友可以做更深一步的研究。

BK网络学院主要内容:平面设计教程,网站开发在线教程,网页制作教程,服务器教程,网络编程,数据库教程等。