《Asp.Net Forums2.0深入分析》之 Asp.Net Forums是如何实现代码分离和换

在Visual Studio中开发Web项目,Web 窗体页由两部分组成:视觉元素(HTML、服务器控件和静态文本)和该页的编程逻辑。 一般将这两个组成部分分别存储在一个单独的文件中。可视元素在一个 .aspx 文件中创建,而代码位于一个单独的类文件中(.aspx.vb 或 .aspx.cs)。或者有时候也会在同一文件中创建视觉元素和代码。

而在Asp.Net Forums的Web窗体页中没有找到我们熟悉的.aspx.cs文件,也没有发现任何C#代码,取而代之是一个个控件,代码在哪里?!

下面将以login.aspx为例详细说明Asp.Net Forums是如何实现代码分离和换皮肤的:
首先我们看看login.aspx在两种皮肤样式下的运行效果
(Theme:default) (Theme:ElectricMidnight)

只是更改了一下Asp.Net Forums的默认皮肤,同样是Login.aspx,显示的是两种不同的皮肤样式。先回想一下VS.Net中,先不论换皮肤功能,如果我们要实现一个登陆页面,那么我们在Aspx或Ascx页中将输入帐号密码的TextBox、登陆的Button拖入,在编辑区双击Button,写上对Button点击事件处理的代码,多么方便,大部分代码都由VS.Net为我们完成了。

我们再来看Login.aspx的源码:

1@ Import Namespace="AspNetForums.Components" 
1@ Register TagPrefix="Forums" Namespace="AspNetForums.Controls" Assembly="AspNetForums.Controls" 
1@ Register TagPrefix="mp" Namespace="MetaBuilders.WebControls.MasterPages" Assembly="MetaBuilders.WebControls.MasterPages" 
 1<mp:contentcontainer id="MPContainer" masterpagefile="~/Themes/MasterPage.ascx" runat="server">
 2<mp:content id="MainContent" runat="server">
 3<p align="center">
 4<forums:navigationmenu displaytitle="true" id="Navigationmenu1" runat="server"></forums:navigationmenu>
 5<br/>
 6<br/>
 7<br/>
 8<forums:login id="PostView1" runat="server"></forums:login>
 9</p>
10</mp:content>
11</mp:contentcontainer>

注:其中

1<mp:***> ,这个是一个第三方控件,其目的是为了保证界面的一致性,提取页面间的重复代码。   
2  
3从源码中我们没有看到任何构成Login.aspx页面效果的TextBox、Button等基本元素。甚至没有发现一行C#代码,不过如果您对页面控件比较熟悉不难发现原来Asp.Net Forums中将登陆的界面封装为了控件(在此对页面控件并不作专门介绍,如果您对控件相关知识还比较陌生的话,强烈推荐您查阅相关资料或书籍)。 原来登陆界面的实现就是在<forums:login id="PostView1" runat="server"></forums:login>控件中,从 

