DotText的换肤实现起来挺复杂的,阅读了代码后把这块的心得记录下来.
1.先简单说哈结构,(详细的结构描述可以看"DotText源码阅读(6) --模版皮肤 ")
其实就是一些用户控件.这里主要有个用户控件叫"PageTemplate.ascx"这个相当于是整套模版的Index.
每套模版放在一个文件夹内(比如winxpBlue),"PageTemplate.ascx"和css这些在这个文件夹的根目录,然后用户控件组在这个文件夹下的Controls里面 ,注意,用户控件的后台代码不是
跟ascx放在一起的,而是在网站根目录的UI\Controls下,也就是说各套模版共用同样的cs.
2.再来说哈主要相关的页面和类.
主要类就是Dottext.Web.UI.WebControls.MasterPage,这个类是整个换肤的主类,是个容器,是最关键的.它用来装载用户控件,具体操作下面再说
另外一个类就是ContentRegion,这个类是个Panel下继承来的,主要用来装在"内容"的用户控件
其实DotText在处理页面布局时,已经划分了好了,就是一个Head,一个Foot,一个LeftColumn(工具箱),还有一个"内容"(这个用来装载内容的,比如装载详细信息,信息列表之类的,而上面提到的ContentRegion就是用来装载这个的)
现在看default.aspx的设置
<%@ Page language="c#" Codebehind="default.aspx.cs" AutoEventWireup="false" Inherits="Dottext.Web.UI.Pages.DottextMasterPage"%>
<%@ Register TagPrefix="DT" Namespace="Dottext.Web.UI.WebControls" Assembly="Dottext.Web" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
<HEAD>
<title><asp:Literal ID="pageTitle" Runat="server" /></title>
<meta content=".Text" name="GENERATOR">
<link id="MainStyle" type="text/css" rel="stylesheet" runat="Server"/>
<link id="SecondaryCss" type="text/css" rel="stylesheet" runat="Server"/>
<link id="RSSLink" title="RSS" type="application/rss+xml" rel="alternate" runat="Server"/>
</HEAD>
<body>
<form id="Form1" method="post" runat="server">
<DT:MASTERPAGE id="MPContainer" runat="server">
<DT:contentregion id="MPMain" runat="server">
<asp:PlaceHolder id="CenterBodyControl" runat="server"></asp:PlaceHolder>
</DT:contentregion>
</DT:MASTERPAGE></form>
</body>
</HTML>
这里default的后台Dottext.Web.UI.Pages.DottextMasterPage,一会我们再说这个干了什么.
现在注意页面,一个MasterPage控件,里面包含了一个id为MPMain的ContentRegion的Panel,在Panel上有一个PlaceHolder.
3,现在来说到底是怎么工作的
在说明怎样工作之前,要看哈DotText的UrlReWriting(详细的可以看"dotText源码阅读(5)--URLreWrite和Handler ").DotText是用HttpHandle来实现UrlReWriting的 .
注意:其实用户访问的每一个页面都是转向到default.aspx,而之所以显示不同的内容是因为模版上的ID为"MPMain"的ContentRegion装载了不同的用户控件而已.
我们看哈WebConfig上关于Handle的Config上的设置
<HandlerConfiguration defaultPageLocation="default.aspx" type="Dottext.Common.UrlManager.HandlerConfiguration, Dottext.Common">
<HttpHandlers>
<HttpHandler pattern="/articles/\d+\.aspx$" controls="viewpost.ascx,Comments.ascx,AnonymousPostComment.ascx,LoginPostComment.ascx" />
<HttpHandler pattern="/articles/\w+\.aspx$" controls="viewpost.ascx,Comments.ascx,AnonymousPostComment.ascx,LoginPostComment.ascx" />
<HttpHandler pattern="/PreviewPost.aspx$" controls="PreviewPost.ascx" />
<HttpHandler pattern="/archive/\d{4}/\d{2}/\d{2}/\d+\.(aspx|htm)$" controls="viewpost.ascx,Comments.ascx,AnonymousPostComment.ascx,LoginPostComment.ascx" />
<HttpHandler pattern="/archive/\d{4}/\d{2}/\d{2}/\w+\.(aspx|htm)$" controls="viewpost.ascx,Comments.ascx,AnonymousPostComment.ascx,LoginPostComment.ascx" />
<HttpHandler pattern="/archive/\d{4}/\d{1,2}/\d{1,2}\.aspx$" controls="ArchiveDay.ascx" />
<HttpHandler pattern="/archive/\d{4}/\d{1,2}\.aspx$" controls="ArchiveMonth.ascx" />
<HttpHandler pattern="/archives/\d{4}/\d{1,2}\.aspx$" controls="ArticleArchiveMonth.ascx" />
<HttpHandler pattern="/contact\.aspx$" controls="Contact.ascx" />
<HttpHandler pattern="/AddToFavorite\.aspx$" handlerType="Page" pageLocation="AddToFavorite.aspx" />
<HttpHandler pattern="/BlogSearch\.aspx$" controls="BlogSearch.ascx" />
<HttpHandler pattern="/posts/|/story/|/archive/" type="Dottext.Web.UI.Handlers.RedirectHandler,Dottext.Web" handlerType="Direct" />
<HttpHandler pattern="/gallery\/\d+\.aspx$" controls="GalleryThumbNailViewer.ascx" />
<HttpHandler pattern="/gallery\/image\/\d+\.aspx$" controls="ViewPicture.ascx" />
<HttpHandler pattern="/(?:category|stories)/(\w|\s)+\.aspx$" controls="CategoryEntryList.ascx" />
<HttpHandler pattern="/favorite/(\w|\s)+\.aspx$" controls="FavoriteList.ascx" />
<HttpHandler pattern="/(?:admin)" type="Dottext.Web.UI.Handlers.BlogExistingPageHandler, Dottext.Web" handlerType="Factory" />
</HttpHandlers>
</HandlerConfiguration>
这里我只想说哈 <HttpHandler pattern="/PreviewPost.aspx$" controls="PreviewPost.ascx" /> 类型的配置, 比如这里,我们访问PreviewPost.aspx,那么会
在后台执行 Dottext.Common.UrlManager.HandlerConfiguration的GetHandle
public virtual IHttpHandler GetHandler(HttpContext context, string requestType, string url, string path)
{
.....................
.....................
switch(items[i].HandlerType)
{
case HandlerType.Page://默认是Page
return ProccessHandlerTypePage(items[i],context,requestType,url);
case HandlerType.Direct:
HandlerConfiguration.SetControls(context,items[i].BlogControls);
return (IHttpHandler)items[i].Instance();
case HandlerType.Factory:
//Pass a long the request to a custom IHttpHandlerFactory
return ((IHttpHandlerFactory)items[i].Instance()).GetHandler(context,requestType,url,path);
default:
throw new Exception("Invalid HandlerType: Unknown");
}
..............................
....................
}
而由于这种 PreviewPost.aspx的HandlerType是Page,所以会执行 ProccessHandlerTypePage .而这个主要执行的工作就是SetControls,即把配置中的这个 "PreviewPost.ascx"保存到HttpContext里面,方便初始化页面的时候调用(调用用GetControls方法);
最后地址仍然是被转向到了default.aspx.
现在来看初始化default.aspx页面,首先装载用户控件,default.aspx的后台代码是Dottext.Web.UI.Pages.DottextMasterPage, 在OnInit里面执行InitializeBlogPage函数,在这里读取HttpContext里面保存的用户控件名,装载它到MPMain上,而对于default.aspx上的用户控件MasterPage所做的操作是这样的,首先由于在请求时会执行事件AddParsedSubObject,在这里把default.aspx上定义的所有ContentRegion变量存入变量contents里面(即"MPMain").
然后在MasterPage的OnInit里面做了两件事:
一个是BuildMasterPage,在这里从BlogConfig里读取模版路径(例如:"WinXPBlue\PageTempalte.ascx"),然后加载它.
另一个是BuildContents,用来初始化框架上内容这块的. 根据contents里面存储的各个ContentRegion变量名和页面上已经装载的控件想比较,找到contents里面存储的那个ContentRegion变量(即"MPMain"),然后加载它.(注意:因为PageTemplate.ascx也定义了一个ID为MPMain的ContentRegion类的变量,这部做的其实就是加载PageTemplate里面定义的MPMain下的用户控件了)
好了,现在重新来看哈整个执行过程
1.访问PreviewPost.ascx
2.将字符串"PreviewPost.ascx"加入到HttpContext
3.转向到default.aspx
4.default.aspx加载Context里面存储的 PreviewPost.ascx 到页面上的MPMain上.
5.default.aspx装载MaterPage
6.MasterPage加载PageTemplate.ascx,并把它里面的子控件都加载在自己的Controls上.由于MasterPage重写了AddParsedSubObject,所以Default.aspx上的MPMain不会出现在MasterPage的Controls里面
7.MasterPage处理MPMain,将变量contents里面保存的default.aspx上的MPMain的控件剪切到从PageTemplate.ascx上加载的MPMain上去.
8.到此default.aspx输出的就是这样的:Head,Foot,Left工具箱是从PageTemplate.ascx上加载来的,而"内容"这块是PreviewPost.ascx了.