对于平时用惯了VS IDE的人来说,如果看到cs的源码,可能一下子就懵了,没有我们所熟悉的.aspx.cs后台代码文件,甚至是在页面里面也根本没有熟悉的HTML标记.取而代之的是一个个的控件标签. 我们先看看首页代码:
<%@ Page SmartNavigation="False" Language="C#" enableViewState = "false" %>
<%@ Register TagPrefix="CS" Namespace="CommunityServer.Controls" Assembly="CommunityServer.Controls" %>
<%@ Register TagPrefix="CSD" Namespace="CommunityServer.Discussions.Controls" Assembly="CommunityServer.Discussions" %>
<%@ Import Namespace="CommunityServer.Galleries.Components" %>
<%@ Import Namespace="CommunityServer.Blogs.Components" %>
<%@ Import Namespace="CommunityServer.Components" %>
<%@ Register TagPrefix="CSH" Namespace="CommunityServer.Controls.HomePage" assembly="Openlab.CSAddOns" %>
<
CS:SelectedNavigation Selected="home" runat="Server"/>
<CS:ContentContainer runat="server" id="MPContainer">
<CS:Content id="BodyContentRegion" runat="server">
<CSH:BodyLayoutTemplate runat="server"/>
</CS:Content>
</CS:ContentContainer>
整个页面全部由控件定义而成,没有后台代码文件asp.cs,我们只有通过类视图来查看,通过页面上的声明,很容易可以找到这些控件的实现代码.比如: BodyLayoutTemplate控件,根据前缀和上面的声明,可以知道该控件是 CommunityServer.Controls.HomePage.BodyLayoutTemplate类.我们再跟进去看这个类里到底写了些什么.(我们现在先不管那个ContentContainer标记,那个是Metabuilder开发的一个控制页面风格统一的自定义控件)
public class BodyLayoutTemplate : TemplatedWebControl
{
// Methods
public BodyLayoutTemplate()
{
this._bodyLayout = null;
}
protected override void AttachChildControls()
{
this._bodyLayout = this.FindControl("bodylayout") as PlaceHolder;
}
// Fields
private PlaceHolder _bodyLayout;
}
进去才发现这个类本身几乎没有做什么事情. 就定义了一个容器控件,用来增加其他子控件. 再跟到它的父类TemplatedWebControl里去.
public abstract class TemplatedWebControl : WebControl, INamingContainer ...
原来它是个模板控件.这让我想起在首页里显示的blog列表,论坛文章列表等都是放在这个里面的.我们知道一般模板控件都要override CreateChildControls() 这个方法,那么加载的信息一定在这里了.果然,我们看看这段代码
protected override void CreateChildControls()
{
Controls.Clear();
// 1) A custom theme setting is the most important
Boolean _skinLoaded = false;
if (!Globals.IsNullorEmpty(ThemeName))
{
if (SkinFolderExists)
{
if (SkinFileExists && this.Page != null)
{
Control skin = this.Page.LoadControl(this.SkinPath);
this.Controls.Add(skin);
_skinLoaded = true;
}
}
}
// 2) Next, look for an inline template
if (!_skinLoaded && SkinTemplate != null)
{
SkinTemplate.InstantiateIn(this);
_skinLoaded = true;
}
// 3) last resort is the default external skin
if (!_skinLoaded && this.Page != null && this.DefaultSkinFileExists)
{
Control defaultSkin = this.Page.LoadControl(this.DefaultSkinPath);
this.Controls.Add(defaultSkin);
_skinLoaded = true;
}
//locations were successful, throw.
if (!_skinLoaded)
{
throw new CSException(CommunityServer.Components.CSExceptionType.SkinNotFound);
}
// 4) If none of the skin
AttachChildControls();
}
这里就可以清晰的看到到底是怎么一回事了,开始是加载自定义的theme,但由于不存在那个路径,所有,程序就执行了第三步,加载默认的theme,
protected virtual string DefaultSkinPath
{
get
{
return "~/Themes/default/Skins/" + ExternalSkinFileName;
}
}
protected virtual String ExternalSkinFileName
{
get
{
if (SkinName == null)
return CreateExternalSkinFileName(null);
return SkinName;
}
set
{
SkinName = value;
}
}
protected virtual string CreateExternalSkinFileName(string path)
{
return CreateExternalSkinFileName(path, "Skin-" + this.GetType().Name);
}
这下可以全部看清了,原来页面是加载了位于 themes/default/skins/目录下的 skin-Skin-BodyLayoutTemplate.ascx控件. 赶紧到这个目录下去看,果然找到了这个文件:
<%@ Control Language="C#" %>
<%@ Register TagPrefix="CS" Namespace="CommunityServer.Controls" Assembly="CommunityServer.Controls" %>
<%@ Register TagPrefix="Galleries" Namespace="CommunityServer.Galleries.Controls" Assembly="CommunityServer.Galleries" %>
<%@ import Namespace="CommunityServer.Components" %>
<%@ Register TagPrefix="CSH" Namespace="CommunityServer.Controls.HomePage" assembly="Openlab.CSAddOns" %>
<div id="HomePageColumn2">
<ul>
<li><Galleries:AggregatePortalPictureListing Count="3" ShowPictures="true" SortBy="ThreadDate" runat="server" /></li>
<li><CSH:BlogRoll Count="8" TitleLength="7" runat="server" ShowRssLink="true"/><br /></li>
<li><CS:WhoIsOnline runat="server" /></li>
</ul>
</div>
<div id="HomePageColumn1">
<ul>
<li><%= CSContext.Current.SiteSettings.HomePageContent %></li>
<li><CSH:LatestForumPosts Count="8" TitleLength="24" ShowTitleOnly="true" runat="server" /></li>
<li><CSH:LatestBlogPosts Count="8" TitleLength="24" ShowTitleOnly="true" runat="server"/></li>
</ul>
</div>
至此,眼前的路豁然开朗起来, 这里的 latestBlogPosts,LatestForumPosts不就是我们首页里的博客列表论坛列表吗. 同样,这里也是使用控件来定义. 现在我们在根据以上的跟踪思路,就可以很轻松的知道我应该怎么去修改这个论坛了. 好,再下面的跟踪就不用再说了. 我们回到正题.
可以看出,整个cs的页面都是以这种方式来设计和实现的. 我们可以总结成一句话: 前台页面所有元素均定义成自定义控件,把实现代码抽取成单独的程序集. 当然, 这个自定义控件一般使用模板控件来控制页面结构,使用具体的元素控件来表现内容. 这样架构我个人认为至少有以下几个好处:
1.页面与后台逻辑几乎完全分离,所有的功能,即使是一个很小的页面元素也使用控件实现,这样,我们可以很容易的实现软件的换肤功能, 所作的只不过是在新的皮肤里将这些元素重新组合一下而已.
2.层级结构清晰. 我们可以看到cs已经将页面总多的控件分了层次来逐级加载.最上层是控制页面结构的空白模板控件,内部一级一级的分别加载.这样使得真个页面的控件耦合度较低. 我们可以按照自己的方式将不同的元素控件组合到某个框架里去.
3.可扩展性强. 比如这次我需要将博客中的文章列表也在首页显示出来,那么,我只需要在上面的页面里自己再定义一个控件,然后在CommunityServer.Controls.HomePage名字空间下再写出这个控件的实现方法即可.不影响现有的任何其他模块代码.
不过这种方式是有性能上的损失的,还好,整个系统都使用了cache, 可以弥补这里的损失.
以后,在我们开发个性化要求很强的软件里, 比如一些较为时尚的站点和应用程序,需要换肤功能, CS提供了一种不错的参考思想