宝玉

专注于web开发技术
随笔 - 114, 评论 - 1517 , 引用 - 583

《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的源码:

<%@ 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实现可以换皮肤的控件一文的例子,也许有助您理解:)

发表于 2004年5月28日 8:43

评论

# re: 《Asp.Net Forums2.0深入分析》之 Asp.Net Forums是如何实现代码分离和换皮肤的

讲得太好了,谢谢!
2004-5-28 13:53 | jiangyu

# re: 《Asp.Net Forums2.0深入分析》之 Asp.Net Forums是如何实现代码分离和换皮肤的

严重佩服您的技术,更佩服您的共享精神,您一定会更有成就的!祝福您!愿成为您的朋友!

QQ:9842766
2004-6-22 23:56 | jlzhou

# re: 《Asp.Net Forums2.0深入分析》之 Asp.Net Forums是如何实现代码分离和换皮肤的

了不起!
2004-7-7 11:04 | Numby

# re: 《Asp.Net Forums2.0深入分析》之 Asp.Net Forums是如何实现代码分离和换皮肤的

代码是看懂了,但感觉无法熟练应用到自己的项目中
2004-7-14 11:17 | xiaoqiao

# re: 《Asp.Net Forums2.0深入分析》之 Asp.Net Forums是如何实现代码分离和换皮肤的

代码是看懂了,但感觉无法熟练应用到自己的项目中
QQ:2220911
2004-7-14 11:17 | xiaoqiao

# re: 《Asp.Net Forums2.0深入分析》之 Asp.Net Forums是如何实现代码分离和换皮肤的

你好,我一直在寻找代码与显示页面分离的方法,在这个过程中碰到了一些问题,在百度上搜索到这里,想请你指教一下,在这里先谢谢你了!

问题是这样的:
我用代码隐藏的方式可以做到控制调用页面的控件如一个ID为test_1的Label控件,进行赋值,但是把这个代码文件编译成DLL就不行了,为什么呢?我想一定有些地方我是理解错了。
2004-8-23 8:36 | jdeasy

# re: 《Asp.Net Forums2.0深入分析》之 Asp.Net Forums是如何实现代码分离和换皮肤的

可能是你调用方法不对,例如:
<%@ Register TagPrefix="Forums" Namespace="AspNetForums.Controls" Assembly="AspNetForums.Controls" %>
<Forums:CreateEditPost runat="server"/>

Assembly属性就是表示dll的名称
2004-8-23 20:50 | 宝玉

# re: 《Asp.Net Forums2.0深入分析》之 Asp.Net Forums是如何实现代码分离和换皮肤的

我的代码是这样的:

显示页面:
<%@ Page Language="VB" runat="server" ResponseEncoding="gb2312" %>
<%@ Register TagPrefix="test_1" Namespace="test1" Assembly="test1" %>
<test_1:Class2 id="mytest" runat="server" />
<form runat="server" >
<Asp:Label id="labtest1" runat="server" />
</form>

test1.dll的源代码(vb)如下:
Imports System
Imports System.Web
Imports Microsoft.VisualBasic
Imports System.Web.UI
Imports System.Web.UI.Page
Imports System.web.UI.WebControls
Imports System.Data
Imports System.Data.OleDb
Imports System.ComponentModel
Imports System.Configuration

Namespace test1

Public Class Class2 : Inherits Control : Implements INamingContainer

Protected labtest1 As System.Web.UI.Webcontrols.Label
#Region " Web 窗体设计器生成的代码 "

'该调用是 Web 窗体设计器所必需的。
<System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()

End Sub

Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Init
'CODEGEN: 此方法调用是 Web 窗体设计器所必需的
'不要使用代码编辑器修改它。
InitializeComponent()
End Sub

#End Region

Public Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
labtest1.text="hello"
End Sub

End Class

End Namespace

如果去掉命名空间,用
<%@ Page Language="VB" runat="server" Inherits="class2" src="test1.vb" %>这样的方式是可以控制的。
我想,是不是用了命名空间之后,就不可以直接控制原页面上的控件了?
2004-8-23 21:19 | jdeasy

# re: 《Asp.Net Forums2.0深入分析》之 Asp.Net Forums是如何实现代码分离和换皮肤的

我的想法是这样的:

在页面上有一个预先放好的控件,然后在DLL里面可以调用或控制。
不论用VB或是C#,我是想这种想法是否可以实现。

在你的这篇文章里,我看到了装载用户控件文件的方式,可以调用预先定义好的控件。但如果被控制的控件是放在aspx里面而不是有用户控件文件ascx里,要怎么才能Load和Find呢?
2004-8-23 22:23 | jdeasy

# re: 《Asp.Net Forums2.0深入分析》之 Asp.Net Forums是如何实现代码分离和换皮肤的

在聚会上就问过你关于换皮肤的问题。文章写的很清晰,一目了然。我有一些问题:是否 .Text 也是用这个原理实现的?还有就是这样一个一个控件的find,在效率上有多大的影响?
2005-1-3 21:10 | 依栏望海

# re: 《Asp.Net Forums2.0深入分析》之 Asp.Net Forums是如何实现代码分离和换皮肤的

换肤原理非常棒,但是在多控件共用一个后台CS的地方,请宝玉回答一下,为什么不多个ascx公用一个ascx.cs文件呢?这样在IDE里,依然可以双点按扭直接切换到按扭事件,而且不用动态搜索控件或者填加事件很直观啊?
以我的理解,为什么不用多个ACCS共用一个*.ascx.cs呢,而单独写控件逻辑处理类?我想也有他的道理和好处
*.ascx.cs里,控件都是自动被protected声明了的,而不是通过FINDCONTROL的.事件也都是自动绑定的.而且还有一个关键问题是:传统的*.ascx里是强性指定了后台逻辑编码文件的路径----CodeBehind="header.ascx.cs",而文章里的ASCX无需知道CS文件位置,而CS知道动态调用的ASCX位置就可以了.逻辑分离的更彻底.
不知道宝玉老师认为我的理解对不对呢.
2005-1-12 10:46 | huihui

