DbEntry 开发实践:Wiki 系统(六)
经过前面一系列的代码编写,我们有了一个可以工作的Wiki系统,现在我们先来给它进行一点儿美化。
我想象中的布局方式是,顶部是banner,导航栏,编辑、历史等链接,中间是Wiki或者FckEditor,下面是底边栏,显示一些提示信息和版权信息。这可以使用frame、iframe自己编码实现,也可以利用一些工具,我选择使用JQuery和它的插件JQuery Layout。
在VisualWiki里创建scripts目录,加入jquery和jquery layout,我用的版本是1.2.6。另外,加入jquery-1.2.6-vsdoc.js的话,在Visual Studio里也可以进行JQuery的智能提示。再创建一个styles目录,把BasicStyle.css复制进去,我们还需要多一些css支持,也可以添加到这个css文件中。创建一个images,放入一个banner.jpg文件等等。
JQuery Layout的写法基本上是定义一些特定class的div,然后把不同的内容,放入不同的div里,然后在script里初始化一下它,就可以了。所以,对于Show或者Edit页面,基本不用修改,只要修改Main.master就可以了,另外,考虑到我们要加入登录功能,我想用master页来进行鉴权工作,分离出一个新的master页也许更好些,Main.master负责鉴权和部分布局(manage部分也需要banner),而Wiki.master负责Wiki的布局,在Main.master里加入对于css和js的链接,并调用jquery layout进行初始化,它还会调用Wiki.master里的resetLayout以完成Wiki页的layout工作:
Wiki.master里增加的也基本上是一些div和一个resetLayout函数:
现在,我们开始设计登录及成员管理功能。DbEntry也支持ASP.Net的MemberShip功能,并且使之可以支持所有DbEntry支持的数据库,不过这里,我们并不使用MemberShip,而是自己设计一个用户表SysUser,其中,password应该是hash的,所以CommonHelper中需要增加一个生成password的函数:
SysUser如下:
然后在DataSources里增加SysUserDataSource,同样也不需要有任何代码。然后增加一个使用Main.master的Manage.master页,其中有两个链接,User List和New User。在Main.master的banner上,增加一个Label,显示一下欢迎信息。
在VisualWiki项目下,增加使用Manage.master的UserList.aspx,向其中添加GridView,SysUserDataSource,并设置GridView的DataSource为SysUserDataSource,添加Role列(这是GridView的bug,不能识别enum列),另外,添加一个HyperLinkField,用来提供一个编辑链接:
UserList.aspx页就开发完成了。UserEdit.aspx页也和DbEntry.Net主页介绍的编辑页编写流程类似,使用TemplateBuilder可以在几分钟之内完成。这里,我们对于Edit方式,设置Name控件为不可修改,另外,有一个和普通的编辑页面不同的需求,就是Password需要是hash的,所以,在SysUserDataSource 的OnObjectUpdating和OnObjectInserting事件中处理:
这样,用户管理页面基本开发完毕。我们再增加一个Login页面,这个Login页面不使用任何母板页,在Page_Load里,如果不是PostBack,则进行SignOut操作,而点击SignIn按钮,则调用SysUser.GetUserForLogin验证登录合法性。登录成功,则转向Default.aspx页,而Default.aspx也需要修改一下,根据登录用户的不同类型,重定向到不同的页面:
这里,我在WebUIExtends里建立了几个扩展方法,用来协助用户登录操作,比如GetLoginUser、SetLoginUser等等:
然后修改Main.master,重载OnInit函数(之所以不是写在Page_Load中,在于ASP.Net在母板页、普通页及控件中奇怪的事件加载顺序),进行用户登录与否的判断,修改Manage.master和Wiki.master,在Page_Load函数里判断登录对象类型,如果类型错误,则返回Login页面,另外,修改web.config中的authentication节,定义登录页面和缺省页面:
现在,运行程序,我们会被重导向到Login页面,不过,我们还没有可以用来登录的用户,让我们修改一下Global.asax,以便为我们增加一个管理员:
现在,运行程序,会被带入Login页面,输入tom/123登录,进入管理界面,点击New User链接以增加一个Wiki用户jerry/456,点击Logout退出登录,再使用jerry登录,就可以进入Wiki浏览、编辑界面了。
这样,用户管理界面也基本开发完毕,本来也以为这应该是这个系列文章的最后,不过,我们还需要处理其它一些细节,所以,提交代码、未完待续……
目前的代码:VisualWiki6.7z
我想象中的布局方式是,顶部是banner,导航栏,编辑、历史等链接,中间是Wiki或者FckEditor,下面是底边栏,显示一些提示信息和版权信息。这可以使用frame、iframe自己编码实现,也可以利用一些工具,我选择使用JQuery和它的插件JQuery Layout。
在VisualWiki里创建scripts目录,加入jquery和jquery layout,我用的版本是1.2.6。另外,加入jquery-1.2.6-vsdoc.js的话,在Visual Studio里也可以进行JQuery的智能提示。再创建一个styles目录,把BasicStyle.css复制进去,我们还需要多一些css支持,也可以添加到这个css文件中。创建一个images,放入一个banner.jpg文件等等。
JQuery Layout的写法基本上是定义一些特定class的div,然后把不同的内容,放入不同的div里,然后在script里初始化一下它,就可以了。所以,对于Show或者Edit页面,基本不用修改,只要修改Main.master就可以了,另外,考虑到我们要加入登录功能,我想用master页来进行鉴权工作,分离出一个新的master页也许更好些,Main.master负责鉴权和部分布局(manage部分也需要banner),而Wiki.master负责Wiki的布局,在Main.master里加入对于css和js的链接,并调用jquery layout进行初始化,它还会调用Wiki.master里的resetLayout以完成Wiki页的layout工作:
代码
<%@ Master Language="C#" Inherits="Lephone.Web.SmartMasterPageBase" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<link type="text/css" href="styles/BasicStyle.css" rel="stylesheet" />
<script type="text/javascript" src="scripts/jquery-1.2.6.js"></script>
<script type="text/javascript" src="scripts/jquery.layout.js"></script>
<script type="text/javascript">
function resetMainLayout() {
$('form').layout({ slidable: false, resizable: false, closable: false,
spacing_open: 0, north__size: 52, center__paneSelector: "#mainContent"
});
if (resetLayout)
resetLayout();
}
$(document).ready(function() { resetMainLayout(); });
$(window).resize(function() { resetMainLayout(); }); // for resize bug with form
</script>
<asp:ContentPlaceHolder id="head" runat="server">
</asp:ContentPlaceHolder>
</head>
<body>
<form id="form1" runat="server">
<div class="ui-layout-north banner">
</div>
<div class="ui-layout-center">
</div>
<asp:ContentPlaceHolder id="ContentPlaceHolder1" runat="server">
</asp:ContentPlaceHolder>
</form>
</body>
</html>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<link type="text/css" href="styles/BasicStyle.css" rel="stylesheet" />
<script type="text/javascript" src="scripts/jquery-1.2.6.js"></script>
<script type="text/javascript" src="scripts/jquery.layout.js"></script>
<script type="text/javascript">
function resetMainLayout() {
$('form').layout({ slidable: false, resizable: false, closable: false,
spacing_open: 0, north__size: 52, center__paneSelector: "#mainContent"
});
if (resetLayout)
resetLayout();
}
$(document).ready(function() { resetMainLayout(); });
$(window).resize(function() { resetMainLayout(); }); // for resize bug with form
</script>
<asp:ContentPlaceHolder id="head" runat="server">
</asp:ContentPlaceHolder>
</head>
<body>
<form id="form1" runat="server">
<div class="ui-layout-north banner">
</div>
<div class="ui-layout-center">
</div>
<asp:ContentPlaceHolder id="ContentPlaceHolder1" runat="server">
</asp:ContentPlaceHolder>
</form>
</body>
</html>
Wiki.master里增加的也基本上是一些div和一个resetLayout函数:
代码
<%@ Master Language="C#" MasterPageFile="~/Main.master" Inherits="Lephone.Web.SmartMasterPageBase" %>
<script runat="server">
[HttpParameter] public string title;
[HttpParameter(AllowEmpty = true)] public string path;
protected void Page_Load(object sender, EventArgs e)
{
NavigationBar.Text = CommonHelper.GetNavigationBar(title, path);
var pageName = Request.Url.Segments[2];
CommonHelper.SetLink(Edit, pageName, "Edit.aspx", title, path);
CommonHelper.SetLink(History, pageName, "History.aspx", title, path);
}
</script>
<asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server">
<script type="text/javascript">
function resetLayout() {
$('#mainContent').layout({ slidable: false, resizable: false, closable: false, spacing_open: 0, north__maxSize: 31, south__maxSize: 27 });
}
</script>
<asp:ContentPlaceHolder id="head" runat="server">
</asp:ContentPlaceHolder>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
<div id="mainContent">
<div class="ui-layout-north header">
<div id="navigator">
<asp:Label ID="NavigationBar" runat="server" Text="Home"></asp:Label>
</div>
<div id="oprator">
<asp:HyperLink ID="Edit" runat="server">Edit</asp:HyperLink> |
<asp:HyperLink ID="History" runat="server">History</asp:HyperLink>
</div>
</div>
<div class="ui-layout-center body">
<asp:ContentPlaceHolder id="ContentPlaceHolder1" runat="server">
</asp:ContentPlaceHolder>
</div>
<div class="ui-layout-south footer">
<biz:NoticeLabel ID="msg" runat="server" SingleLine="true" />
copyright © Lephone Studio
</div>
</div>
</asp:Content>
<script runat="server">
[HttpParameter] public string title;
[HttpParameter(AllowEmpty = true)] public string path;
protected void Page_Load(object sender, EventArgs e)
{
NavigationBar.Text = CommonHelper.GetNavigationBar(title, path);
var pageName = Request.Url.Segments[2];
CommonHelper.SetLink(Edit, pageName, "Edit.aspx", title, path);
CommonHelper.SetLink(History, pageName, "History.aspx", title, path);
}
</script>
<asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server">
<script type="text/javascript">
function resetLayout() {
$('#mainContent').layout({ slidable: false, resizable: false, closable: false, spacing_open: 0, north__maxSize: 31, south__maxSize: 27 });
}
</script>
<asp:ContentPlaceHolder id="head" runat="server">
</asp:ContentPlaceHolder>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
<div id="mainContent">
<div class="ui-layout-north header">
<div id="navigator">
<asp:Label ID="NavigationBar" runat="server" Text="Home"></asp:Label>
</div>
<div id="oprator">
<asp:HyperLink ID="Edit" runat="server">Edit</asp:HyperLink> |
<asp:HyperLink ID="History" runat="server">History</asp:HyperLink>
</div>
</div>
<div class="ui-layout-center body">
<asp:ContentPlaceHolder id="ContentPlaceHolder1" runat="server">
</asp:ContentPlaceHolder>
</div>
<div class="ui-layout-south footer">
<biz:NoticeLabel ID="msg" runat="server" SingleLine="true" />
copyright © Lephone Studio
</div>
</div>
</asp:Content>
现在,我们开始设计登录及成员管理功能。DbEntry也支持ASP.Net的MemberShip功能,并且使之可以支持所有DbEntry支持的数据库,不过这里,我们并不使用MemberShip,而是自己设计一个用户表SysUser,其中,password应该是hash的,所以CommonHelper中需要增加一个生成password的函数:
代码
private static readonly SHA512 Hash = SHA512.Create();
public static string GetHashedPassword(string password)
{
var bytes = Hash.ComputeHash(Encoding.UTF8.GetBytes(password));
var ret = Base32StringCoding.Decode(bytes);
return ret;
}
public static string GetHashedPassword(string password)
{
var bytes = Hash.ComputeHash(Encoding.UTF8.GetBytes(password));
var ret = Base32StringCoding.Decode(bytes);
return ret;
}
SysUser如下:
代码
namespace VisualWiki.Models
{
public enum UserRole
{
Administrator,
User,
}
public abstract class SysUser : LinqObjectModel<SysUser>
{
[Length(1, 50), Index(UNIQUE = true)]
public abstract string Name { get; set; }
[Length(1, 128)]
public abstract string Password { get; set; }
public abstract UserRole Role { get; set; }
public SysUser Init(string name, string password, UserRole role)
{
Name = name;
Password = CommonHelper.GetHashedPassword(password);
Role = role;
return this;
}
public static SysUser GetUserForLogin(string name, string password)
{
var pass = CommonHelper.GetHashedPassword(password);
var u = FindOne(p => p.Name == name);
if (u != null && u.Password != pass)
{
return null;
}
return u;
}
}
}
{
public enum UserRole
{
Administrator,
User,
}
public abstract class SysUser : LinqObjectModel<SysUser>
{
[Length(1, 50), Index(UNIQUE = true)]
public abstract string Name { get; set; }
[Length(1, 128)]
public abstract string Password { get; set; }
public abstract UserRole Role { get; set; }
public SysUser Init(string name, string password, UserRole role)
{
Name = name;
Password = CommonHelper.GetHashedPassword(password);
Role = role;
return this;
}
public static SysUser GetUserForLogin(string name, string password)
{
var pass = CommonHelper.GetHashedPassword(password);
var u = FindOne(p => p.Name == name);
if (u != null && u.Password != pass)
{
return null;
}
return u;
}
}
}
然后在DataSources里增加SysUserDataSource,同样也不需要有任何代码。然后增加一个使用Main.master的Manage.master页,其中有两个链接,User List和New User。在Main.master的banner上,增加一个Label,显示一下欢迎信息。
在VisualWiki项目下,增加使用Manage.master的UserList.aspx,向其中添加GridView,SysUserDataSource,并设置GridView的DataSource为SysUserDataSource,添加Role列(这是GridView的bug,不能识别enum列),另外,添加一个HyperLinkField,用来提供一个编辑链接:
<asp:HyperLinkField Text="Edit" DataNavigateUrlFields="Id" DataNavigateUrlFormatString="~/UserEdit.aspx?ID={0}" />
UserList.aspx页就开发完成了。UserEdit.aspx页也和DbEntry.Net主页介绍的编辑页编写流程类似,使用TemplateBuilder可以在几分钟之内完成。这里,我们对于Edit方式,设置Name控件为不可修改,另外,有一个和普通的编辑页面不同的需求,就是Password需要是hash的,所以,在SysUserDataSource 的OnObjectUpdating和OnObjectInserting事件中处理:
代码
protected void SysUserDataSource1_OnObjectUpdating(SysUser o)
{
o.Password = CommonHelper.GetHashedPassword(o.Password);
}
protected void SysUserDataSource1_OnObjectInserting(SysUser o)
{
o.Password = CommonHelper.GetHashedPassword(o.Password);
}
{
o.Password = CommonHelper.GetHashedPassword(o.Password);
}
protected void SysUserDataSource1_OnObjectInserting(SysUser o)
{
o.Password = CommonHelper.GetHashedPassword(o.Password);
}
这样,用户管理页面基本开发完毕。我们再增加一个Login页面,这个Login页面不使用任何母板页,在Page_Load里,如果不是PostBack,则进行SignOut操作,而点击SignIn按钮,则调用SysUser.GetUserForLogin验证登录合法性。登录成功,则转向Default.aspx页,而Default.aspx也需要修改一下,根据登录用户的不同类型,重定向到不同的页面:
代码
protected void Page_Load(object sender, EventArgs e)
{
if (this.GetLoginUser().Role == UserRole.Administrator)
{
Response.Redirect("UserList.aspx");
}
else
{
Response.Redirect(new UrlBuilder("Show.aspx").Add(Const.TitleName, Const.HomeName).ToString());
}
}
{
if (this.GetLoginUser().Role == UserRole.Administrator)
{
Response.Redirect("UserList.aspx");
}
else
{
Response.Redirect(new UrlBuilder("Show.aspx").Add(Const.TitleName, Const.HomeName).ToString());
}
}
这里,我在WebUIExtends里建立了几个扩展方法,用来协助用户登录操作,比如GetLoginUser、SetLoginUser等等:
public static SysUser GetLoginUser(this Page p)
{
return (SysUser)p.Session["LoginUser"];
}
public static void SetLoginUser(this Page p, SysUser u)
{
p.Session["LoginUser"] = u;
}
{
return (SysUser)p.Session["LoginUser"];
}
public static void SetLoginUser(this Page p, SysUser u)
{
p.Session["LoginUser"] = u;
}
然后修改Main.master,重载OnInit函数(之所以不是写在Page_Load中,在于ASP.Net在母板页、普通页及控件中奇怪的事件加载顺序),进行用户登录与否的判断,修改Manage.master和Wiki.master,在Page_Load函数里判断登录对象类型,如果类型错误,则返回Login页面,另外,修改web.config中的authentication节,定义登录页面和缺省页面:
<authentication mode="Forms">
<forms name=".ADUAUTH" loginUrl="Login.aspx" defaultUrl="Default.aspx" protection="None"/>
</authentication>
<forms name=".ADUAUTH" loginUrl="Login.aspx" defaultUrl="Default.aspx" protection="None"/>
</authentication>
现在,运行程序,我们会被重导向到Login页面,不过,我们还没有可以用来登录的用户,让我们修改一下Global.asax,以便为我们增加一个管理员:
void Application_Start(object sender, EventArgs e)
{
if(SysUser.GetCount(null) == 0)
{
SysUser.New.Init("tom", "123", UserRole.Administrator).Save();
}
}
{
if(SysUser.GetCount(null) == 0)
{
SysUser.New.Init("tom", "123", UserRole.Administrator).Save();
}
}
现在,运行程序,会被带入Login页面,输入tom/123登录,进入管理界面,点击New User链接以增加一个Wiki用户jerry/456,点击Logout退出登录,再使用jerry登录,就可以进入Wiki浏览、编辑界面了。
这样,用户管理界面也基本开发完毕,本来也以为这应该是这个系列文章的最后,不过,我们还需要处理其它一些细节,所以,提交代码、未完待续……
目前的代码:VisualWiki6.7z