当我们第一眼见到CS的时候你是不是被他那么纷繁复杂的结构看傻眼呢。那么在认识CS之前最好对它的页面间关系做一个全面的了解,这对我门改造CS有较大的帮助。
首先我们对整体一个简单的了解,如图,此为在IDE中打开的项目列表:
其中CommunityServerWeb项目为IIS运行的WEB项目,项目的UI层相关的都放在此处。CommunityServerComponents和CommunityServerControls都是支持整个系统运行所必须的组件,其中CommunityServerControls项目里有大量的系统公用的控件,由于本系统的几乎所有页面都是由用户控件组合而成的所以需要先了解在CS中用户控件的分布机制。
CS用户控件:我们知道,用户控件一般以.ascx为扩展名,在建立时都自带了相应的.cs文件,而在CS中考虑到明晰的分层结构和方便的修改UI以及最大程度的换肤功能,程序使用了后台代码与前台UI分开在两个层中,拿常用的CommunityServerControls这个项目来说在项目里我们看不到ascx文件的踪影,原来ascx文件保存在UI层,并且按照各种风格定义了不一样的ascx文件,具体的路径可以参照下图的导航找到:
我们可以看到,在UI层的Themes文件夹里保存了所有在其他层实现的用户控件的ascx文件,我想有必要介绍一下此目录的结构。
如上图,default文件夹里保存了系统平台使用的默认风格的UI文件,其他文件夹为相应的其他风格的文件集合。当然在此Blogs和Galleries两个文件夹分别是针对博客的皮肤和相册的皮肤,因为这两个项目需要根据具体用户的需要单独制定样式,和网站整体的样式不相关所以单独保存在平行的文件夹里。让我们再看看default文件夹里有些什么,首先是images这个不用说,就是保存了此种风格需要的图片资源,Masters文件夹里保存了页面的整体风格的相关框架,这个在后面详细描述,Skin文件夹里保存的既是大量的用户控件的UI实现,其中命名规则为:skin-ClassName.ascx 其中ClassName为相对应的类名,注意文件名一定要按照这个规则来命名,程序运行的时候,后台的是根据这个名称来找到相应的UI层文件的,这样隔离了依赖关系,前台不用被后台代码束缚了,此种模式我们把它叫着模板用户控件,因为UI层的职责已经很明确了那就是提供控件展现的模板供后台代码调用。
可以这样来看页面执行过程:用户请求aspx的页面à程序查找页面中的用户控件à实例话用户控件à实例化时根据配置找到相应风格相应名称的ascx文件一并实例化à生成关联后的控件对象返回给aspx页面。这样看来,虽然代码分离了,达到的效果却没有变。
问题出现了,后台代码和UI层的同步问题,我们在平时建立用户控件或aspx文件的时候IDE自动为我们生成了很多代码,使得我们不需要考虑前台的控件和后台代码的对应问题,而这里代码和UI是分开的,涉及到增加删减控件不同步的问题。要达到用户在UI层删除一个元素之后页面也不会出错,用户随时需要了可以随时修改回去,在CS中是这样处理的,拿登陆的用户控件来看(\src\Controls\Login.cs&\src\Web\Themes\default\Skins\Skin-Login.ascx)
如图,这是Login在CommunityServerControls项目里的继承关系,我们的重点应该放在TemplatedWebControl这个类里,此类是所有通过模板方式建立用户控件的基类,在这里包含了处理模板相关的功能,比如命名规则自动找到相应的ascx文件等等,这里注意一下这个的方法:protected abstract void AttachChildControls();可以看出此方法为抽象的,而且整个类里也就只有这么一个抽象方法,由继承关系我们知道在Login类里必须实现此方法,此方法的作用是干什么的呢,让我们看看Login类里这个方法都干了些什么:
login.cs
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
--> headhead
namespace CommunityServer.Controls
{
[
ParseChildren(true)
]
public class Login : SecureTemplatedWebControl {
CSContext csContext = CSContext.Current;
TextBox username;
TextBox password;
IButton loginButton;
CheckBox autoLogin;
Other CodeOther Code
protected override void AttachChildControls()
{
// Find the username control
username = (TextBox) FindControl("username");
// Find the password control
password = (TextBox) FindControl("password");
// Find the login button
loginButton = ButtonManager.Create(FindControl("loginButton"));
loginButton.Click += new EventHandler(LoginButton_Click);
loginButton.Text = ResourceManager.GetString("LoginSmall_Button");
// Find the autologin checkbox
autoLogin = (CheckBox) FindControl("autoLogin");
autoLogin.Text = ResourceManager.GetString("LoginSmall_AutoLogin");
//autoLogin.AutoPostBack = true;
//autoLogin.CheckedChanged += new EventHandler( AutoLogin_OnCheckedChanged );
// Restore autologin status from the auto login cookie
AutoLoginCookie alCookie = new AutoLoginCookie();
autoLogin.Checked = alCookie.GetValue();
RegisterSetFocusScript();
}
Other CodesOther Codes
}
}
看了这个我们应该明白了吧,此方法就是我们手动建立相关控件的关联,使用FindControl("controlname")方法我们就可以找到模板的相应控件所以在定制模板的时候模板里的控件的ID一定要和此处一一对应即可。
你一定会想,这样一一对应后每每修改前台模板内的控件后不是都要到相应的后台代码里修改相应的代码,不错,是这样,不过还是有相应的对策来弥补这种不足,那就是在后台尽量把前台需要的功能和代码考虑全,这样在前台如果需要去掉某个控件后台的代码也不需要改变,这里后台代码就应该这样写了:
TextBox name = FindControl("username") as TextBox;
if(name != null)
{
//处理代码
}
这里可以看出,第一句使用了as语句,作用为把找到的对象转换为TextBox类型,如果没找到或类型转换失败也不引发异常而是将NULL付给变量,这样我们在代码里只需要加多判断引用是否为NULL即可按照我们的想法处理相应逻辑了。
怕写太多让人没耐心,故分成几篇来分析,后面将介绍在CS中的模板处理机制。
CS中几乎所有的页面都是靠模板的呈现方式来实现的,那么CS中有些什么样的模板呢,让我们看看CS中的模板机制吧:
在我们使用IDE打开Default.aspx页面时,我们仅仅看到一些毫无顺序的控件,几乎看不到控制布局的Html标签,不要困惑,因为在CS中UI层也是按照模板思想分层实现的。先来看看几个类的实现:
打开\src\Controls\ContentContainer.cs
这个文件里分别实现了
public class MPContainer : MetaBuilders.WebControls.MasterPages.ContentContainer{…}
public class MPRegion : MetaBuilders.WebControls.MasterPages.Region{}
public class MPContent : MetaBuilders.WebControls.MasterPages.Content{}
public class MPForm : MetaBuilders.WebControls.MasterPages.NoBugForm {}
public class MPScript : System.Web.UI.WebControls.PlaceHolder {}
这些类前四个都继承自MetaBuilders.WebControls.MasterPages,那么为什么CS不直接使用这些控件呢,因为这是第三方控件,主动权不在CS中,CS为了隔离控件变动使用了代理模式,这样就可以在程序里使用统一的方法,不用担心以后第三方控件的变动了,同样,在CS系统里还大量运用了此模式,比如在使用FreeTextBox的时候不是直接使用,而是通过Telligent.FreeTextBoxWraper这个类进行封装隔离,并且同时继承自ITextEditor接口,这样程序里面在需要用到文本编辑器的时候只需要引入此接口根据配置加载文本编辑器包装类就可以了,这样处理之后撤卸和安装新扩展都会很容易,如果对此模式还不甚了解的话,我会在后面章节介绍这里的原理。
好了,了解了这么几个控件类之后就看我们怎样在程序里面使用了,首先简单介绍一下这几个控件类的作用吧:
MPRegion:向页面进行注册的控件,这有点类似于那些大型的新闻静态页面发布系统的标识符,生成静态页面的时候根据标识符替换相应的动态数据。
MPForm :作用很简单,就是为客户端生成Form标签。
MPContainer :包容MPContent控件的容器控件。
MPContent:此控件的ID只要和在Master页面设置的MPRegion控件的ID一致,那么此控件中的内容便自动嵌入到MPRegion控件的位置了,此控件必须用在MPContainer之内。
简单介绍了这些控件之后如果你是初次接触CS那么肯定还会很迷糊,别急,让我们先了解在CS中模板的“继承”关系,虽然不是严格的继承但我们可以这样理解,让我们打开具体的风格文件夹在这里我们看看在\src\Web\Themes\default\文件夹下的Masters文件夹,此文件夹下大部分都是以Master命名的ascx文件不难从名字可以看出,最主要的文件当然是Master.ascx了,这是所有页面都需要引用的主模板,根据继承的概念我们应该清楚在此文件里应该放一些每个页面都需要用到的Html标签等等,比如页头,公用样式,页面的布局页角以及Form标签等等,如下面的代码:
Master.ascx
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
--><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<CS:MPRegion id="HeaderRegion" runat="server" >
<CS:Head runat="Server">
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<CS:Style id="UserStyle" runat="server" visible = "true" />
<CS:Style id="s2" runat="server" visible = "true" Href="../style/Common.css" />
<CS:Style runat="server" Href="../style/common_print.css" media="print" />
<CS:Script id="s" runat="server" />
</CS:Head>
</CS:MPRegion>
<body>
<CS:MPForm runat="server">
<CS:MPRegion id="bscr" runat="server" />
<div id="Common">
<div id="CommonHeader">
<CS:MPRegion id="bhcr" runat="server" >
<CS:TitleBar runat="server" id="t" />
</CS:MPRegion>
</div>
<div id="CommonBody">
<table cellspacing="0" cellpadding="0" border="0" width="100%" id="CommonBodyTable">
<tr>
<td valign="top" id="CommonLeftColumn">
<CS:MPRegion id="lcr" runat="server" />
</td>
<td valign="top" width="100%" id="CommonBodyColumn">
<CS:MPRegion id="bcr" runat="server" />
</td>
<td valign="top" id="CommonRightColumn">
<CS:MPRegion id="rcr" runat="server" />
</td>
</tr>
</table>
</div>
<div id="CommonFooter">
<CS:MPRegion id="BodyFooterRegion" runat="server" >
<CS:Footer runat="server" id="Footer1"/>
</CS:MPRegion>
</div>
</div>
</CS:MPRegion>
</CS:MPForm>
</body>
</html>
从代码中我们可以看出MPRegion和MPForm这两个元素,也就是告诉“继承”自这个页面的控件什么地方应该嵌入什么。比如<CS:MPRegion id="HeaderRegion" runat="server" >这个标签表示如果子页面有id为HeaderRegion的MPContent这个控件对象的时候,那么此控件中的内容将插入到此处替换调默认的内容,如果子页面不存在此ID的MPContent的话则使用此标签内的内容。
其他文件,比如HomeMaster.ascx文件表示首页的模板,此模板的基本模板还是使用Master.ascx这就好比类的继承关系,Master是基类,HomeMaster是首页的基类,default.aspx则是继承自HomeMaster的子类,只是需要在首页的MPContainer标签处标明使用哪一个模板即可,如<CS:MPContainer runat="server" id="Mpcontainer1" ThemeMasterFile = "HomeMaster.ascx" >一个好处就是尽量减少了重复的公用HTML标签,另外一个好处是页面可以分层,比如在设计一个较复杂的首页时,我们可以把大量的复杂的Html标签放在HomeMaster只在需要插入动态内容的地方注册MPRegion标签,这样做后我们只需要在首页里使用MPContent包含相应的动态内容而不需要被复制的Html标签搞昏了头。这就是为什么我们初次打开首页几乎看不到控制首页格式的HTML标签的原因。同理,其他模块也是如此这般,这里就不冗诉了。
CS中几乎所有的页面都是靠模板的呈现方式来实现的,那么CS中有些什么样的模板呢,让我们看看CS中的模板机制吧:
在我们使用IDE打开Default.aspx页面时,我们仅仅看到一些毫无顺序的控件,几乎看不到控制布局的Html标签,不要困惑,因为在CS中UI层也是按照模板思想分层实现的。先来看看几个类的实现:
打开\src\Controls\ContentContainer.cs
这个文件里分别实现了
public class MPContainer : MetaBuilders.WebControls.MasterPages.ContentContainer{…}
public class MPRegion : MetaBuilders.WebControls.MasterPages.Region{}
public class MPContent : MetaBuilders.WebControls.MasterPages.Content{}
public class MPForm : MetaBuilders.WebControls.MasterPages.NoBugForm {}
public class MPScript : System.Web.UI.WebControls.PlaceHolder {}
这些类前四个都继承自MetaBuilders.WebControls.MasterPages,那么为什么CS不直接使用这些控件呢,因为这是第三方控件,主动权不在CS中,CS为了隔离控件变动使用了代理模式,这样就可以在程序里使用统一的方法,不用担心以后第三方控件的变动了,同样,在CS系统里还大量运用了此模式,比如在使用FreeTextBox的时候不是直接使用,而是通过Telligent.FreeTextBoxWraper这个类进行封装隔离,并且同时继承自ITextEditor接口,这样程序里面在需要用到文本编辑器的时候只需要引入此接口根据配置加载文本编辑器包装类就可以了,这样处理之后撤卸和安装新扩展都会很容易,如果对此模式还不甚了解的话,我会在后面章节介绍这里的原理。
好了,了解了这么几个控件类之后就看我们怎样在程序里面使用了,首先简单介绍一下这几个控件类的作用吧:
MPRegion:向页面进行注册的控件,这有点类似于那些大型的新闻静态页面发布系统的标识符,生成静态页面的时候根据标识符替换相应的动态数据。
MPForm :作用很简单,就是为客户端生成Form标签。
MPContainer :包容MPContent控件的容器控件。
MPContent:此控件的ID只要和在Master页面设置的MPRegion控件的ID一致,那么此控件中的内容便自动嵌入到MPRegion控件的位置了,此控件必须用在MPContainer之内。
简单介绍了这些控件之后如果你是初次接触CS那么肯定还会很迷糊,别急,让我们先了解在CS中模板的“继承”关系,虽然不是严格的继承但我们可以这样理解,让我们打开具体的风格文件夹在这里我们看看在\src\Web\Themes\default\文件夹下的Masters文件夹,此文件夹下大部分都是以Master命名的ascx文件不难从名字可以看出,最主要的文件当然是Master.ascx了,这是所有页面都需要引用的主模板,根据继承的概念我们应该清楚在此文件里应该放一些每个页面都需要用到的Html标签等等,比如页头,公用样式,页面的布局页角以及Form标签等等,如下面的代码:
Master.ascx
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<CS:MPRegion id="HeaderRegion" runat="server" >
<CS:Head runat="Server">
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<CS:Style id="UserStyle" runat="server" visible = "true" />
<CS:Style id="s2" runat="server" visible = "true" Href="../style/Common.css" />
<CS:Style runat="server" Href="../style/common_print.css" media="print" />
<CS:Script id="s" runat="server" />
</CS:Head>
</CS:MPRegion>
<body>
<CS:MPForm runat="server">
<CS:MPRegion id="bscr" runat="server" />
<div id="Common">
<div id="CommonHeader">
<CS:MPRegion id="bhcr" runat="server" >
<CS:TitleBar runat="server" id="t" />
</CS:MPRegion>
</div>
<div id="CommonBody">
<table cellspacing="0" cellpadding="0" border="0" width="100%" id="CommonBodyTable">
<tr>
<td valign="top" id="CommonLeftColumn">
<CS:MPRegion id="lcr" runat="server" />
</td>
<td valign="top" width="100%" id="CommonBodyColumn">
<CS:MPRegion id="bcr" runat="server" />
</td>
<td valign="top" id="CommonRightColumn">
<CS:MPRegion id="rcr" runat="server" />
</td>
</tr>
</table>
</div>
<div id="CommonFooter">
<CS:MPRegion id="BodyFooterRegion" runat="server" >
<CS:Footer runat="server" id="Footer1"/>
</CS:MPRegion>
</div>
</div>
</CS:MPRegion>
</CS:MPForm>
</body>
</html>
从代码中我们可以看出MPRegion和MPForm这两个元素,也就是告诉“继承”自这个页面的控件什么地方应该嵌入什么。比如<CS:MPRegion id="HeaderRegion" runat="server" >这个标签表示如果子页面有id为HeaderRegion的MPContent这个控件对象的时候,那么此控件中的内容将插入到此处替换调默认的内容,如果子页面不存在此ID的MPContent的话则使用此标签内的内容。
其他文件,比如HomeMaster.ascx文件表示首页的模板,此模板的基本模板还是使用Master.ascx这就好比类的继承关系,Master是基类,HomeMaster是首页的基类,default.aspx则是继承自HomeMaster的子类,只是需要在首页的MPContainer标签处标明使用哪一个模板即可,如<CS:MPContainer runat="server" id="Mpcontainer1" ThemeMasterFile = "HomeMaster.ascx" >一个好处就是尽量减少了重复的公用HTML标签,另外一个好处是页面可以分层,比如在设计一个较复杂的首页时,我们可以把大量的复杂的Html标签放在HomeMaster只在需要插入动态内容的地方注册MPRegion标签,这样做后我们只需要在首页里使用MPContent包含相应的动态内容而不需要被复制的Html标签搞昏了头。这就是为什么我们初次打开首页几乎看不到控制首页格式的HTML标签的原因。同理,其他模块也是如此这般,这里就不冗诉了。