《Asp.Net Forums2.0深入分析》之 Asp.Net Forums是如何实现代码分离和换
2024-07-10 12:56:47
供稿:网友
在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的源码:
<%@ import namespace="aspnetforums.components" %>
<%@ register tagprefix="forums" namespace="aspnetforums.controls" assembly="aspnetforums.controls" %>
<%@ register tagprefix="mp" namespace="metabuilders.webcontrols.masterpages" assembly="metabuilders.webcontrols.masterpages" %>
<mp:contentcontainer runat="server" id="mpcontainer" masterpagefile="~/themes/masterpage.ascx">
<mp:content id="maincontent" runat="server">
<p align="center">
<forums:navigationmenu displaytitle="true" id="navigationmenu1" runat="server" />
<br />
<br />
<br />
<forums:login runat="server" id="postview1" />
</p>
</mp:content>
</mp:contentcontainer>
注:其中 <mp:***> ,这个是一个第三方控件,其目的是为了保证界面的一致性,提取页面间的重复代码。
从源码中我们没有看到任何构成login.aspx页面效果的textbox、button等基本元素。甚至没有发现一行c#代码,不过如果您对页面控件比较熟悉不难发现原来asp.net forums中将登陆的界面封装为了控件(在此对页面控件并不作专门介绍,如果您对控件相关知识还比较陌生的话,强烈推荐您查阅相关资料或书籍)。 原来登陆界面的实现就是在<forums:login runat="server" id="postview1" />控件中,从
<%@ register tagprefix="forums" namespace="aspnetforums.controls" assembly="aspnetforums.controls" %>
我们可以知道login控件对应的类应该为:aspnetforums.controls.login,在vs.net中,切换到类视图,找到aspnetforums.controls.login并转到对应文件:
(该图告诉您如何快速的查找控件对应的文件)
从代码中看到的该控件是从skinnedforumwebcontrol类继承的:
public class login : skinnedforumwebcontrol { // 从 skinnedforumwebcontrol 基类继承
......
}
我们还是先看看基类skinnedforumwebcontrol。
using system;
using system.drawing;
using system.collections;
using system.collections.specialized;
using system.web;
using system.web.ui;
using system.web.ui.webcontrols;
using aspnetforums;
using aspnetforums.components;
using system.componentmodel;
using system.io;
using system.web.security;
using aspnetforums.enumerations;
namespace aspnetforums.controls {
[
parsechildren(true)
]
/// <summary>
/// 几乎asp.net forums中所有控件的基类,继承自webcontrol,并实现inamingcontainer接口
/// </summary>
public abstract class skinnedforumwebcontrol : webcontrol, inamingcontainer {
forumcontext forumcontext = forumcontext.current;
string skinfilename = null;
string skinname = null;
string returnurl = null;
forummode mode = forummode.user;
public skinnedforumwebcontrol() {
// 使用的皮肤——如果是匿名用户,则使用系统默认样式
//
if (forumcontext.user.isanonymous) {
skinname = globals.skin;
}
else {
skinname = forumcontext.user.theme;
}
}
/// <summary>
/// 当开发复合服务器控件或模板服务器控件时,必须重写此方法。
/// 通知使用基于合成的实现的服务器控件创建它们包含的任何子控件,以便为回发或呈现做准备。
/// </summary>
protected override void createchildcontrols() {
control skin;
// 装载用户控件
skin = loadskin();
// 初始化控件
initializeskin(skin);
controls.add(skin);
}
/// <summary>
/// 通过skinname和skinfilename找出用户控件文件的路径,装载该用户控件后的control对象
/// </summary>
/// <returns></returns>
protected control loadskin() {
control skin;
// 用户控件文件所在位置
string skinpath = globals.getskinpath() + "/skins/" + skinfilename.trimstart('/');
string defaultskinpath = globals.applicationpath + "/themes/default/skins/" + skinfilename.trimstart('/');
// 必须要有skinfilename属性
if (skinfilename == null)
throw new exception("you must specify a skin.");
// 从用户控件文件获取 usercontrol 对象。
try {
skin = page.loadcontrol(skinpath);
}
catch (filenotfoundexception) {
// 如果没有找到指定皮肤的用户控件文件,装载默认皮肤下的控件文件
try {
skin = page.loadcontrol(defaultskinpath);
}
catch (filenotfoundexception) {
throw new exception("critical error: the skinfile " + skinpath + " could not be found. the skin must exist for this control to render.");
}
}
return skin;
}
/// <summary>
/// 初始化控件,并绑定控件数据
/// </summary>
/// <param name="skin"></param>
protected abstract void initializeskin(control skin);
/// <summary>
/// 用户控件文件(*.ascx)路径
/// </summary>
public string skinfilename {
get {
return skinfilename;
}
set {
skinfilename = value;
}
}
/// <summary>
/// 皮肤名
/// </summary>
protected string skinname {
get {
return skinname;
}
set {
skinname = value;
}
}
public forummode mode {
get { return mode; }
set { mode = value; }
}
}
}
从代码中可以看出,基类skinnedforumwebcontrol继承自webcontrol类并实现了inamingcontainer接口。既然是自定义控件,自然就是从webcontrol类继承。之所以实现inamingcontainer接口,是因为在开发模板化控件时,应实现该接口以避免同一页上的命名冲突。
在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。
还有两个重要的方法,一个是loadskin(),在该方法中,首先根据上面介绍的两个属性找出用户控件文件的路径,然后通过page.loadcontrol(defaultskinpath)方法,从用户控件文件中获取 usercontrol 对象。这也就是为什么皮肤不同,页面样式就不同,因为随着皮肤的不同,我们load的用户控件文件也不同,我们只要将用户控件文件样式设置的不一样,就可以随着皮肤的不同显示的样式也不一样。
但是光这些还不够,我们还需要识别ascx页中的页面控件。例如在skin-login.ascx中,我们必须知道哪个输入框是帐号的,那个输入框是密码的,知道用户是否点击了登陆按钮。回想vs.net中,ide自动帮我们把这些控件根据id识别出来,在ide中双击按钮,就可以直接加上响应点击事件的代码,多么方便。但是现在我们该如何?……
基类中还有一个抽象的initializeskin(control skin)方法,所有继承自skinnedforumwebcontrol的控件都必须override该方法,因为我们可以在这个方法中来初始化控件,在loadskin()方法中我们已经通过page.loadcontrol(defaultskinpath)方法返回了一个usercontrol,现在我们在initializeskin(control skin)中通过control.findcontrol 方法,在usercontrol中搜索指定的服务器控件,并对控件进行绑定数据和事件。还是以login控件为例,摘取aspnetforums.controls.login类中的部分代码如下:
public class login : skinnedforumwebcontrol { // 从 skinnedforumwebcontrol 基类继承
string skinfilename = "skin-login.ascx"; // 默认皮肤文件
textbox username; // 帐号输入框
textbox password; // 密码输入框
button loginbutton; // 登陆按钮
}
public login() : base() {
if (skinfilename == null)
skinfilename = skinfilename; // 定义默认的皮肤文件
}
// 重写 initializeskin 初始化
override protected void initializeskin(control skin) {
// 查找ascx页中id是username的textbox控件
username = (textbox) skin.findcontrol("username");
// 查找ascx页中id是password的textbox控件
password = (textbox) skin.findcontrol("password");
// 找到登陆按钮
loginbutton = (button) skin.findcontrol("loginbutton");
loginbutton.click += new system.eventhandler(loginbutton_click); // 绑定登陆按钮的click事件
loginbutton.text = resourcemanager.getstring("loginsmall_button");
}
在skin-login.ascx中,我们的每个控件都有一个id,例如用户名输入框的id是username,这样,我们就可以在重写initializeskin(control skin)的时候,利用username = (textbox) skin.findcontrol("username");这样的方法来一个个找到对应用户控件文件中的控件。并可以对绑定数据和事件,例如:loginbutton.click += new system.eventhandler(loginbutton_click),绑定登陆按钮的click事件。
综上所述,asp.net forums就是通过自定义控件来实现代码分离的,并通过在控件中动态装载用户控件文件(*.aspx)来实现换皮肤功能的。当您在看asp.net forums2.0源代码的时候,再也不要被<forums:navigationmenu displaytitle="true" id="navigationmenu1" runat="server" />这样的代码所吓倒,直接切换到类视图,找到对应的类:aspnetforums.controls.navigationmenu,从类代码中……
如果您还不是很理解,您可以自己研读一下asp.net forums2.0的源码,如果您觉得太复杂,请看模拟asp.net forums实现可以换皮肤的控件一文的例子,也许有助您理解:)