在 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呢?
前面说了,ContentPlaceHolder是内容的容器,Content控件是具体的内容,他们的代码如下:
Content.cs:

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

可以看到,Content控件包含一个属性ContentPlaceHolderID,用以指向母版页的ContentPlaceHolder控件。
ContentPlaceHolder.cs:

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

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:

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

Page类重写了OnInit方法,并在OnInit时,调用MasterPage类的Initialize方法初始化母版。另外,它重写了Render方法,当母版存在的时候,展现母版的内容。
MasterPage.cs:

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

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:

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

在这个母版里,定义了三个ContentPlaceHolder,分别表示页面的Title,Head和Content。
然后定义一个使用该模板的子页面(后台代码继承与上面定义的基类Page):

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

这个页面定义了三个Content控件,分别对应与母版的三个ContentPlaceHolder控件。在该页面的后台代码类的OnInit方法里,加入this.MasterPageFile = "Master.ascx";用以指定母版文件,如下:

2

3

4

5

6

7

8

9

10

11

关于这个 MasterPageFile属性值的指定,这里是在 OnInit方法里硬编码赋值的,您也可以通过额外的方式(如配置文件, Spring的依赖注入等)来实现以提高灵活性,当然这些不属于我们讨论的内容。
我还尝试着扩展 Page指令,使 MasterPageFile属性可以像 Asp.net 2.0那样,通过 Page指令来设置,如:

但不幸的是, .net 1.1并不能像 2.0那样可以在 Page指令里指定 Page中属性的值,最终放弃了这个想法。如果哪位朋友知道如何扩展 .net 1.1下的 Page指令,希望能告诉我,不胜感激。
当然,如何能像 VS 2005的窗体设计器那样支持 MasterPage,有兴趣的朋友可以做更深一步的研究。
转:http://www.cnblogs.com/northdevil/articles/1156293.html