在CS 2.0下,使用了大量的Ajax来提高用户体验,比如AjaxPager来实现无刷新翻页,以后有时间会把AjaxPager讨论一下。
大家可能知道,在CS里面,登录是专门放到一个页面来处理的,当点击首页时,跳转到登录页面。这样做的好处是逻辑比较清楚,代码比较简单。但缺点是每次登录完后需要重新跳转到首页,增加了用户的等待时间。而我们在二次开发的过程中,很多时候希望在首页上直接有登录的区域,类似很多门户网站的操作,但是又不希望每次登录都PostBack,这样的话采用了Ajax。下面是我开发的登录控件的过程,另外由于我现在工作的环境主要还是framework 1.1,所以开发的这个登录控件也可以在1.1环境下使用。
先说一下开发的思路,由于原来CS的登录也是做成了控件的形式,通过AnonymousTemplate和LoggedInTemplate模版来配置登录时和登录后的页面,在后台完成相应的代码,也就是和Asp.net 2.0里的登录控件差不多(2.0里面简单看了看,可能比较类似)。因此改造登录控件时,我也希望延用这种方式,再把Ajax加上去。
其实在之前我也写了一个基于CS的登录控件,但是感觉不够模块化,那个控件采用了Ajax的Anthem第三方开源控件,但是正如上一篇文章
CommunityServer 2.0中的Ajax和Anthem比较提到的,由于只有标识为UpdateAfterCallBack=true的控件才能够完成无刷新更新,如果页面的控件比较多,而且Asp.net控件和Anthem控件堆到一起时,代码非常的乱;另外Anthem的脚本会自动生成到aspx页中,个人感觉不便于缓存,而且如果首页上只有登录控件使用Anthem,也会生成大量的Anthem脚本文件,很是不爽。而CS2.0的Ajax,给人感觉代码比较简洁,有的时候也比较灵活。因此采用了CS 2.0的Ajax来完成,重构后的登录控件和页面耦合度更低,可以放置到各个页面而且不需要修改代码,另外可以通过Template来更改布局。
下面来看一下代码:
AjaxManager.cs这是CommunityServer中用来封装Ajax的一些代码
global.js CommunityServer中实现Ajax_CallBack的脚本
TemplateWebControl CS中用来实现皮肤调用的基类,在CS中只要有类继承此类,就需要有对应的皮肤
Login.cs 继承了TemplateWebControl,相当于一个容器
LoginView.cs 这里为了方便,直接继承了Panel,但是参考原有CS的实现,使用了ITemplate
AnonymousUserCtrlComplex.cs 匿名用户看到的内容
RegisteredUserControl.cs 登录用户看到的内容
这里着重看一下LoginView的部分代码
[PersistChildren(
false
), ParseChildren(
true
)]
public
class
LoginView : Panel,INamingContainer
{
protected override void OnInit(EventArgs e)
{
base.OnInit (e);
AjaxManager.Register(this,"Login");
}
public LoginView()
{
}
[AjaxMethod(IncludeControlValuesWithCallBack=true)]
public string ValidUser(string user,string password)
{
//CommunityServer.Components.User userToLogin = new CommunityServer.Components.User();
string redirectUrl = null;
if(user=="admin" && password=="admin")
{
FormsAuthentication.SetAuthCookie(user, false);
}
StringWriter stringWriter = new StringWriter();
HtmlTextWriter htmlWriter = new HtmlTextWriter(stringWriter);
this.Controls.Clear();
this.ChildControlsCreated=false;
this.Page.DataBind();
this.Render(htmlWriter);
return stringWriter.ToString();
}
public override void DataBind()
{
this.EnsureChildControls();
base.DataBind ();
}
protected override void CreateChildControls()
{
this.Controls.Clear();
this.ChildControlsCreated=false;
ITemplate template = null;
if(!Context.User.Identity.IsAuthenticated)
{
template = AnonymousTemplate;
}
else
{
template = LoggedInTemplate;
}
if(template != null)
{
//basilwang 2008-02-26 没有采用原有CS增加Control的方式,而保持AnonymousTemplate的Parent为LoginView
//Control cntrl = new Control();
//template.InstantiateIn(cntrl);
template.InstantiateIn(this);
//this.Controls.Add(cntrl);
}
}
[
Browsable( false ),
DefaultValue( null ),
Description( "TODO SkinTemplate Description" ),
PersistenceMode( PersistenceMode.InnerProperty ),
]
public ITemplate AnonymousTemplate
{
get {return _anonymousTemplate;}
set {_anonymousTemplate = value;}
}
private ITemplate _anonymousTemplate;
[
Browsable( false ),
DefaultValue( null ),
Description( "TODO SkinTemplate Description" ),
PersistenceMode( PersistenceMode.InnerProperty ),
]
public ITemplate LoggedInTemplate
{
get {return _loggedInTemplate;}
set {_loggedInTemplate = value;}
}
private ITemplate _loggedInTemplate;
}
注意AjaxManager.Register的注册时间,是在生命周期的最开始Init完成的
另外对于CallBack的函数一定要加上[AjaxMethod(IncludeControlValuesWithCallBack=true)]
那么对于回调生成的html,这里我向老赵学习,尽量通过framework生成规整的html,当然这里也费了我很多的时间
由于采用了模版ITemplate,而模版在AddParsedSubObject的时候只是add到Control集合当中,注意此时并没有调用模版的CreateChildControls方法,那么什么时候这些方法会被调用呢,我开始一直认为是在Page的PreRender,但是通过Reflector发现Page的PreRenderRecursiveInternal在执行完this.EnsureChildControls()后(此时还没有递归调用他的子控件的PreRender),紧接着处理 this.OnPreRender(EventArgs.Empty);但是由于我们的Ajax方案是在PreRender时候即返回了结构,所以总是得不到Template的值。后来我使用了DataBind(建议大家在使用模版ITemplate时,别忘了DataBind这个好用的方法),因为DataBind()有一个递归EnsureChildControls()方法,可以帮助我们把所有的子控件都给创建出来,最后的事情就比较简单了,Render出来就可以了。
另外在WebForm1.aspx页面上一定要加
{
//base.VerifyRenderingInServerForm(control);
}
防止CallBack时框架验证是否包含在服务器端Form中,这也是我觉得不爽的地方
asp.net1.1源代码附上
LoginAjax.rar,
asp.net 2.0下面也可以用,做一下转换即可