# re: 《Asp.Net Forums2.0深入分析》之 Asp.Net Forums是如何实现代码分离和换皮肤的

楼上的想法有创意.严重同意...
2005-4-22 2:48 | Meteor

# re: 《Asp.Net Forums2.0深入分析》之 Asp.Net Forums是如何实现代码分离和换皮肤的

据说老兄是个“武痴”,佩服!
2005-6-8 23:58 | test

# re: 《Asp.Net Forums2.0深入分析》之 Asp.Net Forums是如何实现代码分离和换皮肤的

感觉aspnetforums的代码分离不是很彻底, 差不多每个ascx文件中都有<% ... %>这样的代码, 而这些代码又没有类似cs的编译检查, 一旦业务层进行代码调整那更改起来就通苦了!
2005-7-22 2:48 | billy

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

Ping Back来自:blog.csdn.net
2005-8-14 13:28 | david8k

# 模拟Asp.Net Forums实现可以换皮肤的控件

Ping Back来自:blog.csdn.net
2005-8-14 16:29 | david8k

# re: 《Asp.Net Forums2.0深入分析》之 Asp.Net Forums是如何实现代码分离和换皮肤的

宝玉,佩服你,你真是太棒了!
2005-10-4 0:48 | 蒋明桔

# re: 《Asp.Net Forums2.0深入分析》之 Asp.Net Forums是如何实现代码分离和换皮肤的

你好,谢谢你的文章,受教了。
有一个问题,不知道是版本问题,还是您笔误了,在我下的ANF中SkinnedForumWebControl 是继承Control, INamingContainer ,不是WebControl,我的版本是Asp.Net Forums V2.2.1929 中文官方版
2005-10-25 21:44 | 仁面寿星

# 模拟Asp.Net Forums实现可以换皮肤的控件

模拟Asp.Net Forums实现可以换皮肤的控
2005-11-9 10:24 | 淹不死的鱼

# re: 《Asp.Net Forums2.0深入分析》之 Asp.Net Forums是如何实现代码分离和换皮肤的

我若是只开发一个普通的网站,基本上不需要换肤,用这种架构划算吗?
2005-11-9 22:57 | 二进制逻辑

# re: 《Asp.Net Forums2.0深入分析》之 Asp.Net Forums是如何实现代码分离和换皮肤的

真是爽,正在找这个就被我找到了哈哈
2005-11-14 22:49 | king

# re: 《Asp.Net Forums2.0深入分析》之 Asp.Net Forums是如何实现代码分离和换皮肤的

在communityserver中,cs和ascx分离,继承TemplatedWebControl类加载ascx,现在有一个问题请教:如果在repeater中嵌套repeater,如何为二级repeater的itemcommand事件进行委托,
如果skin和代码不分离,可以在ascx中repeater中设置:
<asp:Repeater id="Repeater2" runat="server" OnItemCommand="Repeater2_ItemCommand" OnItemDataBound="Repeater2_ItemDataBound">
这样,会自动去codebihind的cs中寻找Repeater2_ItemCommand和Repeater2_ItemDataBound,但是,分离后,aspnet仍然按照原来的默认方法去找,是找不到的。

如果在repeater1的itemcreated中:
Repeater2.ItemCreated += new RepeaterItemEventHandler(Repeater2_ItemCreated);
Repeater2.ItemCommand += new RepeaterCommandEventHandler(Repeater2_ItemCommand);
就必须每一次(无论是否postback),都要重新取数据,将repeater1绑定一次,这样的话,一是效率低,另外,实现查询等非常困难。
所以请教,如何能够在代码分离的前提下,能够将repeater2的事件委托给后台的代码。
非常感谢
2005-11-17 2:08 | zhangll

# re: 《Asp.Net Forums2.0深入分析》之 Asp.Net Forums是如何实现代码分离和换皮肤的

不错 ^_^
2006-12-2 4:27 | 早起的虫子

# re: 《Asp.Net Forums2.0深入分析》之 Asp.Net Forums是如何实现代码分离和换皮肤的

先做出很多模板,把这些模板的codefile定位到同一个文件,把模板保存到不同的文件夹,通过定位到不同的文件夹换肤
2007-4-27 21:01 | wongfays

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

转贴:以便查看
《Asp.Net Forums2.0深入分析》之 Asp.Net Forums是如何实现代码分离和换皮肤的
在Visual Studio中开发Web项目,Web 窗体页由两部分组成:视觉元素(HTML、服务器控件和静态文本)和该页的编程逻辑。...
2007-11-22 21:47 | 网虫的BLOG

# 模拟Asp.Net Forums实现可以换皮肤的控件

转贴(作者宝玉,链接:http://webuc.net/dotey/archive/2004/05/28/835.aspx)
以便查看
模拟Asp.Net Forums实现可以换皮肤的控件
为了帮助您理解《Asp.Net...
2007-11-22 22:02 | 网虫的BLOG

# Attribute应用,简化ANF自定义控件初始化过程

研究ANF的源码,让我获益良多。其中很多思想,都是非常值得学习的。其中换肤的方式,宝玉已经介绍过了,《Asp.NetForums2.0深入分析》之Asp.NetForums是如何实现代码分离和换...
2008-5-20 14:35 | bqrm_521(小奎)

Post Comment

主题  
姓名  
主页
校验码  
内容   
京ICP备 05050892号