
C# ПІДРУЧНИКИ / c# / Hungry Minds - ASP.NET Bible VB.NET & C#
.pdf
Chapter 30: Chatty Discussion Forum
Overview
Part of the promise of the Web was to open a whole new and different communications medium for people to share thoughts and ideas. And while the Web has leveled the playing field, to some degree, for small businesses, in terms of presenting themselves to the public, the average person has been left with little more than a "home page" that no one visits. Communication, to be interesting, has to be two-way, and the Web seems very entrenched in the idea of displaying information, not sharing information.
However, there are ways around this problem. Many sites, particularly those interested in creating more of a community-like atmosphere, have begun providing Web-based discussion forums. These forums allow people to browse categories that interest them and then read the messages other visitors have posted there. They can even post their own messages and carry on a kind of disconnected, two-way conversation. This very compelling feature can attract new visitors and is particularly effective at getting current visitors to return to your site on a very regular basis.
So is it possible to create a discussion forum in ASP.NET? You bet! In fact, you might be surprised at how easy it is. This chapter shows you how to create a simple discussion forum called Chatty, which provides a set of topics that you define and then allows users to create new threads within that topic. Then users can post messages to threads and respond to the posts from other users.
A Quick Walkthrough of Chatty
To give you a better idea of how Chatty Discussion Forum works, I'll provide you with a quick walkthrough of the application, from the user's perspective. I encourage you to pull the application off this book's companion Web site and try it out as I describe it.
The first page is called Topics (see Figure 30-1).
Figure 30-1: The Topics page
Topics simply presents a list of the discussion topics available, from which the user can click whatever looks interesting to them.
After choosing a topic, the user is presented with the Threads page (see Figure 30-2).

Figure 30-2: The Threads page
Here, all the discussion threads under the chosen topic are presented. In addition, a link is provided at the bottom of the page to create a new thread.
If the user clicks the name of an existing thread, they promptly arrive at the Messages page (see Figure 30-3).
Figure 30-3: The Messages page
At the top of the Messages page, you see that the topic and thread chosen appear, so that users never loose track of exactly what messages they are looking at.
Then, each message is listed in its own tidy box, organized in chronological order from the first posting to the most recent.
At the bottom of the Messages page is a form that allows the user to post their own message to this thread (see Figure 30-4).

Figure 30-4: The post message form at the bottom of the Messages page
If the user enters a message and clicks Post, the message is added to the bottom of the list as the most recent posting.
Finally, if you go back to the Threads page (by clicking the link at the bottom of the Messages page), you can click the link at the bottom of the list to create a new thread. This takes you to the NewThread page (see Figure 30-5).
Figure 30-5: The NewThread page
Here, you enter not only the name for the thread you want to create, but also the first message you'd like to appear there. When you click Post, you are taken back to the Threads page to see your new thread listed.
Designing a Chatty Discussion Forum
When you begin to create an application like a discussion forum, it's always a good idea to go out and find similar applications on the Web and try them out. You'll find ideas for design, user interface, features you hadn't considered, and much more. You'll also be able to compare and contrast different approaches and choose which will work best for your own application.

Hierarchical or flat?
If you go out and look at discussion forums, you'll probably be surprised at the variety of different approaches and user interfaces. Some, particularly those designed for a more technical audience, are fully hierarchical. You can respond to any individual message, and your message will appear under that message. Then, other people can respond to that message and appear alongside your message, or they can respond to your message and appear under your message. This is a very detailed way of carrying on conversations and provides the most flexibility to those browsing to either follow a particular line of discussion or ignore it. The problem with a fully hierarchical approach is that it's easy to get confused and not post a message at precisely the right spot. For example, you might want to just add a message to the main discussion, but accidentally post it as a response to the last message you read.
Another approach is to go with a more flattened hierarchy. You are probably going to want to offer discussions under several different topics on your site. So that represents one level. Then, within the topic, there might be several discussions going on at the same time. These separate discussions, or threads, each consist of a string of messages that appears one after the other, in the order that they were posted. You can't post a response to a specific message. You simply post a new message in the same thread. This approach is really clear to the user and works for all levels of user sophistication.
After using both systems, I decided to go with the latter approach. I like it a lot better and, as a bonus, it's more straightforward to create.
Storing the discussions
The next big issue is more of a physical design question. Where do I want to store the posted messages? I could use XML. It makes sense because the data needs to be easily read and displayed but doesn't have heavy update requirements.
Another approach would be to store the information in a database. And then, of course, the question becomes, which database?
In the end, I chose to implement Chatty using an Access database. Some Web hosts have trouble with applications that create and modify text files on their servers, so using XML might limit the places where Chatty could be deployed.
Of course, if you plan to have a high volume of messages posted and users browsing, you might want to consider upgrading to SQL Server, but the bridge between the two is easy to cross in the future, and Access is an easy place to get started.
Creating the Database
Once you've decided to go with a relational database, you're going to have to figure out how you want to organize the information into tables and columns.
Chatty has two levels of organization for its messages:
§Topics are the highest level and are chosen by the creator of the site.
§Threads are essentially subtopics that the user can create.
Messages, then, are always posted by the user to a thread, which in turn is under a topic.
These storage needs can be accommodated with three tables: Topics, Threads, and Messages.
Topics is the highest level, but is also the simplest. All you have to track is the topic's name. Table 30-1 shows the columns and data types.
Table 30-1: The Topics table

