当我们第一眼见到CS的时候你是不是被他那么纷繁复杂的结构看傻眼呢。那么在认识CS之前最好对它的页面间关系做一个全面的了解,这对我门改造CS有较大的帮助。
当我们第一眼见到CS的时候你是不是被他那么纷繁复杂的结构看傻眼呢。那么在认识CS之前最好对它的页面间关系做一个全面的了解,这对我门改造CS有较大的帮助。
首先我们对整体一个简单的了解,如图,此为在IDE中打开的项目列表:
其中CommunityServerWeb项目为IIS运行的WEB项目,项目的UI层相关的都放在此处。CommunityServerComponents和CommunityServerControls都是支持整个系统运行所必须的组件,其中CommunityServerControls项目里有大量的系统公用的控件,由于本系统的几乎所有页面都是由用户控件组合而成的所以需要先了解在CS中用户控件的分布机制。
CS用户控件:我们知道,用户控件一般以.ascx为扩展名,在建立时都自带了相应的.cs文件,而在CS中考虑到明晰的分层结构和方便的修改UI以及最大程度的换肤功能,程序使用了后台代码与前台UI分开在两个层中,拿常用的CommunityServerControls这个项目来说在项目里我们看不到ascx文件的踪影,原来ascx文件保存在UI层,并且按照各种风格定义了不一样的ascx文件,具体的路径可以参照下图的导航找到:
我们可以看到,在UI层的Themes文件夹里保存了所有在其他层实现的用户控件的ascx文件,我想有必要介绍一下此目录的结构。
如上图,default文件夹里保存了系统平台使用的默认风格的UI文件,其他文件夹为相应的其他风格的文件集合。当然在此Blogs和Galleries两个文件夹分别是针对博客的皮肤和相册的皮肤,因为这两个项目需要根据具体用户的需要单独制定样式,和网站整体的样式不相关所以单独保存在平行的文件夹里。让我们再看看default文件夹里有些什么,首先是images这个不用说,就是保存了此种风格需要的图片资源,Masters文件夹里保存了页面的整体风格的相关框架,这个在后面详细描述,Skin文件夹里保存的既是大量的用户控件的UI实现,其中命名规则为:skin-ClassName.ascx 其中ClassName为相对应的类名,注意文件名一定要按照这个规则来命名,程序运行的时候,后台的是根据这个名称来找到相应的UI层文件的,这样隔离了依赖关系,前台不用被后台代码束缚了,此种模式我们把它叫着模板用户控件,因为UI层的职责已经很明确了那就是提供控件展现的模板供后台代码调用。
可以这样来看页面执行过程:用户请求aspx的页面à程序查找页面中的用户控件à实例话用户控件à实例化时根据配置找到相应风格相应名称的ascx文件一并实例化à生成关联后的控件对象返回给aspx页面。这样看来,虽然代码分离了,达到的效果却没有变。
问题出现了,后台代码和UI层的同步问题,我们在平时建立用户控件或aspx文件的时候IDE自动为我们生成了很多代码,使得我们不需要考虑前台的控件和后台代码的对应问题,而这里代码和UI是分开的,涉及到增加删减控件不同步的问题。要达到用户在UI层删除一个元素之后页面也不会出错,用户随时需要了可以随时修改回去,在CS中是这样处理的,拿登陆的用户控件来看(\src\Controls\Login.cs&\src\Web\Themes\default\Skins\Skin-Login.ascx)
如图,这是Login在CommunityServerControls项目里的继承关系,我们的重点应该放在TemplatedWebControl这个类里,此类是所有通过模板方式建立用户控件的基类,在这里包含了处理模板相关的功能,比如命名规则自动找到相应的ascx文件等等,这里注意一下这个的方法:protected abstract void AttachChildControls();可以看出此方法为抽象的,而且整个类里也就只有这么一个抽象方法,由继承关系我们知道在Login类里必须实现此方法,此方法的作用是干什么的呢,让我们看看Login类里这个方法都干了些什么:
login.cs
head#region head
//------------------------------------------------------------------------------
// <copyright company="Telligent Systems">
// Copyright (c) Telligent Systems Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
using System;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using CommunityServer.Components;
#endregion
namespace CommunityServer.Controls
{
[
ParseChildren(true)
]
public class Login : SecureTemplatedWebControl {
CSContext csContext = CSContext.Current;
TextBox username;
TextBox password;
IButton loginButton;
CheckBox autoLogin;
Other Code#region Other Code
// *********************************************************************
// Login
//
/**//// <summary>
/// Constructor
/// </summary>
// ***********************************************************************/
public Login() : base()
{
}
protected string ReferralLink
{
get
{
return ViewState["ReferralLink"] as string;
}
set
{
ViewState["ReferralLink"] = value;
}
}
protected override void OnInit(EventArgs e)
{
this.EnableViewState = true;
base.OnInit (e);
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad (e);
if(!Page.IsPostBack)
{
Uri referral = Context.Request.UrlReferrer;
if(referral != null)
ReferralLink = referral.ToString();
}
}
// *********************************************************************
// CreateChildControls
//
/**//// <summary>
/// This event handler adds the children controls.
/// </summary>
// ***********************************************************************/
protected override void CreateChildControls()
{
// If the user is already authenticated we have no work to do
if(Page.Request.IsAuthenticated)
{
// If the URL is for the login page and the user is already logged in
// we need to throw an access denied exception
if (Globals.GetSiteUrls().Login.StartsWith(csContext.Context.Request.Path))
throw new CSException (CSExceptionType.UserAlreadyLoggedIn, csContext.ReturnUrl);
return;
}
base.CreateChildControls();
}
#endregion
protected override void AttachChildControls()
{
// Find the username control
username = (TextBox) FindControl("username");
// Find the password control
password = (TextBox) FindControl("password");
// Find the login button
loginButton = ButtonManager.Create(FindControl("loginButton"));
loginButton.Click += new EventHandler(LoginButton_Click);
loginButton.Text = ResourceManager.GetString("LoginSmall_Button");
// Find the autologin checkbox
autoLogin = (CheckBox) FindControl("autoLogin");
autoLogin.Text = ResourceManager.GetString("LoginSmall_AutoLogin");
//autoLogin.AutoPostBack = true;
//autoLogin.CheckedChanged += new EventHandler( AutoLogin_OnCheckedChanged );
// Restore autologin status from the auto login cookie
AutoLoginCookie alCookie = new AutoLoginCookie();
autoLogin.Checked = alCookie.GetValue();
RegisterSetFocusScript();
}
Other Codes#region Other Codes
/**//// <summary>
/// Event handler to update auto login cookie value.
/// </summary>
protected void AutoLogin_OnCheckedChanged (Object sender, EventArgs e)
{
AutoLoginCookie alCookie = new AutoLoginCookie();
alCookie.Write( autoLogin.Checked );
}
// *********************************************************************
// LoginButton_Click
//
/**//// <summary>
/// Event handler to handle the login button click event
/// </summary>
// ***********************************************************************/
public void LoginButton_Click (Object sender, EventArgs e) {
User userToLogin = new User();
string redirectUrl = null;
// Save in cookie auto login user's preference
// only if it wasn't previously set or the cookie value differs from
// login's autologin checkbox status.
//
AutoLoginCookie alCookie = new AutoLoginCookie();
if (!alCookie.HasValue ||
(alCookie.HasValue && (alCookie.GetValue() != autoLogin.Checked)))
{
alCookie.Write( autoLogin.Checked );
}
if (!Page.IsValid)
return;
userToLogin.Username = username.Text;
userToLogin.Password = password.Text;
LoginUserStatus loginStatus = Users.ValidUser(userToLogin);
bool enableBannedUsersToLogin = CSContext.Current.SiteSettings.EnableBannedUsersToLogin;
// Change to let banned users in
//
if (loginStatus == LoginUserStatus.Success ||
(enableBannedUsersToLogin && loginStatus == LoginUserStatus.AccountBanned)) {
// Are we allowing login?
// TODO -- this could be better optimized
if (!CSContext.Current.SiteSettings.AllowLogin && !userToLogin.IsAdministrator) {
throw new CSException(CSExceptionType.UserLoginDisabled);
}
HttpCookie formsAuthCookie;
formsAuthCookie = FormsAuthentication.GetAuthCookie(userToLogin.Username, autoLogin.Checked);
UserCookie userCookie = csContext.User.GetUserCookie();
userCookie.WriteCookie(formsAuthCookie, 30, autoLogin.Checked);
// Get the link from the context
if ((CSContext.Current.ReturnUrl != null) && (CSContext.Current.ReturnUrl.Trim() != string.Empty))
redirectUrl = CSContext.Current.ReturnUrl;
// If none, get the stored redirect url
if ((redirectUrl == null) && (ReferralLink != null) && (ReferralLink.Trim() != string.Empty))
redirectUrl = ReferralLink;
// Check to ensure we aren't redirecting back to a Message prompt or back to the logout screen
// Or ChangePassword*, or CreateUser*, or EmailForgottenPassword*
// Or, if no URL, use appPath
if (Globals.IsNullorEmpty(redirectUrl)
|| (redirectUrl.IndexOf("MessageID") != -1)
|| (redirectUrl.IndexOf(Globals.GetSiteUrls().Logout) != -1)
|| (redirectUrl.IndexOf("ChangePassword") != -1)
|| (redirectUrl.IndexOf("EmailForgottenPassword") != -1))
redirectUrl = Globals.GetSiteUrls().Home;
LeaveSecureConnection(redirectUrl);
}
else if (loginStatus == LoginUserStatus.InvalidCredentials) {
// Invalid Credentials
Page.Response.Redirect( Globals.GetSiteUrls().Message(CSExceptionType.UserInvalidCredentials), true );
}
else if (loginStatus == LoginUserStatus.AccountPending) {
// Account not approved yet
Page.Response.Redirect( Globals.GetSiteUrls().Message(CSExceptionType.UserAccountPending), true );
}
else if (loginStatus == LoginUserStatus.AccountDisapproved) {
// Account disapproved
Page.Response.Redirect( Globals.GetSiteUrls().Message(CSExceptionType.UserAccountDisapproved), true );
}
else if (loginStatus == LoginUserStatus.UnknownError) {
// Unknown error because of miss-syncronization of internal data
throw new CSException(CSExceptionType.UserUnknownLoginError);
}
// Reject banned users if they are not allowed to
// pass through login.
//
else if (!enableBannedUsersToLogin && loginStatus == LoginUserStatus.AccountBanned) {
// Account banned
Page.Response.Redirect( Globals.GetSiteUrls().Message(CSExceptionType.UserAccountBanned), true );
}
}
private void RegisterSetFocusScript()
{
string key = "LoginOnFocus";
if(Page.IsStartupScriptRegistered(key))
return;
string script = @"
<script language=""javascript"">
<!--
document.forms[0].{0}.focus()
-->
</script>";
Page.RegisterStartupScript("LoginOnFocus", string.Format(script, username.ClientID) ) ;
}
#endregion
}
}
看了这个我们应该明白了吧,此方法就是我们手动建立相关控件的关联,使用FindControl("controlname")方法我们就可以找到模板的相应控件所以在定制模板的时候模板里的控件的ID一定要和此处一一对应即可。
你一定会想,这样一一对应后每每修改前台模板内的控件后不是都要到相应的后台代码里修改相应的代码,不错,是这样,不过还是有相应的对策来弥补这种不足,那就是在后台尽量把前台需要的功能和代码考虑全,这样在前台如果需要去掉某个控件后台的代码也不需要改变,这里后台代码就应该这样写了:
TextBox name = FindControl("username") as TextBox;
if(name != null)
{
//处理代码
}
这里可以看出,第一句使用了as语句,作用为把找到的对象转换为TextBox类型,如果没找到或类型转换失败也不引发异常而是将NULL付给变量,这样我们在代码里只需要加多判断引用是否为NULL即可按照我们的想法处理相应逻辑了。
怕写太多让人没耐心,故分成几篇来分析,后面将介绍在CS中的模板处理机制。