DbEntry 开发实践:Wiki 系统(七)
流水帐记到现在,系统基本完成,不过还有几点需要解决。
首先,我们来添加一个“记住我”的功能。当用户登录的时候,如果选择了“Remember Me”,那么就同时把用户名密码等信息保存到用户的cookie中,用户访问时,先检测cookie,如果cookie中有保存的信息,则验证此信息,如果验证通过,则建立相应的session,否则导向Login页面。
先在SysUser中建立序列化和反序列化的两个函数:
然后修改Main.master中的OnInit以修改验证逻辑加入cookie部分:
然后修改Login.aspx加入Remember Me按钮:
运行程序,验证如果选择了Remember Me,则再次直接访问页面,不再需要登录,而且,Welcome信息显示名称正确;如果点击了Logout,再次运行程序,则仍然需要登录。现在,这个功能开发完毕。
第二个要完成的功能,是Wiki和用户关联。既然我们现在有登录用户了,自然可以记录下究竟是谁修改了哪个Wiki页。
修改Article和ArticleHistory,增加User:
然后修改它们两个的Init函数和SaveArticle函数,增加对于user的支持,编译程序,将以前只提供两个参数的Init和SaveArticle函数调用,都改成也提供user的形式。现在我们实际上已经修改了数据库表结构,所以删除App_Data目录下的wiki.db文件,再次运行程序,创建用户、编辑文章,还和以前一样的操作,不过,我们已经记录了编辑者,只是还没有显示出来而已。
在History.aspx里增加user列:
在Show.aspx里增加一个tip变量,一个msg变量,这个msg变量,标记为InMaster,以便我们访问母板页里的msg控件:
然后在Page_Load里对tip进行赋值,再override OnPreRender函数,添加这个tip(之所以需要这样,而不是直接在Page_Load里操作,也是因为asp.net奇怪的事件加载顺序):
现在,运行程序,Show页面底边将会显示编辑者的名字和编辑的时间,History页面也会显示编辑者的名字了。
最后,我们再考虑一下性能。对于Show页面而言,问题不大,不过,History页面,并不显示Content,却需要加载它,是有些浪费的,所以,我们标记ArticleHistory的Content为延迟加载:
另外,History里显示用户名,Show页面显示用户名,都需要访问数据库,History更是多次访问数据库读取sys_users表,只为得到用户名,对于这一点,我们给它加个缓存,在web.config里添加:
在SysUser上标记可以被缓存:
为了运行时可以跟踪生成的SQL语句,也为了验证一下我们缓存的效果,修改web.config中的log设置,将它设置为将生成的sql写入数据库:
分别以打开和关闭缓存的方式访问相同的网页,之后检查数据库lephone_logs表里的SQL语句,验证有缓存时生成SQL数量较少。另外,ArticleHistory相关的Select语句在History页面不会有Content在内。
好的,这个VisualWiki系列文章到这里算是全部结束了,虽然这个Wiki系统,还有一些功能有待完善,比如历史比对功能等,不过,它已经是一个可以运行,并且用起来也颇顺手的Wiki了,作为我前一个公司的内部Wiki系统,它一直运行,而且表现不错。最终代码:VisualWiki7.7z
全文完。
首先,我们来添加一个“记住我”的功能。当用户登录的时候,如果选择了“Remember Me”,那么就同时把用户名密码等信息保存到用户的cookie中,用户访问时,先检测cookie,如果cookie中有保存的信息,则验证此信息,如果验证通过,则建立相应的session,否则导向Login页面。
先在SysUser中建立序列化和反序列化的两个函数:
代码
public static string SerializeToString(string name, string password)
{
var s = string.Format("{0}\n{1}", name, password);
var bs = Encoding.UTF8.GetBytes(s);
return Base32StringCoding.Decode(bs);
}
public static SysUser DeserializeFromString(string source)
{
var bs = Base32StringCoding.Encode(source);
var s = Encoding.UTF8.GetString(bs);
var ss = s.Split('\n');
if (ss.Length == 2)
{
return GetUserForLogin(ss[0], ss[1]);
}
return null;
}
{
var s = string.Format("{0}\n{1}", name, password);
var bs = Encoding.UTF8.GetBytes(s);
return Base32StringCoding.Decode(bs);
}
public static SysUser DeserializeFromString(string source)
{
var bs = Base32StringCoding.Encode(source);
var s = Encoding.UTF8.GetString(bs);
var ss = s.Split('\n');
if (ss.Length == 2)
{
return GetUserForLogin(ss[0], ss[1]);
}
return null;
}
然后修改Main.master中的OnInit以修改验证逻辑加入cookie部分:
代码
protected override void OnInit(EventArgs e)
{
var u = this.GetLoginUser();
if (null == u)
{
var luc = Request.Cookies[Const.LoginCookie];
if (luc != null)
{
u = SysUser.DeserializeFromString(luc.Value);
}
if (null == u)
{
var url = new UrlBuilder("Login.aspx").Add(Const.BackToUrl, Request.Url.ToString());
Response.Redirect(url.ToString());
}
this.SetLoginUser(u);
}
UserName.Text = u.Name;
base.OnInit(e);
}
{
var u = this.GetLoginUser();
if (null == u)
{
var luc = Request.Cookies[Const.LoginCookie];
if (luc != null)
{
u = SysUser.DeserializeFromString(luc.Value);
}
if (null == u)
{
var url = new UrlBuilder("Login.aspx").Add(Const.BackToUrl, Request.Url.ToString());
Response.Redirect(url.ToString());
}
this.SetLoginUser(u);
}
UserName.Text = u.Name;
base.OnInit(e);
}
然后修改Login.aspx加入Remember Me按钮:
代码
protected void Page_Load(object sender, EventArgs e)
{
ErrMsg.Visible = false;
if (!IsPostBack)
{
SysUser u = this.GetLoginUser();
if (u != null)
{
this.SetLoginUser(null);
}
FormsAuthentication.SignOut();
var luc = Request.Cookies[Const.LoginCookie];
if (luc != null)
{
luc.Expires = DateTime.Now.AddDays(-1);
luc.Value = "";
Response.Cookies.Set(luc);
}
}
}
protected void SignIn_Click(object sender, EventArgs e)
{
ProcessLogin(UserName.Text, Password.Text);
}
private void ProcessLogin(string name, string password)
{
SysUser u = SysUser.GetUserForLogin(name, password);
if (u != null)
{
this.SetLoginUser(u);
if (RememberMe.Checked)
{
var cookie = new HttpCookie(Const.LoginCookie, SysUser.SerializeToString(u.Name, password)) { Expires = DateTime.Now.AddDays(30) };
Response.Cookies.Add(cookie);
}
var url = Request[Const.BackToUrl];
if(!string.IsNullOrEmpty(url))
{
Response.Redirect(url);
}
else
{
Response.Redirect("Default.aspx");
}
}
else
{
ErrMsg.Text = "User name or password error!";
ErrMsg.Visible = true;
}
}
{
ErrMsg.Visible = false;
if (!IsPostBack)
{
SysUser u = this.GetLoginUser();
if (u != null)
{
this.SetLoginUser(null);
}
FormsAuthentication.SignOut();
var luc = Request.Cookies[Const.LoginCookie];
if (luc != null)
{
luc.Expires = DateTime.Now.AddDays(-1);
luc.Value = "";
Response.Cookies.Set(luc);
}
}
}
protected void SignIn_Click(object sender, EventArgs e)
{
ProcessLogin(UserName.Text, Password.Text);
}
private void ProcessLogin(string name, string password)
{
SysUser u = SysUser.GetUserForLogin(name, password);
if (u != null)
{
this.SetLoginUser(u);
if (RememberMe.Checked)
{
var cookie = new HttpCookie(Const.LoginCookie, SysUser.SerializeToString(u.Name, password)) { Expires = DateTime.Now.AddDays(30) };
Response.Cookies.Add(cookie);
}
var url = Request[Const.BackToUrl];
if(!string.IsNullOrEmpty(url))
{
Response.Redirect(url);
}
else
{
Response.Redirect("Default.aspx");
}
}
else
{
ErrMsg.Text = "User name or password error!";
ErrMsg.Visible = true;
}
}
运行程序,验证如果选择了Remember Me,则再次直接访问页面,不再需要登录,而且,Welcome信息显示名称正确;如果点击了Logout,再次运行程序,则仍然需要登录。现在,这个功能开发完毕。
第二个要完成的功能,是Wiki和用户关联。既然我们现在有登录用户了,自然可以记录下究竟是谁修改了哪个Wiki页。
修改Article和ArticleHistory,增加User:
[BelongsTo]
public abstract SysUser User { get; set; }
public abstract SysUser User { get; set; }
然后修改它们两个的Init函数和SaveArticle函数,增加对于user的支持,编译程序,将以前只提供两个参数的Init和SaveArticle函数调用,都改成也提供user的形式。现在我们实际上已经修改了数据库表结构,所以删除App_Data目录下的wiki.db文件,再次运行程序,创建用户、编辑文章,还和以前一样的操作,不过,我们已经记录了编辑者,只是还没有显示出来而已。
在History.aspx里增加user列:
<asp:TemplateField HeaderText="User">
<ItemTemplate><%# ((ArticleHistory)(Container.DataItem)).User.Name %></ItemTemplate>
</asp:TemplateField>
<ItemTemplate><%# ((ArticleHistory)(Container.DataItem)).User.Name %></ItemTemplate>
</asp:TemplateField>
在Show.aspx里增加一个tip变量,一个msg变量,这个msg变量,标记为InMaster,以便我们访问母板页里的msg控件:
[InMaster] public Lephone.Web.Common.NoticeLabel msg;
private string tip;
private string tip;
然后在Page_Load里对tip进行赋值,再override OnPreRender函数,添加这个tip(之所以需要这样,而不是直接在Page_Load里操作,也是因为asp.net奇怪的事件加载顺序):
protected override void OnPreRender(EventArgs e)
{
if(!string.IsNullOrEmpty(tip))
{
msg.AddTip(tip);
}
}
{
if(!string.IsNullOrEmpty(tip))
{
msg.AddTip(tip);
}
}
现在,运行程序,Show页面底边将会显示编辑者的名字和编辑的时间,History页面也会显示编辑者的名字了。
最后,我们再考虑一下性能。对于Show页面而言,问题不大,不过,History页面,并不显示Content,却需要加载它,是有些浪费的,所以,我们标记ArticleHistory的Content为延迟加载:
[LazyLoad]
public abstract string Content { get; set; }
public abstract string Content { get; set; }
另外,History里显示用户名,Show页面显示用户名,都需要访问数据库,History更是多次访问数据库读取sys_users表,只为得到用户名,对于这一点,我们给它加个缓存,在web.config里添加:
<add key="CacheEnabled" value="true" />
在SysUser上标记可以被缓存:
[Cacheable]
public abstract class SysUser : LinqObjectModel<SysUser>
public abstract class SysUser : LinqObjectModel<SysUser>
为了运行时可以跟踪生成的SQL语句,也为了验证一下我们缓存的效果,修改web.config中的log设置,将它设置为将生成的sql写入数据库:
<add key="SqlLogRecorder" value="Lephone.Data.Logging.DatabaseLogRecorder, Lephone.Data" />
分别以打开和关闭缓存的方式访问相同的网页,之后检查数据库lephone_logs表里的SQL语句,验证有缓存时生成SQL数量较少。另外,ArticleHistory相关的Select语句在History页面不会有Content在内。
好的,这个VisualWiki系列文章到这里算是全部结束了,虽然这个Wiki系统,还有一些功能有待完善,比如历史比对功能等,不过,它已经是一个可以运行,并且用起来也颇顺手的Wiki了,作为我前一个公司的内部Wiki系统,它一直运行,而且表现不错。最终代码:VisualWiki7.7z
全文完。