上下文
基本上我们在儿童时代都玩过积木玩具。通过一块块的积木,再加上我们的想象力,就可以构造出非常多不同的风格的建筑。那么, 我们可不可以把这种搭积木的方式应用到我们的web应用上呢。
问题
web应用通过提供给用户一整套组件(相当于积木),以及一套已经成型的方案(相当于图纸)。用户可以采用类似搭建积木的方式来根据自己的需要制作界面和应用。
环境
采用asp.net 1.0或asp.net 1.1
预备知识
C#,asp.net的服务器控件编程,asp.net服务器控件生命周期
最好具备以下知识
Page Controller,Front Controller
解决方案
1.建立目录结构
为了了解如何采用积木块式应用,我们首先建立如下的主题目录结构:

在Sheme目录下保存所有的主题,每个主题都采用相同的目录结构。UserControl目录保存最基本的积木块(UserControl),Page目录是积木块组成的页面所形成的UserControl,Css目录保存与积木块同名的css文件来控制UserControl的界面。
PageTeamplate.ascx是页面布局框架,根据所请求的页面不同,在PageTemplate的主体位置载入不同Page目录下的ascx文件。
2.划分自己的web积木——UserControl
将web应用分成一块块的积木,每一块积木形成一个UserControl,并且每一个UserControl有一个同名的css文件用来控制界面。最基本的积木块要放在UserControl目录下,而由最基本积木块组成的页面文件放在Page目录下。
提示:在一般的web应用中,都会有Header,Footer,Login等等这样的模块,这些模块就可以形成UserControl组成web应用的积木。具体积木块应该根据你的web应用功能来划分,一般来说我们可以把某个功能就划分成一个积木。
3.构建载入积木块的容器——MyPlaceHolder
有了UserControl这些积木块之后,就需要有能够自动在应用中载入这些UserControl积木的容器,这就是PlaceHolder。不过,我们需要扩展PlaceHolder的功能达到自动载入UserControl的目的。
public class MyPlaceHolder : PlaceHolder{private string userControl; // 要载入的UserControl目录下的.ascx
private string pageControl; // 要载入的Page目录下的.ascx
public MyPlaceHolder(){ userControl = ""; pageControl = "";} public string UserControl{ get { return userControl; } set { userControl = value; }} public string PageControl{ get { return pageControl; } set { pageControl = value; }} // 当需要载入多个UserControl时,可以直接调用LoadUserControl
// 当只需要载入一个UserControl时,可以调用Clear清除载入过的内容
public void Clear(){ this.Controls.Clear();} // 载入UserControl目录下的.ascx
// 以及导入对应的css文件
public void LoadUserControl(string UserControl){ this.userControl = UserControl; BasePage page = (BasePage)this.Page;// 请参考后面的BasePage的代码
Control control = this.Page.LoadControl(page.Scheme + "usercontrol/" + userControl + ".ascx"); string css = "css/" + userControl + ".css";// 对应的css文件
if(File.Exists(this.Page.MapPath(page.Scheme+css))){ page.AddCss(page.Scheme + css); } this.Controls.Add(control);} // 载入Page目录下的.ascx
// LoadPage与LoadUserControl的区别是两者载入的.ascx所在的目录不同
// Page目录下的.ascx可以看成是一些搭建主体结构的.ascx,其使用MyPlaceHolder
// 来包含最基础的积木块.ascx(在UserControl目录下)
public void LoadPage(string PageControl){ this.PageControl = PageControl; BasePage page = (BasePage)this.Page; Control control = this.Page.LoadControl(page.Scheme + "page/" + pageControl + ".ascx"); string css = "css/" + pageControl + ".css"; if(File.Exists(this.Page.MapPath(page.Scheme+css))) { page.AddCss(page.Scheme + css); } this.Controls.Add(control);} protected override void OnLoad(EventArgs e){ base.OnLoad(e); if(!userControl.Equals(string.Empty)) { LoadUserControl(userControl); }}} public class MyPlaceHolder : PlaceHolder{private string userControl; // 要载入的UserControl目录下的.ascx
private string pageControl; // 要载入的Page目录下的.ascx
public MyPlaceHolder(){ userControl = ""; pageControl = "";} public string UserControl{ get { return userControl; } set { userControl = value; }} public string PageControl{ get { return pageControl; } set { pageControl = value; }} // 当需要载入多个UserControl时,可以直接调用LoadUserControl
// 当只需要载入一个UserControl时,可以调用Clear清除载入过的内容
public void Clear(){ this.Controls.Clear();} // 载入UserControl目录下的.ascx
// 以及导入对应的css文件
public void LoadUserControl(string UserControl){ this.userControl = UserControl; BasePage page = (BasePage)this.Page;// 请参考后面的BasePage的代码
Control control = this.Page.LoadControl(page.Scheme + "usercontrol/" + userControl + ".ascx"); string css = "css/" + userControl + ".css";// 对应的css文件
if(File.Exists(this.Page.MapPath(page.Scheme+css))){ page.AddCss(page.Scheme + css); } this.Controls.Add(control);} // 载入Page目录下的.ascx
// LoadPage与LoadUserControl的区别是两者载入的.ascx所在的目录不同
// Page目录下的.ascx可以看成是一些搭建主体结构的.ascx,其使用MyPlaceHolder
// 来包含最基础的积木块.ascx(在UserControl目录下)
public void LoadPage(string PageControl){ this.PageControl = PageControl; BasePage page = (BasePage)this.Page; Control control = this.Page.LoadControl(page.Scheme + "page/" + pageControl + ".ascx"); string css = "css/" + pageControl + ".css"; if(File.Exists(this.Page.MapPath(page.Scheme+css))) { page.AddCss(page.Scheme + css); } this.Controls.Add(control);} protected override void OnLoad(EventArgs e){ base.OnLoad(e); if(!userControl.Equals(string.Empty)) { LoadUserControl(userControl); }}}使用方法:<HomeOffice:MyPlaceHolderid="Myplaceholder1"runat="server"UserControl="Header"></HomeOffice:MyPlaceHolder> // 这里的Header是位于UserControl目录下的Header.ascx
4.构建主要的建筑结构——PageTemplate.ascx
PageTemplate其实也是一个UserControl,只不过其功能是用来包含其他的UserControl积木,在PageTemplate里,可以定义页面的整体布局。比如:Header、Footer在整个页面中的位置,页面主体区域的位置等等。
更重要的是,PageTemplate中应该包含Form的定义,这是asp.net所需要的不可缺少的服务器控件。
<%@ Register TagPrefix="HomeOffice" Namespace="HomeOffice.Web.UI.WebControl"Assembly = "HomeOffice.Web.UI" %><!DOCTYPE HTML PUBLIC "-//W3C //DTD HTML 4.0 Transitional//EN" ><HTML><HEAD><title>构建积木式应用程序</title>
<asp:Literal ID="CssHolder" runat="server"></asp:Literal> <asp:Literal ID="ScriptHolder" Runat="server"></asp:Literal><style> BODY { margin-left : 0px; margin-right : 0px; } </style></HEAD><body bgcolor="#e6e6e6"><form id="Form1" method="post" runat="server" enctype="multipart/form-data"><table width="100%" cellpadding="0" cellspacing="0"><tr> <td> </td> <td width="800"> <table width="100%" cellpadding="0" cellspacing="0"> <tr> <td><HomeOffice:MyPlaceHolder id="PlaceHolder1"runat="server" UserControl="Header"></HomeOffice:MyPlaceHolder> </td> </tr> <tr> <td> <HomeOffice:MyPlaceHolder id="Myplaceholder1" runat="server" UserControl="MainMenu"></HomeOffice:MyPlaceHolder> </td> </tr> <tr> <td style="height:6px;background:#f6f 6f 6;font-size:1px;border-top:1px solid white;"> </td> </tr> <tr> <td style="height:4px;background:#e1e1e1;font-size:1px;border-top:1px solid #e6e6e6; "> </td> </tr> <tr> <td style="background:white;border-bottom:1px solid #bbbbbb"> <HomeOffice:MyPlaceHolder id="PageBody" runat="server"></HomeOffice:MyPlaceHolder> </td> </tr> <tr> <td style="padding-top:20px"> <HomeOffice:MyPlaceHolder id="Myplaceholder2" runat="server" UserControl="Footer"></HomeOffice:MyPlaceHolder> </td> </tr> </table></td><td> </td></tr></table></form></body></HTML>在这个PageTemplate中,我们可以看到使用了4个MyPlaceHolder来载入积木块,除了PageBody这个外,其他的都载入了位于UserControl目录下最基本的积木块。而PageBody的作用则是根据http所请求的页面不同载入Page目录下不同的页面。
这些其对应的cs代码在BasePage中处理。
public class BasePage : Page{public string Scheme = "/Scheme/blue/"; // 所采用的主题
public AppSetting Setting; // 环境配置,在Init中分析,其内容包括解析http请求到正确的Page目录下的
// 文件,建立当前登陆用户的信息
public Control focusControl; // 当页面载入后,首先获得焦点的控件
private Literal CssHolder; // 要导入的css
private Literal ScriptHolder; // 要导入的script文件
public BasePage() { focusControl = null; } // 导入css文件引用
public void AddScript(string script) {// 进行IsPostBack判断的原因是
// 防止重复导入
if(!this.IsPostBack){ ScriptHolder.Text += string.Format("<script src=http://blog.csdn.net/"{0}/" type=\"text/javascript\"></script>\n", script);} } // 导入script文件引用
public void AddCss(string css) { if(!this.IsPostBack) { CssHolder.Text += "<link rel=\"stylesheet\" type=\"text/css\" href=\"" + css + "\">\n";} } // 载入http请求分析后的Page目录下的所请求的文件
public void LoadPageTemplate() { Control control = (Control)this.LoadControl(this.Scheme+"PageTemplate.ascx"); CssHolder = (Literal)control.FindControl("CssHolder"); ScriptHolder = (Literal)control.FindControl("ScriptHolder"); this.Controls.Add(control); MyPlaceHolder body = (MyPlaceHolder)control.FindControl("PageBody"); body.LoadPage(this.Setting.TargetPage);// 调用MyPlaceHolder的LoadPage方法
// TargetPage记录了请求的页面
} protected override void OnInit(EventArgs e) { base.OnInit(e); // 分析http请求
Setting = new AppSetting(this.Request.Path); // 设置用户信息
if(this.Request.IsAuthenticated) { Setting.SetUser(User.Identity.Name); } } protected override void OnLoad(EventArgs e) { base.OnLoad(e); this.LoadPageTemplate(); } // 当页面显示后,初始获得焦点的控件
protected void SetFocusControl() { if(this.focusControl==null) return; string template = @"<script language='jscript'>document.all.{0}.focus();</script>"; string script = string.Format(template, this.focusControl.ClientID); this.RegisterStartupScript("FocusControl", script); } protected override void OnPreRender(EventArgs e) { base.OnPreRender(e); SetFocusControl(); } // 修复了asp.net 1.1的一个bug
// 没有这段代码,LinkButton等某些服务器将无法使用
protected override void Render(HtmlTextWriter writer) { StringBuilder stringBuilder = new StringBuilder(); StringWriter stringWriter = new StringWriter(stringBuilder); HtmlTextWriter htmlWriter = new HtmlTextWriter(stringWriter); base.Render(htmlWriter); string html = stringBuilder.ToString(); int start = html.IndexOf("<form name=\"") + 12; int end = html.IndexOf("\"", start); string formID = html.Substring(start, end - start); string replace = formID.Replace(":", "_"); html = html.Replace("document."+formID,"document."+replace);writer.Write(html); }}BasePage中的一个非常重要的成员变量Setting,其作用是保存分析http请求后的结果。由于采用积木式应用,用户所请求的aspx文件在硬盘上并不存在,需要把用户的这种http请求解析成Page目录下的某个ascx文件,让BasePage