|
|
|
|
|
|
Field Name |
|
Data Type |
|
|
|
|
|
|
|
TopicID (Primary Key) |
|
AutoNumber |
|
|
|
|
|
|
|
Name |
|
Text |
|
|
|
|
||
|
The Threads table is a little more complicated, but not much (see Table 30-2). |
|
||
|
Table 30-2: The Threads table |
|
|
|
|
|
|
|
|
|
Field Name |
|
Data Type |
|
|
|
|
|
|
|
ThreadID (Primary Key) |
|
AutoNumber |
|
|
|
|
|
|
|
Name |
|
Text |
|
|
|
|
|
|
|
Topic |
|
Number |
|
|
|
|
|
|
The Topic column holds the TopicID of the row in the Topics table with which this thread is associated.
The Name column holds the name of the thread. It should have some specific attributes set, as specified in Table 30-3.
Table 30-3: The Thread's Name column attributes
Attribute |
|
Value |
|
|
|
Required |
|
Yes |
|
|
|
Allow Zero Length |
|
No |
|
|
|
Indexed |
|
Yes |
|
|
(No |
|
|
Duplica |
|
|
tes) |
By setting these attributes, you assure that whenever a new row is added, the Name field is filled in and that it is filled in with a value that is different from any of the other names already in the table. In other words, all threads must have a unique name.
The same attributes could have been set for the Topics table's Name field, but because it's the site administrator (and not users) who will be filling that table in, you can probably assume that the administrator will assure that the topics are right.
The Messages table has the most columns (see Table 30-4).
|
Table 30-4: The Messages table |
|
|
|
|
|
|
|
|
|
Field Name |
|
Data Type |
|
|
|
|
|
|
|
MessageID (Primary Key) |
|
AutoNumber |
|
|
|
|
|
|
|
Subject |
|
Text |
|
|
|
|
|
|
|
Message |
|
Memo |
|
|
|
|
|
|
|
Author |
|
Text |
|
|
|
|
|
|
|
Posted |
|
Date/Time |
|
|
|
|
|
|
|
Thread |
|
Number |
|
|
|
|
|
|
As in an e-mail message, the Subject line is used to title and summarize the contents of the message itself. The author of the message will be stored in Chatty as the e-mail address they enter. If your site authenticates its users, then you'll be able to put their username or their chosen handle into the Author field instead.