@ Register TagPrefix="Forums" Namespace="AspNetForums.Controls" Assembly="AspNetForums.Controls"

  1
  2我们可以知道Login控件对应的类应该为:AspNetForums.Controls.Login,在VS.Net中,切换到类视图,找到AspNetForums.Controls.Login并转到对应文件:   
  3![](http://www.163er.com/edu/upload/2004_09/040902173367741.gif)   
  4(该图告诉您如何快速的查找控件对应的文件)   
  5
  6
  7从代码中看到的该控件是从SkinnedForumWebControl类继承的: 
  8
  9public class Login : SkinnedForumWebControl { // 从 SkinnedForumWebControl 基类继承   
 10......   
 11} 
 12
 13  
 14我们还是先看看基类SkinnedForumWebControl。 
 15
 16using  System;   
 17using  System.Drawing;   
 18using  System.Collections;   
 19using  System.Collections.Specialized;   
 20using  System.Web;   
 21using  System.Web.UI;   
 22using  System.Web.UI.WebControls;   
 23using  AspNetForums;   
 24using  AspNetForums.Components;   
 25using  System.ComponentModel;   
 26using  System.IO;   
 27using  System.Web.Security;   
 28using  AspNetForums.Enumerations;   
 29  
 30namespace  AspNetForums.Controls {   
 31  
 32[   
 33ParseChildren(  true  )   
 34]   
 35/// <summary>   
 36/// 几乎Asp.Net Forums中所有控件的基类,继承自WebControl,并实现INamingContainer接口   
 37/// </summary>   
 38public abstract class  SkinnedForumWebControl : WebControl, INamingContainer {   
 39  
 40ForumContext forumContext = ForumContext.Current;   
 41string  skinFilename =  null  ;   
 42string  skinName =  null  ;   
 43string  returnURL =  null  ;   
 44ForumMode mode = ForumMode.User;   
 45  
 46  
 47public  SkinnedForumWebControl() {   
 48  
 49// 使用的皮肤——如果是匿名用户,则使用系统默认样式   
 50//    
 51if  (forumContext.User.IsAnonymous) {   
 52skinName = Globals.Skin;   
 53}    
 54else  {   
 55skinName = forumContext.User.Theme;   
 56}   
 57  
 58}   
 59  
 60  
 61  
 62/// <summary>   
 63/// 当开发复合服务器控件或模板服务器控件时,必须重写此方法。   
 64/// 通知使用基于合成的实现的服务器控件创建它们包含的任何子控件,以便为回发或呈现做准备。   
 65/// </summary>   
 66protected override void  CreateChildControls() {   
 67Control skin;   
 68  
 69// 装载用户控件    
 70skin = LoadSkin();   
 71  
 72// 初始化控件    
 73InitializeSkin(skin);   
 74  
 75Controls.Add(skin);   
 76}   
 77  
 78  
 79/// <summary>   
 80/// 通过SkinName和SkinFilename找出用户控件文件的路径,装载该用户控件后的Control对象   
 81/// </summary>   
 82/// <returns></returns>   
 83protected  Control LoadSkin() {   
 84Control skin;   
 85// 用户控件文件所在位置    
 86string  skinPath = Globals.GetSkinPath() + "/Skins/" + SkinFilename.TrimStart('/');   
 87string  defaultSkinPath = Globals.ApplicationPath + "/Themes/default/Skins/" + SkinFilename.TrimStart('/');   
 88  
 89// 必须要有SkinFilename属性    
 90if  (SkinFilename ==  null  )   
 91throw new  Exception("You must specify a skin.");   
 92  
 93// 从用户控件文件获取 UserControl 对象。    
 94try  {   
 95skin = Page.LoadControl(skinPath);   
 96}   
 97catch  (FileNotFoundException) {   
 98  
 99// 如果没有找到指定皮肤的用户控件文件,装载默认皮肤下的控件文件    
100try  {   
101skin = Page.LoadControl(defaultSkinPath);   
102}   
103catch  (FileNotFoundException) {   
104throw new  Exception("Critical error: The skinfile " + skinPath + " could not be found. The skin must exist for this control to render.");   
105}   
106}   
107return  skin;   
108}   
109  
110/// <summary>   
111/// 初始化控件,并绑定控件数据   
112/// </summary>   
113/// <param name="skin"/>   
114protected abstract void  InitializeSkin(Control skin);   
115  
116/// <summary>   
117/// 用户控件文件(*.ascx)路径   
118/// </summary>   
119public string  SkinFilename {   
120get  {   
121return  skinFilename;   
122}   
123set  {   
124skinFilename = value;   
125}   
126}   
127  
128  
129/// <summary>   
130/// 皮肤名   
131/// </summary>   
132protected string  SkinName {   
133get  {   
134return  skinName;   
135}   
136set  {   
137skinName = value;   
138}   
139}   
140  
141public  ForumMode Mode {   
142get  {  return  mode; }   
143set  { mode = value; }   
144}   
145  
146}   
147}   
148
149
150从代码中可以看出,基类SkinnedForumWebControl继承自WebControl类并实现了INamingContainer接口。既然是自定义控件,自然就是从WebControl类继承。之所以实现INamingContainer接口,是因为在开发模板化控件时,应实现该接口以避免同一页上的命名冲突。 
151
152![](http://www.163er.com/edu/upload/2004_09/040902173367744.gif) 在SkinnedForumWebControl中有两个重要的属性:SkinName 和 SkinFilename,分别表示皮肤名和用户控件文件路径。在Asp.Net Forums2.0中,在Web目录下有一个Themes目录,每种皮肤对应一个目录,例如default、ElectricMidnight,每个皮肤文件夹下有三个文件夹:image、Skins和style,分别用来存放该皮肤下对应的图片文件、用户控件文件(*.ascx)和样式表Css文件。通过这两个属性,我们可以知道用户控件文件(*.ascx)的真实路径,例如我们的SkinName是default,SkinFilename是skin-login.ascx,那么用户控件的路径是Themes/default/skins/skin-login.ascx,同理,如果我们将皮肤样式换成ElectricMidnight,那么用户控件的路径将是 Themes/ElectricMidnight/skins/skin-login.ascx。 
153
154还有两个重要的方法,一个是LoadSkin(),在该方法中,首先根据上面介绍的两个属性找出用户控件文件的路径,然后通过Page.LoadControl(defaultSkinPath)方法,从用户控件文件中获取 UserControl 对象。这也就是为什么皮肤不同,页面样式就不同,因为随着皮肤的不同,我们Load的用户控件文件也不同,我们只要将用户控件文件样式设置的不一样,就可以随着皮肤的不同显示的样式也不一样。   
155
156
157但是光这些还不够,我们还需要识别ascx页中的页面控件。例如在skin-login.ascx中,我们必须知道哪个输入框是帐号的,那个输入框是密码的,知道用户是否点击了登陆按钮。回想VS.Net中,IDE自动帮我们把这些控件根据ID识别出来,在IDE中双击按钮,就可以直接加上响应点击事件的代码,多么方便。但是现在我们该如何?…… 
158
159基类中还有一个抽象的InitializeSkin(Control skin)方法,所有继承自SkinnedForumWebControl的控件都必须override该方法,因为我们可以在这个方法中来初始化控件,在LoadSkin()方法中我们已经通过Page.LoadControl(defaultSkinPath)方法返回了一个UserControl,现在我们在InitializeSkin(Control skin)中通过Control.FindControl 方法,在UserControl中搜索指定的服务器控件,并对控件进行绑定数据和事件。还是以Login控件为例,摘取AspNetForums.Controls.Login类中的部分代码如下: 
160
161public class  Login : SkinnedForumWebControl {  // 从 SkinnedForumWebControl 基类继承    
162string  skinFilename = "Skin-Login.ascx";  // 默认皮肤文件    
163TextBox username;  // 帐号输入框    
164TextBox password;  // 密码输入框    
165Button loginButton;  // 登陆按钮    
166}   
167public  Login() :  base  () {   
168if  (SkinFilename ==  null  )   
169SkinFilename = skinFilename;  // 定义默认的皮肤文件    
170} 
171
172// 重写 InitializeSkin 初始化    
173override protected void  InitializeSkin(Control skin) {   
174  
175// 查找ascx页中ID是username的textbox控件    
176username = (TextBox) skin.FindControl("username");   
177  
178// 查找ascx页中ID是password的textbox控件    
179password = (TextBox) skin.FindControl("password");   
180  
181// 找到登陆按钮    
182loginButton = (Button) skin.FindControl("loginButton");   
183loginButton.Click +=  new  System.EventHandler(LoginButton_Click);  // 绑定登陆按钮的Click事件    
184loginButton.Text = ResourceManager.GetString("LoginSmall_Button");   
185  
186} 
187
188在skin-login.ascx中,我们的每个控件都有一个ID,例如用户名输入框的ID是username,这样,我们就可以在重写InitializeSkin(Control skin)的时候,利用username = (TextBox) skin.FindControl("username");这样的方法来一个个找到对应用户控件文件中的控件。并可以对绑定数据和事件,例如:loginButton.Click += new System.EventHandler(LoginButton_Click),绑定登陆按钮的Click事件。 
189
190综上所述,Asp.Net Forums就是通过自定义控件来实现代码分离的,并通过在控件中动态装载用户控件文件(*.aspx)来实现换皮肤功能的。当您在看Asp.Net Forums2.0源代码的时候,再也不要被<forums:navigationmenu displaytitle="true" id="Navigationmenu1" runat="server"></forums:navigationmenu>这样的代码所吓倒,直接切换到类视图,找到对应的类:AspNetForums.Controls.NavigationMenu,从类代码中…… 
191
192如果您还不是很理解,您可以自己研读一下Asp.Net Forums2.0的源码,如果您觉得太复杂,请看  模拟Asp.Net Forums实现可以换皮肤的控件  一文的例子,也许有助您理解:)</mp:***>
Published At
Categories with Web编程
Tagged with
comments powered by Disqus