JumpyForum: 受 Code Project 论坛 / 讨论 /留言板启发






4.87/5 (95投票s)
2006年12月15日
6分钟阅读

457060

5767
一个非常简单的论坛,其功能类似于 Code Project 讨论板,用户可以在树状视图中查看多条消息,使用 C#, ASP.NET, SQL Server 和 JavaScript 实现。(现在符合 W3C HTML 4.01 标准)
引言
本文介绍了一个受 Code Project 论坛启发并与之类似的讨论板。我相信,你们都会同意 Code Project 上的讨论板是最好的之一。我曾尝试在 CP 和其他地方搜索类似的项目,但所有给出的示例都是用经典 ASP/PHP 编写的,而且非常复杂。我的努力是利用 ASP.NET、JavaScript 和 SQL Server 创建一个非常简单易懂的论坛,并具备相同的功能。
在你们兴致勃勃之前,我只想提醒你们关于 JumpyForum 的优势,这是一个非常简单的(可以说是初学者级别)版本,你们可能需要考虑更多才能真正拥有一个具有 CP 级别质量、可扩展性和耐用性的论坛。JumpyForum 只会为您提供一个简单且可行的起点,之后,广阔的天地由你们去探索。
目标是创建一个像 Code Project 一样好(甚至更好)的简单且可扩展的讨论板/论坛系统
- 可用性:用户可以添加类型为/新闻/一般/笑话和问题类型的评论。其他用户可以以一般/新闻/问题/笑话或回复的形式进行回复
- 丰富的显示:论坛显示应保留层次结构和每条评论后的日期/时间
- 可重用性:论坛的全部功能应易于插入任何表以供重用
- 一致性:如果一条消息被删除,所有后续的子消息也应被层级删除
- 可扩展性:论坛应易于扩展到不同的文章或不同的论坛
- 性能:评论的显示不应花费太多时间
* 免责声明:它看起来确实像 Code Project 论坛,但只声称这是实现它的方式之一。
实际应用
为了引起你们的兴趣,这是完成后的样子。在此处查看 演示。
假设
这些是最初的想法,但为了使其成为一个通用、可重用且可扩展的模块,我做了一些假设
- 我们在 SQL Server 中有一个表 CP_Forum_Comments(参见下面的截图)
- 该表将包含如图所示的字段,包括
ArticleID
(文章表的外部键)。这样做的想法是为每篇文章提供不同的论坛。Article ID 可以更改为ForumId
或任何你想要的名称。你可以根据自己的需求进行完全扩展。 - 其他字段基本不言自明。
ParentID
是树状视图中评论的父 ID(回复的消息的 ID)。 Indent
字段是归因于 JumpyForum 中消息缩进的关键字段。
评论表结构 CP_FORUMS_COMMENT
(创建表 SQL 查询已附加在源代码中)
Actions
这些是使整个模块工作的操作
- 数据访问
- 论坛显示和用户界面
- 论坛的层级存储过程(核心)
- 添加评论
- 添加回复
- 删除评论并层级删除(此功能目前在 CodeProject 论坛中不存在,这就是为什么有时我们会看到孤立的消息,如果父消息被删除)
数据访问类
有一个标准的DataAccess类,clsDataAccess.cs,它处理所有数据相关的操作。
代码
这里的主要函数是 getForumData
,它调用存储过程。为了让你一窥数据访问方法,我只保留了其他函数的名称。
using
public class clsDataAccess { public
clsDataAccess()
{ } SqlConnection mycon = new
SqlConnection( ConfigurationSettings.AppSettings["ConnectionString"]);
//Opens database connection in SQL SERVER
public bool openConnection()
public void closeConnection()
public SqlDataReader getForumData(int ArticleId)
{
SqlCommand sqlCommand = new SqlCommand();
sqlCommand.CommandType= CommandType.StoredProcedure;
sqlCommand.CommandText = "ShowHierarchyForum";
SqlParameter newSqlParam = new SqlParameter();
newSqlParam.ParameterName = "@ArticleId";
newSqlParam.SqlDbType = SqlDbType.Int ;
newSqlParam.Direction = ParameterDirection.Input;
newSqlParam.Value = ArticleId;
sqlCommand.Parameters.Add(newSqlParam);
SqlParameter newSqlParam2 = new SqlParameter();
newSqlParam2.ParameterName = "@Root";
newSqlParam2.SqlDbType = SqlDbType.Int ;
newSqlParam2.Direction = ParameterDirection.Input;
newSqlParam2.Value = 0;
sqlCommand.Parameters.Add(newSqlParam2);
sqlCommand.Connection=mycon;
SqlDataReader myr = sqlCommand.ExecuteReader
(CommandBehavior.CloseConnection);
return myr;
}
}
}
为了显示,我们需要以下图像。我从 CodeProject 拿来的,虽然这些图像在互联网上也很容易找到。
论坛显示和用户界面
首先,我们假设我们正在以我们需要的确切顺序获取所有记录(由嵌套的层级存储过程处理),并在论坛页面上显示它们。考虑论坛的动态显示和跳转功能。
我们在这里做的是显示与文章相关的所有记录,然后隐藏除标题外的所有记录。当用户点击一条消息时,该特定消息将被显示,其他消息将被隐藏。一个简单的 JavaScript 函数可以通过 on
/off
状态来实现这一点。这是神奇的 JavaScript:
function OnOffPost(e)
{
if ( !e ) e = window.event;
var target = e.target ? e.target : e.srcElement;
while ( target && target.id != 'LinkTrigger' )
target = target.parentNode;
if ( !target || target.id != 'LinkTrigger' )
return;
if (Selected)
{
var body = document.getElementById(Selected + "ON");
if (body)
body.style.display = 'none';
var head = document.getElementById(Selected + "OFF");
if (head)
head.bgColor = '#EDF8F4';
}
if (Selected == target.name) // just collapse
Selected="";
else
{
Selected = target.name;
var body = document.getElementById(Selected + "ON");
if (body)
{
if (body.style.display=='none')
body.style.display='';
else
body.style.display = 'none';
}
var head = document.getElementById(Selected + "OFF");
if (head)
head.bgColor = '#B7DFD5';
if ( body && head && body.style.display != 'none' )
{
document.body.scrollTop = FindPosition(head, "Top") -
` document.body.clientHeight/10;
OpenMessage(target.name, true);
}
}
if ( e.preventDefault )
e.preventDefault();
else
e.returnValue = false;
return false;
}
更多 JavaScript 函数用于打开消息并查找消息的位置
function OpenMessage(msgID, bShowTop) {
var msgHeader = document.getElementById(msgID + "OFF");
var msgBody = document.getElementById(msgID + "ON");
var MyBody = document.body;
var top = FindPosition(msgHeader, 'Top');
var bottom = FindPosition(msgBody, 'Top') + msgBody.offsetHeight;
if ( MyBody.scrollTop > top && !bShowTop)
MyBody.scrollTop = top - document.body.clientHeight/10;
if ( MyBody.scrollTop+MyBody.clientHeight < bottom )
MyBody.scrollTop = bottom-MyBody.clientHeight;
if ( MyBody.scrollTop > top && bShowTop)
MyBody.scrollTop = top - document.body.clientHeight/10;
}
function FindPosition(i,which)
{
iPos = 0
while (i!=null)
{
iPos += i["offset" + which];
i = i.offsetParent;
}
return iPos
}
JavaScript On
/Off
函数和 JavaScript 本身一样古老,不需要太多解释。你只需要将元素 ID 作为参数传递给点击事件,该事件会调用方法来更改该 ID 的显示。它已包含在源代码中。
用户界面中显示的数据
为了在页面上显示存储过程结果的所有记录,我们需要两样东西:一个 reader.read
循环和一个 stringbuilder
来创建带有所有消息的整个页面。以下是我们处理 time
、message
type
和 indent
的方式,其余的只是与 reader 中的数据一起附加到 string builder。
while (myReader.Read())
{
DateTime dt1 = DateTime.Now;
DateTime dt2 = Convert.ToDateTime(myReader["DateAdded"].ToString());
TimeSpan ts = dt1.Subtract(dt2);
string mytimeago = "";
if (Convert.ToInt32(ts.TotalDays) !=0) mytimeago = "" +
Math.Abs(Convert.ToInt32(ts.TotalDays))+ " Days ago";
else {
if // dealing with the time
((Convert.ToInt32(ts.TotalMinutes) < 5)&&
(Convert.ToInt32(ts.TotalHours)==0))
{ mytimeago="Just Posted"; }
else
if ((Convert.ToInt32(ts.TotalMinutes) > 5)&&
(Convert.ToInt32(ts.TotalHours)==0))
{
mytimeago = Convert.ToInt32(ts.TotalMinutes) % 60 + " Mins ago";
}
else if(Convert.ToInt32(ts.TotalHours)!=0)
{
mytimeago = "" + Convert.ToInt32(ts.TotalHours) + "
Hours " + Convert.ToInt32(ts.TotalMinutes) % 60 + " Mins ago";
}
else
{
mytimeago = Convert.ToInt32(ts.TotalMinutes) % 60 + " Mins ago";
}
}
// Indentation of the image and the new image.gif
string newimg ="";
if (String.Compare(mytimeago,"Just Posted")==0)
newimg = "<'JumpyForum/new.gif' border=0>";
int myindent = 4;
if (Convert.ToInt32(myReader["Indent"])<=4)
myindent = 16 * Convert.ToInt32(myReader["Indent"]);
else if (Convert.ToInt32(myReader["Indent"])<=8)
myindent = 15 * Convert.ToInt32(myReader["Indent"]) ;
else if (Convert.ToInt32(myReader["Indent"])<=16)
myindent = 14 * Convert.ToInt32(myReader["Indent"]) ;
else if (Convert.ToInt32(myReader["Indent"])<=20)
myindent = Convert.ToInt32(13.5 *
Convert.ToDouble(myReader["Indent"]));
else if (Convert.ToInt32(myReader["Indent"])<=24)
myindent = 13 * Convert.ToInt32(myReader["Indent"]);
else if (Convert.ToInt32(myReader["Indent"])<=28)
myindent = Convert.ToInt32(12.7 *
Convert.ToDouble(myReader["Indent"]));
else if (Convert.ToInt32(myReader["Indent"])<=32)
myindent = Convert.ToInt32(12.4 *
Convert.ToDouble(myReader["Indent"]));
// Message Type
if (Convert.ToInt32(myReader["CommentType"].ToString())==1)
sb.Append("<'JumpyForum/general.gif' align=absMiddle> </TD>");
if (Convert.ToInt32(myReader["CommentType"].ToString())==2)
sb.Append("<'JumpyForum/info.gif' align=absMiddle> </TD>");
if (Convert.ToInt32(myReader["CommentType"].ToString())==3)
sb.Append("<'JumpyForum/answer.gif' align=absMiddle> </TD>");
if (Convert.ToInt32(myReader["CommentType"].ToString())==4)
sb.Append("<'JumpyForum/question.gif' align=absMiddle> </TD>");
if (Convert.ToInt32(myReader["CommentType"].ToString())==5)
sb.Append("<'JumpyForum/game.gif' align=absMiddle> </TD>");
}
现在让我们跳转到论坛的主要部分,即显示层级数据。
论坛的层级存储过程
这是完成此功能的嵌套层级存储过程。我敢打赌,嵌套层级存储过程可能相当令人头疼,但了解细节是有益的。我们传递两个参数:
Articleid
以获取该文章的所有消息,以及Rootid
消息的主键,以获取该消息的所有子消息。
此处有一个“注意事项”,嵌套层级存储过程不会超过 32 个子级别。我们在这里做的是在一个嵌套循环中创建一个临时表,然后在最后抛出临时表的记录。存储过程已附加到源代码中。
CREATE PROC dbo.ShowHierarchyForum
(
@Root int,
@ArticleId int
)
AS
BEGIN
if not exists (select name from [tempdb].[dbo].[sysobjects]
where name like '#YourLocalTempTable%')
create table #YourLocalTempTable (Id int, ParentId int,ArticleId int,
Title nVarchar(250),
username nvarchar(50),UserEmail nvarchar(50),
Description nvarchar(2000),Indent int,
DateAdded datetime,UserProfile nvarchar(100),
CommentType tinyint)
SET NOCOUNT ON
DECLARE @CID int, @PID int, @Title varchar(250)
insert into #YourLocalTempTable SELECT CP_FORUM_Comments.Id ,
ParentId ,ArticleId ,
Title,username ,UserEmail ,Description ,Indent ,
DateAdded ,UserProfile, CommentType
from CP_FORUM_Comments WHERE ID = @Root and ArticleId =
@ArticleId
SET @CID = (SELECT MAX(ID) FROM CP_FORUM_Comments WHERE ParentID = @Root)
WHILE @CID IS NOT NULL
BEGIN
EXEC dbo.ShowHierarchyForum @CID, @ArticleId
SET @CID = (SELECT MAX(ID) FROM CP_FORUM_Comments
WHERE ParentID = @Root
AND ID < @CID and ArticleId = @ArticleId)
END
END
if @@NESTLEVEL =1
select * from #YourLocalTempTable
GO
添加评论
这和任何 Add
记录一样简单。添加新评论时 Parentid = 0
。为了增添一丝品味,我包含了 HTMLAREA 控件。更多关于这个出色控件的信息 在此,由 Fraser Cain 提供。
SqlConnection myC =new SqlConnection();
myC.ConnectionString=ConfigurationSettings.AppSettings["ConnectionString"];
string sqlQuery="INSERT into " +
ConfigurationSettings.AppSettings["CommentTable"] +
"(ParentId,ArticleId,Title,UserName,UserEmail,Description,Indent,UserProfile)
VALUES ('" +mParentId + "','" + mArticleId + "','" + mTitle + "','" +
mUserName + "','" +
mUserEmail + "','" + mDescription + "','" + mIndent + "','" +
"https://codeproject.org.cn/script/profile/whos_who.asp?id=81898" + "')";
myC.Open();
SqlCommand myCommand=new SqlCommand();
myCommand.CommandText=sqlQuery;
myCommand.Connection=myC;
int i=myCommand.ExecuteNonQuery();
myC.Close();
lblStatus.ForeColor = Color.Green ;
lblStatus.Text ="Status: Success";
Response.Redirect("Forum.aspx?id=" + articleid );
添加回复
这里只需要提到一点,parentid
和 indent
是从父消息中获取并相应更新的。
删除评论
同一个层级存储过程被修改为在根消息被删除时层级删除 Forum 中的所有子消息。DeleteHierarchyForum
CREATE PROC dbo.DeleteHierarchyForum
(
@Root int,
@ArticleId int
)
AS
BEGIN
if not exists (select name from [tempdb].[dbo].[sysobjects]
where name like '#YourLocalTempTable%')
create table #YourLocalTempTable (Id int, ParentId int,ArticleId int,
Title nVarchar(250),
username nvarchar(50),UserEmail nvarchar(50),Description nvarchar(2000),
Indent int,DateAdded datetime,UserProfile nvarchar(100))
SET NOCOUNT ON
DECLARE @CID int, @PID int, @Title varchar(250)
insert into #YourLocalTempTable SELECT CP_FORUM_Comments.Id ,
ParentId ,ArticleId ,
Title,username ,UserEmail ,Description ,Indent ,DateAdded ,UserProfile
from CP_FORUM_Comments WHERE ID = @Root and ArticleId = @ArticleId
SET @CID = (SELECT MAX(ID) FROM CP_FORUM_Comments WHERE ParentID = @Root)
WHILE @CID IS NOT NULL
BEGIN
EXEC dbo.DeleteHierarchyForum @CID, @ArticleId
SET @CID = (SELECT MAX(ID) FROM CP_FORUM_Comments
WHERE ParentID = @Root AND ID < @CID and ArticleId = @ArticleId)
END
END
if @@NESTLEVEL =1
Delete from CP_FORUM_Comments where CP_FORUM_Comments.Id in
(select ID from #YourLocalTempTable)
GO
微笑,我们完成了。
Jumpy Forum 运行中:多条记录
文章历史
- 2006 年 12 月 15 日:首次发布
- 2006 年 12 月 18 日:内容更新
- 2007 年 1 月 12 日:支持分页和上次访问
- 2007 年 1 月 17 日:根据用户要求通过 W3C 标准验证
符合 HTML 4.01 Transitional 标准 - 2007 年 1 月 19 日:修复了一个 JavaScript 错误
以及感谢
感谢您一路阅读!希望您觉得这篇文章有用,如果您觉得有用,请给我投票/评论,并请多保重。