The same attributes could have been set for the Topics table's Name field, but because it's the site administrator (and not users) who will be filling that table in, you can probably assume that the administrator will assure that the topics are right.
The Messages table has the most columns (see Table 30-4).
|
Table 30-4: The Messages table |
|
|
|
|
|
|
|
|
|
Field Name |
|
Data Type |
|
|
|
|
|
|
|
MessageID (Primary Key) |
|
AutoNumber |
|
|
|
|
|
|
|
Subject |
|
Text |
|
|
|
|
|
|
|
Message |
|
Memo |
|
|
|
|
|
|
|
Author |
|
Text |
|
|
|
|
|
|
|
Posted |
|
Date/Time |
|
|
|
|
|
|
|
Thread |
|
Number |
|
|
|
|
|
|
As in an e-mail message, the Subject line is used to title and summarize the contents of the message itself. The author of the message will be stored in Chatty as the e-mail address they enter. If your site authenticates its users, then you'll be able to put their username or their chosen handle into the Author field instead.
The Posted column is important. It enables you to organize the messages in the right order within a thread.
Finally, Thread holds the ThreadID of the row in the Threads table with which this message is associated.
Seeding the Database
In order to test the pages as you create them, you'll want to put some of your own data in the database to get you started. While you're in Microsoft Access creating the tables, go ahead and add a few topics, one or more threads for each topic and a message or two for each thread. Make sure the foreign keys (like the Topic column in the Thread table and the Thread column in the Message table) refer to the primary keys in rows you've already created in the associated tables.
If you decide to use this completed application in your own Web sites, you'll have to use Microsoft Access to enter your topics. I haven't included a page to add or update topics since the discussion topics are usually created once by the Web site administrator and aren't often changed after that. Of course, if you do want easy access to update or add new topics, you can always use the code I've provided here for creating threads as a starting point for a new page that does what you want.
Picking a Topic
When a user visits Chatty, the first decision they have to make is which topic they want to browse. The Topics.aspx page, shown here, provides that opportunity:
<%@ Page Explicit="True" Language="VB"
Debug="True" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.OleDb" %>
<Script Runat="Server">
Sub Page_Load( s As Object, e As EventArgs )
If Not isPostBack Then
Dim TopicConnection As OleDbConnection
Dim TopicCommand As OleDbCommand
TopicConnection = New OleDbConnection( _
"Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & _ "c:\inetpub\wwwroot\Discuss\DiscussDB.mdb")
TopicCommand = New OleDbCommand( _
"Select TopicID, Name from Topics", _
TopicConnection )
TopicConnection.Open()
TopicDataList.DataSource = _
TopicCommand.ExecuteReader()
TopicDataList.DataBind()
TopicConnection.Close()
End If
End Sub
Sub SelectTopic(s As Object, _
e As DataListCommandEventArgs) Dim TopicID,TopicName As String TopicName = e.CommandArgument TopicID = _
TopicDataList.DataKeys.Item( _
e.Item.ItemIndex).toString()
Response.Redirect("Threads.aspx?TopicID=" & _
TopicID & "&TopicName=" & TopicName)
End Sub
</Script>
<html>
<head><title>Chatty Topics</title></head> <body>
<font face="arial"> <h1><font color="DarkRed">
Chatty Discussion Forum</font></h1> <font size=5><i>Topics</i></font> <form Runat="Server">
<asp:DataList id="TopicDataList" cellpadding=10 cellspacing=0 gridlines="both" RepeatColumns="3"
RepeatDirection="Horizontal"
Width="100%" DataKeyField="TopicID"
OnItemCommand="SelectTopic"
Runat="Server">
<ItemTemplate>
<asp:LinkButton
id="TopicLink"
Text='<%# Container.DataItem("Name") %>'
CommandArgument='<%# Container.DataItem("Name") %>'
Runat="Server"/>
</Itemtemplate>
</asp:DataList>
</font>
</form>
</body>
</html>
Retrieving the topics
When the page is first retrieved, a connection is made to the Access database, and a command object is created with a Select statement that retrieves the topic information:
If Not isPostBack Then
Dim TopicConnection As OleDbConnection
Dim TopicCommand As OleDbCommand
TopicConnection = New OleDbConnection( _
"Provider=Microsoft.Jet.OLEDB.4.0;" & _
"Data Source=" & _
"c:\inetpub\wwwroot\Discuss\DiscussDB.mdb")
TopicCommand = New OleDbCommand( _
"Select TopicID, Name from Topics", _
TopicConnection )
TopicConnection.Open()
The Command object's ExecuteReader method is called, which executes the Select statement and then returns the results in the form of a DataReader object. This object is immediately assigned to the DataSource property of the TopicDataList control on this page. Calling the DataBind method of that control assures that the topics retrieved will appear there:
TopicDataList.DataSource = _
TopicCommand.ExecuteReader()
TopicDataList.DataBind()
Displaying the topics
The DataList control is used to quickly and easily display and format the information retrieved from the database:
<asp:DataList id="TopicDataList"
cellpadding=10 cellspacing=0
gridlines="both" RepeatColumns="3"
RepeatDirection="Horizontal"
Width="100%" DataKeyField="TopicID"
OnItemCommand="SelectTopic"
Runat="Server">
<ItemTemplate>
<asp:LinkButton
id="TopicLink"
Text='<%# Container.DataItem("Name") %>'
CommandArgument='<%# Container.DataItem("Name") %>'
Runat="Server"/>
</Itemtemplate>
</asp:DataList>
Most of the attributes specified for the DataList are concerned with how the data will be formatted. There are two important attributes that are not concerned with formatting:
DataKeyField and OnItemCommand. I'll discuss OnItemCommand in the next section. The DataList expects to be associated with a result set, like that retrieved from a Select statement. The DataKeyField attribute identifies the column in the result set that is the key.
The ItemTemplate tag is used to identify how individual items in this list should be displayed. In this case, I only want to display one item, the name of the topic. But I want to display it as a link. The LinkButton control makes this easy. The text is set to the value of the Container's "Name" data item. The container, in this case, is the DataList, and because it is bound to the Topics table result set, this will display the name of the topic. The CommandArgument is set to the same value. This assures that the Topic name is passed to the appropriate subroutine when the user clicks a topic.
Handling topic selection
When the user clicks a topic, the DataList's OnItemCommand attribute tells the control what to do. In this case, it tells it to call the SelectTopic subroutine:
Sub SelectTopic(s As Object, _
e As DataListCommandEventArgs)
Dim TopicID, TopicName As String
TopicName = e.CommandArgument
TopicID =__
TopicDataList.DataKeys.Item( _
e.Item.ItemIndex).toString()
Response.Redirect("Threads.aspx?TopicID=" & _
TopicID & "&TopicName=" & TopicName)
End Sub
The topic name was passed as an argument, which comes in through the e object. That's easy enough to retrieve. The topic ID is a little tougher. The topic ID is in the list of DataKeys (because that column was specified as the DataKeyField for the DataList). So I use that list to retrieve the DataKey at the current ItemIndex. With the topic ID and name in hand, I'm ready to pass control on to another page, Threads.aspx.