阅读文章

在Asp.net 1.1下实现MasterPage

[日期:2008-04-17] 来源:  作者: [字体: ]

     在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,有兴趣的朋友可以做更深一步的研究。
  
    


阅读:
录入:blue1000

评论 】 【 推荐 】 【 打印
上一篇:开发手记(一)准备新的征途
下一篇:WCF从理论到实践(15):响应变化
相关文章      
本文评论
发表评论


点评: 字数
姓名:

  • 尊重网上道德,遵守中华人民共和国的各项有关法律法规
  • 承担一切因您的行为而直接或间接导致的民事或刑事法律责任
  • 本站管理人员有权保留或删除其管辖留言中的任意内容
  • 本站有权在网站内转载或引用您的评论
  • 参与本评论即表明您已经阅读并接受上述条款