10.1 母版页
Web网站通常都包含一些公关的部分,比如题头、脚注、导航等部分。母版页其实可以为创建页面提供模板,一经创建,可以多次重用,减少设计页面的工作量。
10.1.1 母版页和内容页
母版页为具有扩展名.master的ASP.NET文件,它具有可以包括静态文本、HTML元素和服务器控件的预定义布局。母版页由特殊的@Master指令识别,该指令替换了用于普通.aspx页的@Page指令。该指令的声明如下面代码:
@Master指令中可以设置的属性如下。
● CodeFile:指定包含分部类的单独文件的名称,该分部类具有事件处理程序和特定于母版页的其他代码。
● Debug:指示是否使用调试符号来编译母版页。如果要使用调试符号进行编译,则为true;否则为false。
● Inherits:指定供页继承的代码隐藏类,它可以是从MasterPage类派生的任何类。
● Language:指定在对页中所有内联呈现(<% %>和<%= %>)和代码声明块进行编译时使用的语言。值可以表示.NET Framework支持的任何语言,包括VB(Visual Basic)、C#和JScript。
● Src:指定在请求页时动态编译的代码隐藏类的源文件名称。可以选择将页的编程逻辑包含在代码隐藏类中或“.aspx”文件的代码声明块中。
● MasterPageFile:指定用作某个母版页的“.master”文件。定义嵌套母版页方案中的子母版页时,在母版页中使用MasterPageFile属性。
● AutoEventWireup:指示是否可以使用语法Page且不使用任何显示挂钩或事件签名,为特定的生命周期阶段定义简单的事件处理程序。如果启用了事件自动连接,则为true;否则为false。默认值为true。
● ClassName:指定自动从标记生成并在处理母版页时自动进行编译的类的类名。此值可以是任何有效的类名,并且可以包括命名空间。
例如,下面的母版页指令包括一个隐藏代码文件的名称,并将一个类名称分配给母版页。
上述代码声明一个@Master指令,设置程序语言为C#,设置CodeFile属性为隐藏代码文件的名称,设置Inherits属性指定类名。
除了@Master指令外,母版页还包含页的所有顶级HTML元素,如html、head和form。可以在母版页中使用任何HTML元素和ASP.NET元素。
除了在所有页上显示的静态文本和控件外,母版页还包括一个或多个ContentPlaceHolder控件。ContentPlaceHolder控件称为“占位符控件”,这些占位符控件定义可替换内容出现的区域。
可替换内容是在内容页中定义的,所谓内容页就是绑定到特定母版页的ASP.NET页(.aspx文件以及可选的代码隐藏文件),通过创建各个内容页来定义母版页的占位符控件的内容,从而实现页面的内容设计。
在内容页的@Page指令中通过使用MasterPageFile属性来指向要使用的母版页,从而建立内容页和母版页的绑定。例如,一个内容页可能包含@Page指令,该指令将该内容页绑定到Master.master页,代码如下:
上述代码声明一个@Page指令,设置程序语言为C#,设置母版页文件为MasterPage. master,设置启用事件自动连接。
在内容页的@Page指令中通过使用MasterPageFile属性来指向要使用的母版页,从而建立内容页和母版页的绑定。例如,一个内容页可能包含“@Page”指令,该指令将该内容页绑定到Master.master页,在内容页中,通过添加Content控件并将这些控件映射到母版页上的ContentPlaceHolder控件来创建内容,代码如下:
上面的代码中第1行代码在Page指令中设置了属性MasterPageFile为Master.master,表示该页面母版页为Master.master,第2行声明Content控件并将这些控件映射到母版页上的ContentPlaceHolder控件。
创建Content控件后,就可以开始向这些控件添加文本和控件。在内容页中,Content控件外的任何内容(除服务器代码的脚本外)都将导致错误。在ASP.NET页中执行的所有任务都可以在内容页中执行。在母版页中为ContentPlaceHolder控件的区域在新的内容页显示为Content控件。
母版页提供了开发人员以传统的方式重复复制现有的代码、文本和控件元素,使用框架集,对通用元素使用包含文件和用户控件。
母版页具有下面的优点:
● 使用母版页可以集中处理页的通用功能,以便可以只在一个位置上进行更新。
● 使用母版页可以方便地创建一组控件和代码,并将结果应用于一组页。例如,可以在母版页上使用控件来创建一个应用于所有页的菜单。
● 通过允许控制占位符控件的呈现方式,母版页可以在细节上控制最终页的布局。
母版页提供一个对象模型,使用该对象模型可以从各个内容页自定义母版页。
10.1.2 母版页的运行机制
单独的母版页是不能被用户所访问的。没有内容页的支持,母版页仅仅是一个页面模板,没有更多的实用价值。同样道理,单独的内容页没有母版页支持,也不能够应用。由此可见,母版页与内容页关系密切,是不可分割的两个部分。只有同时正确创建和使用母版页及内容页,才能发挥它们强大的功能。这一点,无论从代码结构,还是运行机制等方面都可以得到有力印证。
1.代码结构
从代码结构方面来说,母版页内容以页面公共部分为主,包括代码头、ContentPlaceHolder控件及其他常见Web元素。内容页则主要包含页面非公共部分,包括代码头和Content控件,Content控件中包含着页面非公共内容。
在控件应用方面,母版页和内容页有着严格对应关系。母版页中包含多少个ContentPlaceHolder控件,那么内容页中也必须设置与其相同数目的Content控件,而且Content控件的属性ContentPlaceHolderlD的设置必须与母版页中设置的相互对应。可以把母版页的ContentPlaeeHolder控件看做是页面中的占位符,那么占位符所对应的具体内容就包含在内容页的Content控件中。两者的对应关系是通过设置Content控件中的ContentPlaceHolderlD属性来完成的。
在实际应用中,为了给整个网站创建一致的风格和样式,一个母版页可能被多个内容页绑定。只有正确处理母版页与内容页之间的控件对应关系,才能够准确、高效地创建Web应用程序。
2.运行过程
当客户端浏览器向服务器发出请求,要求浏览页面时,ASP.NET执行引擎将执行内容页和母版页的代码,并将最终结果发送给客户端浏览器。
母版页和内容页的运行过程可以概括为以下5个步骤:
● 用户通过键入内容页的URL来请求某页。
● 获取该页后,读取@Page指令。如果该指令引用一个母版页,则也读取该母版页。如果这是第一次请求这两个页,则两个页都要进行编译。
● 包含更新内容的母版页合并到内容页的控件树中。
● 各个Content控件的内容合并到母版页中相应的ContentPlaceHolder控件中。
● 浏览器中呈现得到的合并页。
整个过程具有很强的逻辑性,并且母版页和内容页配合得非常巧妙。从用户角度来看,合并后的母版页和内容页是一个完整的页面,并且其URL访问路径与内容页的路径相同。从开发人员角度来看,控件的巧妙应用和配合,是实现的关键。注意,在运行时,母版页成为了内容页的一部分。实际上,母版页与用户控件的作用方式大致相同,既作为内容页的一个子级,又作为该页中的一个容器。然而,当前母版页是所有呈现到浏览器中的服务器控件的容器。
3.事件顺序
通常情况下,母版页和内容页中的事件顺序对于页面开发人员并不重要。但是,如果所创建的事件处理程序取决于某些事件的可用性,那么了解母版页和内容页中的事件顺序很有帮助。在这里将对母版页和内容页的事件顺序进行简要说明,以便加深读者对母版页和内容页的理解。
当访问结果页时,实际访问的是内容页和母版页。作为有着密切关系的两个页面,两者都要执行各自的初始化和加载等事件。
加载母版页和内容页共需要经过8个过程。这8个过程显示初始化和加载母版页及内容页是一个相互交叠的过程。基本过程是:初始化母版页和内容页控件树;然后,初始化母页和内容页页面,接着,加载母版页和内容页;最后,加载母版页和内容页控件树。
以上8个过程对应着11个具体事件:
● 母版页中控件Init事件;
● 内容页中Content控件Init事件;
● 母版页Init事件;
● 内容页Init事件:
● 内容页Load事件:
● 母版页Load事件;
● 内容页中Content控件Load事件:
● 内容页PreRender事件:
● 母版页PreRender事件:
● 母版页控件PreRender事件;
● 内容页中Content控件PreRender事件。
实际上,8个过程或者是11个事件都用于说明母版页和内容页中的具体事件顺序。内容页和母版页中会引发相同的事件。例如,两者都引发Init、Load和PreRender事件。引发事件的一般规律是,初始化Init事件从最里面的控件(母版页)向最外面的控件(Conetent控件及内容页)引发,所有其他事件则从最外面的控件向最里面的控件引发。需要牢记,母版页会合并到内容页中,并被视为内容页中的一个控件,这一点十分有用。
创建应用程序中,必须注意以上事件顺序。例如,当在内容页中访问母版页的属性或者服务器控件时,如果按照过去的处理思路,可能会在内容页的Page Load事件处理程序中加以实现。由前文可知,在母版页Load事件引发之前,内容页Load事件已经引发,那么过去的思路显然是不正确的。
10.1.3 创建母版页
母版页中包含的是页面公共部分,即网页模板。因此,在创建示例之前,必须判断哪些内容是页面公共部分,这就需要从分析页面结构开始,页面结构如图10-1所示。
图10-1 页面结构图
图10-1中的页面由4个部分组成:页头、页尾、内容1和内容2。其中页头和页尾是所在网站中页面的公共部分,网站中许多页面都包含相同的页头和页尾。内容1和内容2是页面的非公共部分,是页面所独有的。结合母版页和内容页的有关知识可知,如果使用母版页和内容页来创建页面,那么必须创建一个母版页MasterPage.master和一个内容页。其中母版页包含页头和页尾等内容,内容页中则包含内容1和内容2。
【实例10-1】母版页的创建
本实例演示如何在ASP.NET Web应用程序中创建一个母版页,具体实现步骤如下。
01 启动Visual Studio 2012,创建一个ASP.NET Web空应用程序,命名为“实例10-1”。
02 在“实例10-1”中创建一个名为Images的文件夹,其中包含页头背景图片文件head.JPG。
03 用鼠标右键单击网站名,在的菜单中选择“添加”|“添加新项”命令,在弹出如图10-2的“添加新项”对话框中选择“已安装模板”下的“Visual C#”模板,并在模板文件列表中选中“母版页”选项,然后在“名称”文本框输入该文件的名称MasterPage.master,最后单击“添加”按钮。
图10-2 添加新项对话框
04 此时在网站根目录下会自动生成一个如图10-3所示MasterPage.master文件和一个MasterPage.master.cs文件,前者是母版页页面设计文件,后者是后台代码编辑文件。
图10-3 生成母版页文件
05 单击打开MasterPage.master文件,编写代码如下:
上面的代码中第1行声明一个“@Master”指令。第10行通过一个Table元素构成整个页面结构。第11行~第14行定义表格的第1行第1列构成了页面中的页头,其中,第12行设置图片"Images/head.jpg"作为页头的背景图片。
第15行~第32行定义表格的第2行,这一行中嵌套了一个从第17行~第30行的Table元素,将这一行又分成了两个列。其中,第19行~第23行构成了第一个列,在第20行~第22行声明了控件ContentPlaceHolder1,用于在页面模板中为内容1占位。第24行~第28行构成了第二个列,在第25行~第27行声明了控件ContentPlaceHolder2,用于在页面模板中为内容2占位。
第33行~第36行构成了页面中的页尾,显示一个网站的版本信息。
纵观整个代码,可以发现MasterPage页面与普通页面还是存在着一定的差异。差异主要有:
(1)第1行代码不同,母版页使用的是Master,而普通.aspx文件使用的是Page。除此之外,二者在代码头方面是相同的。
(2)是母版页中声明了控件ContentPlaceHolder,而在普通“.aspx”文件中是不允许使用该控件的。在MasterPage.master的源代码中,ContentPlaceHolder控件本身并不包含具体内容设置,仅是一个控件声明。
06 切换到“源视图”,母版页的设计界面如图10-4所示。图中的两个矩形框表示ContentPlaceHolder控件。开发人员可以直接在矩形框中添加内容,所设置内容的代码将包含在ContentPlaceHolder控件声明代码中。
图10-4 设计视图
使用Vistual Studio 2012可以对母版页进行编辑,并且它完全支持“所见即所得”功能。无论是在代码模式下,还是设计模式下,使用Vistual Studio 2012编辑母版页的方法与编辑普通.aspx文件都是相同的。
10.1.4 创建内容页
内容页用来定义母版页占位符控件ContentPlaceHolder的内容,为绑定到特定页母版页的ASP.NET页。通过包含指向要使用的母版页的MastPageFile属性,在内容页的@Page指令中建立绑定。
内容页的创建有两种方法:第一种是在母版页中放入新建的内容页,第二种是在母版页放入已经存在的内容页。
【实例10-2】创建内容页
本实例演示如何实现在母版页中放入新建的内容页面,具体实现步骤如下。
01 在上面“实例10-1”中创建了一个母版页,本例在此基础上进行操作。用鼠标右键单击网站名称,在弹出快捷菜单中选择“添加”|“添加新项”命令,弹出如图10-5所示的“添加新项”对话框。
图10-5 “添加新项”对话框
02 选择“已安装”模板下的“Visual C#”模板,并在模板文件列表中选中“Web窗体”,然后在“名称”文本框输入该文件的名称Default.aspx,最重要的是选中“选择母版页”复选按钮,最后单击“添加”按钮,弹出10-5所示的“选择母版页”对话框。
03 选择“文件夹内容”列表中“实例10-1”创建的母版页文件MasterPage.master,单击“确定”按钮,新创建的内容页就放入母版页中,如图10-6所示。
图10-6 “选择母版页”对话框
04 单击Default.aspx文件,编写代码如下:
上面的代码中第1行声明“@Page”指令,其中MasterPageFile属性表示该页面是继承自母版页MasterPage.master。第2行通过添加一个Content控件Content1,并将这些控件映射到母版页上的ContentPlaceHolder1控件来创建内容1。在内容页面上,不比指定内容的位置,因为在母版页中定义了。所以只需要将适当的内容分在所提供的内容区域上。第3行~第9行就是添加要显示的内容,定义了7个服务器链接按钮控件,分别显示导航列表,即在母版页内容1中要显示的内容。第10行同样添加一个Content控件Content2,并将控件映射到母版页上的ContentPlaceHolder2控件来创建内容2。第11行定义一个服务器标签控件来显示欢迎进入网站后台的信息,即在母版页内容2中要显示的内容。在这个页面的代码中没有发现任何的HTML标记,是因为它们都被包含在了Master.master页面中了。
05 按快捷键Ctrl+F5运行程序,运行结果如图10-7所示。
图10-7 运行结果
另外一种内容页创建的方法是在母版页放入已经存在的内容页,通过手工加入或修改一些代码来使存在的网页嵌入到母版页中,步骤如下:
01 进入已经存在的内容页的“源视图”,在页面指示语句中增加与母版页相关联的属性,如下代码:
上面代码中的关键是MasterPageFile属性的设置,它是与母版页相关联的属性,只需将其值设置为相应的母版页文件所在路径即可。
02 删除原来代码中的HTML标记,如<html>、<head>、<body>、<form>等,因为母版页中已经存在相同的标记,删除它们以避免重复。
03 增加<Content>标记,并添加相应的属性,也就是编写“例10-2”中Default.aspx代码中第2行~第11行的内容。
10.1.5 访问母版页控件和属性
可以在内容页中编写代码来引用母版页中的属性、方法和控件,但这种引用有一定的限制。对于属性和方法的规则是:如果它们在母版页上被声明为公共成员,则可以引用它们。这包括公共属性和公共方法。在引用母版页上的控件时,没有只能引用公共成员的这种限制。
1.使用FindFindControl方法
在运行时,母版页与内容页合并,因此内容页的代码可以访问母版页上的控件。这些控件是受保护的,因此不能作为母版页成员直接访问。但是,可以使用FindControl方法定位母版页上的特定控件。如果要访问的控件位于母版页的ContentPlaceHolder控件内部,必须首先获取对ContentPlaceHolder控件的引用,然后调用其FindControl方法获取对该控件的引用。
例如,在母版页面上有一个ID为Labell的标签控件,在内容页中有一个ID为LabelTitle的标签控件,在页面加载事件中,让内容页的控件LabelTitle获取母版页控件Label1的Text,代码如下:
上面的代码中直接通过Master获取ContentPlaceHolder外面的控件,然后在LabelText控件中显示被获取的控件的内容。
当然,这种交互打破了基于类设计和封装的原则。如果确实需要访问母版页的控件,最好在母版页里通过属性封装控件(或者,更理想的是只暴露感兴趣的属性)。这样,母版页和内容页的交互更加清晰、更为文档化,耦合也更松散。如果内容页修改其他页面的内部逻辑,逻辑母版页时很可能破坏这种依赖关系,从而产生脆弱的代码模型。
2.使用MasterType指令
为了提供对母版页成员的访问,Page类公开了Master属性。如果要从内容页访问特定母版页的成员,可以通过创建@MasterType指令指定.master文件的虚拟路径,指向一个特定的母版页。当该内容页创建自己的Master属性时,属性的类型被设置为引用的母版页。
下面是添加了MasterType指令的某个内容页的前两行代码:
MasterType页面指令允许对Master页面进行强类型化引用,通过Master对象访问Master页面的属性。
【实例10-3】访问母版页
本实例以例10-2为基础,演示如何从内容页来访问获得母版页中的属性,具体实现步骤如下:
01 在“实例10-2”中,单击MasterPage.master,进入“源视图”,在页面首部添加一个ID为LabelM的Label控件,设置其Text属性为“我是属于母版页的!”,代码如下:
02 单击MasterPage.master.cs,编写代码如下:
上面的代码中,第1行~第8行创建母版页的属性TitleText,其中使用get和set属性访问器赋值和取值。
03 单击内容页Default.aspx,进入源视图,在“@page”指令下,添加“@MasterType”指令,代码如下:
04 单击Default.aspx.cs文件,编写代码如下:
上面的代码通过MasterL类来访问上面创建的TitleText属性并赋值。
05 按快捷键Ctrl+F5运行程序,运行结果如图10-8所示。页面显示了在内容页上赋值的文本内容,而不是原来母版页中添加Label控件上设置的文本内容。
图10-8 运行结果
10.1.6 母版页的嵌套
母版页可以嵌套,让一个母版页引用另外的页作为其母版页。利用嵌套的母版页可以创建组件化的母版页。例如,大型站点可能包含一个用于定义站点外观的总体母版页。然后,不同的站点内容合作伙伴又可以定义各自的子母版页,这些子母版页引用站点母版页,并相应定义该合作伙伴的内容的外观。
与任何母版页一样,子母版页也包含文件扩展名.master。子母版页通常会包含一些内容控件,这些控件将映射到父母版页上的内容占位符。就这方面而言,子母版页的布局方式与所有内容页类似。但是,子母版页还有自已的内容占位符,可用于显示其子页提供的内容。
【实例10-4】母版页的嵌套
本实例演示如何在一个ASP.NET Web应用程序中使用嵌套的母版页,具体实现步骤如下:
01 启动Visual Studio 2012,创建一个ASP.NET Web空应用程序,命名为“实例10-4”。
02 在“实例10-4”中创建一个名为FatherMaster.master母版页,单击该母版页,进入“源视图”,在<form>和</form>标记之间编写如下主要代码:
上面的代码中第2行设置一个字符串,表示这个位置是父母版页,第3行在占位符之前输出“这是父母版页”字符串,第4行设置母版页的占位符。
03 用鼠标右键单击网站名,在弹出的菜单中选择“添加”|“添加新项”命令,在弹出如图10-9的“添加新项”对话框中选择“已安装”模板下的“Visual C#”模板,并在模板文件列表中选中“母版页”,然后在“名称”文本框输入该文件的名称ChildMaster.master,注意最重要的是选中“选择母版页”复选框,最后单击“添加”按钮,弹出“选择母版页”对话框。
图10-9 “添加新项”对话框
04 选择“文件夹内容”列表中刚创建的母版页文件FatherMaster.master。单击“确定”按钮,新创建的子母版页就放入父母版页中。
05 单击ChildMaster.master文件,编写代码如下:
上面的代码中第1行~第14行定义了子母版页占位符的内容,其中第6行和第10行分别定义了一个占位符,由此可见,子母版页既有母版页的特征又有内容页的特征。
06 在“实例10-4”中创建一个名为Default的内容页,引用子母版页ChildMaster.master,在该页面中编写如下代码:
上面的代码中第1行~第4行以及第5行~第8行分别定义了母版页中两个占位符的内容。
07 按快捷键Ctrl+F5运行程序,运行结果如图10-10所示。
图10-10 运行结果
10.1.7 动态加载母版页
在开发过程中,简单的实现内容页仅绑定一个固定的母版页是远远不够的,往往需要动态加载母版页。例如,要求站点提供多个可供选择的页面模板,并允许动态加载这些模板。
实现动态加载母版页的核心是设置MasterPageFile属性值,需要强调的是应将该属性设置在Page_PreInit事件处理程序中,因为Page_PreInit事件是页面生命周期中较先引发的事件,如果试图在Page_Load事件中设置MasterPageFile属性将会发生页面异常。
MasterPageFile属性用于获取或设置包含当前内容母版页的名称。其语法格式如下:
MasterPageFile属性值是当前母版页的父级母版页的名称;如果当前母版页没有父级,则为空引用。
PreInit事件在页初始化开始时发生,其语法格式如下:
PreInit事件是在页生命周期的早期阶段中可以访问的事件。在PreInit事件后,将加载个性化信息和页主题。
因为母版页和内容页会在页处理的初始化阶段合并,所以必须在此分配母版页。通常在PreInit事件阶段动态地分配母版页。例如:
如果内容页使用@MasterType指令将一个强类型赋给了母版页,该类型必须适用于动态分配的所有母版页。如果要动态地选择一个母版页,可以创建一个基类,并从此基类派生母版页,此类定义母版页共有的属性和方法。在内容页中,当使用@MasterType指令将一个强类型赋给母版页时,可以将类型赋给该基类而不是单个母版页。
【实例10-5】动态加载母版页
本实例演示如何在ASP.NET Web应用程序中动态加载母版页,具体实现步骤如下所示:
01 启动Visual Studio 2012,创建一个ASP.NET Web空应用程序,命名为“实例10-5”。
02 在“实例10-5”中添加一个名为“App_Code”的文件夹,并在创建的名为BaseMaster.cs类文件内编写代码如下:
上面的代码中第1行定义一个基类,该类派生自MasterPage,这是基母版页类型,第2行到第4行定义一个虚属性,通过get访问器获得字符串,该属性必须在子类中被重写。
03 在“实例10-5”中创建一个名为MasterPage1.master母版页,单击该母版页,进入“源视图”,在<form>和</form>标记之间编写如下主要代码:
上面的代码中第4行~第6行添加了一个服务器占位符控件,并在控件中显示文本。
04 单击网站目录中的MasterPage1.master.cs文件,编写代码如下:
上面的代码中第1行定义重新基类BaseMaster的属性MyTitle。
05 在“实例10-5”中创建一个名为MasterPage2.master母版页,单击该母版页,进入“源视图”,在<form>和</form>标记之间编写如下主要代码:
上面的代码中第4行~第6行添加了一个服务器占位符控件,并在控件中显示文本。
06 单击网站目录中的MasterPage2.master.cs文件,编写代码如下:
上面的代码中第1行重新定义基类BaseMaster的属性MyTitle。
07 在“实例10-5”中添加一个名为Default.aspx的窗体,单击该窗体,进入“源视图”,编写如下主要代码:
上面的代码中最重要的是第2行通过@MasterType指令设置TypeNamea属性为BaseMaster基类。
08 单击网站目录中的MasterPage2.master.cs文件,编写代码如下:
上面的代码中第1行~第10行处理了页面的PreInit事件,也就是页面加载之前的事件。在该事件处理程序中,第2行判断如果通过URL地址传递的属性type的值是Game的话,则第3行加载MasterPage2.master母版页,否则,加载MasterPage1.master母版页。
09 按快捷键Ctrl+F5运行程序,如图10-11所示显示加载“音乐天堂”的母版页。如果用户在浏览器地址栏输入的URL为http://localhost:51994/Default.aspx?type=Game,则加载如图10-12所示的“游戏世界”母版页。
图10-11 运行结果1
图10-12 运行结果2