
C# ПІДРУЧНИКИ / c# / Hungry Minds - ASP.NET Bible VB.NET & C#
.pdfNew OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;" & _
"Data Source=c:\inetpub\wwwroot\Discuss\DiscussDB.mdb")
In addition, a command object that holds a SQL Insert statement is created:
InsertString = "Insert Into Messages (Subject, Message, " & _
"Author, Posted, Thread) VALUES (@Subject, @Message, " & _
"@Author, @Posted, @ThreadID)"
MessageCommand = _
New OleDbCommand(InsertString, MessageConnection)
However, you'll notice that the data to be filled in isn't concatenated to the string that contains the Insert statement. Instead of using concatenation as I did in the Select statement of the BindData subroutine (and in the Select statement used in the Threads page), I decided, here, to use parameters.
Using parameters
Parameters enable you to identify several placeholders in your SQL statement that are to be filled in later. The placeholders always begin with an @ sign so that you don't miss them or confuse them with columns from your table.
After the command object is created, you add new OleDbParameter objects to the Parameters collection of the command — one for each placeholder in your SQL statement:
MessageCommand.Parameters.Add( _
New OleDbParameter("@Subject", OleDbType.Varchar,50))
MessageCommand.Parameters.Add( _
New OleDbParameter("@Message", OleDbType.LongVarchar))
MessageCommand.Parameters.Add( _
New OleDbParameter("@Author", OleDbType.Varchar,50))
MessageCommand.Parameters.Add( _
New OleDbParameter("@Posted", OleDbType.Varchar,50))
MessageCommand.Parameters.Add( _
New OleDbParameter("@ThreadID", OleDbType.Varchar,50))
When you instantiate the OleDbParameter, you pass the name of the placeholder and the data type of the information (using the OleDbType enumeration object).
Finally, after you have created all the parameters, you can fill them in with their appropriate values:
MessageCommand.Parameters("@Subject").Value = _
SubjectText.Text
MessageCommand.Parameters("@Message").Value = Message
MessageCommand.Parameters("@Author").Value = AuthorText.Text
MessageCommand.Parameters("@Posted").Value = CStr(Now)
MessageCommand.Parameters("@ThreadID").Value = _
CInt(Request.QueryString("ThreadID"))
...
MessageConnection.Open()
MessageCommand.ExecuteNonQuery
MessageConnection.Close()
Why did I go to all of this trouble? Wouldn't it be simpler to just concatenate the values into an Insert statement? Well, with this many parameters, the concatenation code would get pretty confusing. So although this may take a little more code, it is, in the end, easier to read and understand.
But that's not the best reason to use parameters. Suppose the user enters a subject that looks like this:
Can't afford the migration cost...
Notice the apostrophe in Can't. When you concatenate strings, VB.NET will interpret any apostrophes as a single quote and will think you are starting a substring, which you didn't finish. Double quotes in the string cause even worse problems. Fortunately, using parameters to fill in your SQL statement avoids all of these problems. Your quotes will come out looking just as they were typed.
Getting hard returns right
There's a couple of lines I skipped as I was describing the Messages page in the previous sections:
Message = MessageText.Text
Message = Replace(Message, CStr(chr(13)), "<br>")
When the user presses the return key inside the multiline edit box, it creates a hard return in the text, just as you'd expect it to. This is a valid character and it is saved along with the rest of the message in the database just fine. But when it is retrieved and displayed, the browser ignores hard returns. So, to be sure the text displayed in the browser looks like the text the user entered, it's necessary to do a little replacement. That's what the preceding lines do.
Preparing to return the updated page
After the user has entered a new message, and clicked Post, the new message is added to the database and the updated Messages page appears. To make that happen, you have to do two things: clear out the form fields and re-retrieve the messages from the database:
SubjectText.Text = ""
MessageText.Text = ""
AuthorText.Text = ""
BindData
Creating a New Thread
One more page is needed to make this application complete: a link from the Threads page that allows the user to create a new thread. This link sends them to the NewThread page.
<%@ Page Explicit="True" Language="VB" Debug="True" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.OleDb" %>
<Script Runat="Server">
Sub PostClick( obj As Object, e As EventArgs )
Dim Connection As OleDbConnection
Dim ThreadCommand As OleDbCommand
Dim GetThreadCommand As OleDbCommand
Dim MessageCommand As OleDbCommand
Dim ThreadReader As OleDbDataReader
Dim InsertThread, InsertMessage As String
Dim GetThread, Message as string
Dim ThreadID As Long
Dim ThreadResult As Integer
Connection = New OleDbConnection( _
"Provider=Microsoft.Jet.OLEDB.4.0;" & _
"Data Source=c:\inetpub\wwwroot\Discuss\DiscussDB.mdb")
Feedback.Text = ""
InsertThread = "Insert Into Threads (Name, Topic) " & _
"VALUES (@ThreadName,@TopicID)"
ThreadCommand = New OleDbCommand(InsertThread, Connection)
ThreadCommand.Parameters.Add( _
New OleDbParameter("@ThreadName", OleDbType.Varchar,50))
ThreadCommand.Parameters.Add( _
New OleDbParameter("@TopicID", OleDbType. Integer))
ThreadCommand.Parameters("@ThreadName").Value = _
ThreadText.Text
ThreadCommand.Parameters("@TopicID").Value = _
Request.QueryString("TopicID")
Try
Connection.Open()
ThreadResult = ThreadCommand.ExecuteNonQuery Catch excp As System.Data.OleDb.OleDbException
If excp.Errors(0).NativeError = -105121349 Then
Feedback.Text = "<font color=red>*** There " & _
"is already a thread with the name <b>" & _ ThreadText.Text & "</b>. Please choose a " & _ "different name and click Post.</font><br><br>"
Else
Feedback.Text = "<font color=red>*** " & _
Response.Redirect("Threads.aspx?TopicID=" & _
Request.QueryString("TopicID") & _
"&TopicName=" & Request.QueryString("TopicName"))
End Sub
</Script>
<html>
<head><title>Chatty New Thread</title></head> <body>
<font face="arial">
<h1><font color="DarkRed">Chatty Discussion Forum</font></h1> <font size="5">Create New <u> <%=Request.QueryString("TopicName") %></u> Thread</font> <form Runat="Server">
<table width="100%" border="1" cellspacing="0" cellpadding="3"
bordercolor="DarkBlue"> <tr><td bgcolor="LightGrey">
<p>To create a new thread, enter the name of the thread and the information for the first message you'd like
to post to the thread. If you wish to cancel, simply click one of the links at the bottom of this page.</p>
<asp:label id="Feedback" runat="server" /> <p><font size="2">New Thread Name:</font><br/> <asp:textbox id="ThreadText" runat="server" columns="50"/></p>
<p><font size="2">Subject:</font><br/> <asp:textbox id="SubjectText" runat="server" columns="50"/></p>
<p><font size="2">Message:</font><br /> <asp:textbox id="MessageText" runat="server" textmode="multiline"
columns="50" rows="3"/></p>
<p><font size="2">Your Email Address:</font><br /> <asp:textbox id="AuthorText" runat="server" columns="50"/></p>
<center>
<asp:button runat="server" text=" Post " onclick="PostClick" />
</center>
</td></tr></table>
<br />
<a href="threads.aspx?TopicID=
<%=Request.QueryString("TopicID")%>
&TopicName=<%=Request.QueryString("TopicName")%>">
Return to <b><%=Request.QueryString("TopicName") %>
</b> Threads</a><br>
<a href="topics.aspx">
Return to list of <b>Topics</b></a>
</form>
</font>
</body>
</html>
The NewThread form
Unlike the other pages in this application, NewThread does not retrieve anything from the database when it is first opened. Instead, it presents the user with a form that looks very much like the form at the bottom of the Messages page:
<asp:label id="Feedback" runat="server" />
<p><font size="2">New Thread Name:</font><br/>
<asp:textbox id="ThreadText" runat="server"
columns="50"/></p>
<p><font size="2">Subject:</font><br/>
<asp:textbox id="SubjectText" runat="server"
columns="50"/></p>
<p><font size="2">Message:</font><br />
<asp:textbox id="MessageText" runat="server"
textmode="multiline"
columns="50" rows="3"/></p>
<p><font size="2">Your Email Address:</font><br />
<asp:textbox id="AuthorText" runat="server"
columns="50"/></p>
<center>
<asp:button runat="server" text=" Post "
onclick="PostClick" />
</center>
The user is here to create a new thread. However, it doesn't make much sense to create a new thread without also posting a message to it. So to save the user the trouble, this page allows the user to do both at once.
NewThread's PostClick: a big job
After the user has entered the thread name and the first message for the thread, he or she clicks the Post button. The Post button causes the page to return to the server and execute the PostClick subroutine.
PostClick has a number of responsibilities. It must do the following:
§Open a connection to the database.
§Insert a new row in the Threads table using the name the user entered and associating it with the current topic.
§Retrieve the newly created thread's ThreadID (which is assigned to it automatically by Microsoft Access).
§Insert a new row in the Message table using the rest of the information the user entered in the form. That message must be associated with the newly created thread, using the retrieved ThreadID.
Threading a new row
Aside from the primary key, the Threads table has only two columns: the thread name and the topic the thread is associated with. I use parameters to fill in these values:
InsertThread = "Insert Into Threads (Name, Topic) " & _
"VALUES (@ThreadName,@TopicID)"
ThreadCommand = New OleDbCommand(InsertThread, Connection)
ThreadCommand.Parameters.Add( _
New OleDbParameter("@ThreadName", OleDbType.Varchar,50))
ThreadCommand.Parameters.Add( _
New OleDbParameter("@TopicID", OleDbType.Integer))
ThreadCommand.Parameters("@ThreadName").Value = _
ThreadText.Text
ThreadCommand.Parameters("@TopicID").Value = _
Request.QueryString("TopicID")
Watch for duplicates
When I created this database, I didn't want the user creating threads with the same name, so I created an index on the Threads table's Name column that didn't allow duplicates. But because I did that, I now have to check for that possibility in my code. That's made relatively painless with VB.NET's new error-trapping capabilities:
Try
Connection.Open()
ThreadResult = ThreadCommand.ExecuteNonQuery
Catch excp As System.Data.OleDb.OleDbException
If excp.Errors(0).NativeError = -105121349 Then
Feedback.Text = "<font color=red>*** There " & _
"is already a thread with the name <b>" & _
ThreadText.Text & "</b>. Please choose a " & _
"different name and click Post.</font><br><br>"
Else
Feedback.Text = "<font color=red>*** " & _
excp.Errors(0).Message & "<br><br>"
End If
End Try
The Try block opens the connection and executes the query. If a database exception is raised, I check to see if the error is because a duplicate row exists. I use the NativeError property to see the raw error number returned from the database.
SubjectText.Text
MessageCommand.Parameters("@Message").Value = Message
MessageCommand.Parameters("@Author").Value = AuthorText.Text
MessageCommand.Parameters("@Posted").Value = CStr(Now)
MessageCommand.Parameters("@ThreadID").Value = ThreadID
MessageCommand.ExecuteNonQuery
Connection.Close()
Response.Redirect("Threads.aspx?TopicID=" & _
Request.QueryString("TopicID") & _
"&TopicName=" & Request.QueryString("TopicName"))
End Sub
After the thread is created and the message is posted, the user is sent back to the Threads page so that they can see the newly created thread in the list. If they prefer, they can click the thread and see their message at the top of the list.
Ideas for Enhancement
The Chatty Discussion Forum is a simple application. It could be enhanced or expanded to meet your needs in many ways. Here are a few ideas that I thought of:
§In its current form, Chatty offers no way to add, update, or delete topics. Because this is a site administrator task, it's assumed that they'll simply use Microsoft Access to go in and add the topics they want when they first begin using the application. But it would be handy if there was a topic maintenance page that simplified the task. Keep in mind that deleting a topic means you need to delete all the threads and all the threads' messages!
§Likewise, Chatty has no way to delete or archive old threads and messages. Again, this can be done by hand in Access, but if you begin to have a lot of active discussions going on, it'd be nice if there were maintenance pages that made the task even easier.
§While you're making maintenance easier, you might want to consider automating some of the tasks. For example, you might want to automatically archive any thread and its messages that hasn't been posted to in over a month.
§If you have trouble with people posting off-topic or inappropriate messages, you might want to put newly entered messages into a holding bin to be approved by a site administrator before they are posted.
§Another way to combat the problem of inappropriate postings is to require
users to log in to your site or at least log in to the discussion forum application before they post a new message. This provides you with more information on who the user is and enables you to cancel their account if they act antisocially.
§Provide a new page (or modify the Threads page) so that you can see all the Subject lines of all the messages in a thread. Then, allow the user to click one of the Subject lines to jump directly to that message on the Messages page.
Summary
A discussion forum is a very practical application. It can be used on virtually any Web site where you want to enhance the sense of community. In addition, this application has demonstrated a number of important ASP.NET techniques. Some of the techniques are creating a hierarchical relationship among tables in a relational database; connecting to and retrieving data from a database using ADO.NET; displaying data with the Repeater and DataList server controls using data-binding; using an ItemTemplate inside a repeater to format repeating data; creating new rows in a database table; connecting new rows to an exiting row in another table using appropriate foreign keys; and using parameters in queries — as well as when and why it's important.
Appendix A: Visual Basic Syntax
Overview
Welcome to the start of a whole new adventure for both Microsoft and yourself. The
.NET series of application development tools and its accompanying office suite has the potential to take you much further and much faster in productivity than you have gone before. The integration of these tools has become much tighter and thus more efficient than in the past. Because this is a book on Web application development with ASP.NET supplemented by VB.NET, you will first be taken on a tour through the VB.NET development environment. This will give you the foundation required for you to be a topnotch .NET developer. Then you will be taken on a tour of all the pieces of the construction of an application, from designing an MDI (Multiple Document Interface) application to understanding data types and defining variables, and finally to controlling the logical flow of an application.
Brief Tour of the GUI
As an application developer, it is the world of the GUI IDE (graphical user interface, integrated development environment) in which you will spend most of your time. VB.NET, although new and improved over VB 6.0, is still a GUI IDE and therefore will take some getting used to if you are new to software development. You will now take a brief tour of this IDE. You will see the highlights along the way of what is new or may have changed in look, feel, or functionality. The IDE, for the most part, looks similar to that of VB 6.0, so for those developers who are seasoned in VB 6.0, finding your way around this IDE shouldn't be very difficult.
It is assumed here that you have successfully installed at least the basic feature set of the VB portion of the .NET development suite.
Note For more detail on the installation process, refer to this book's introduction.
As you take this tour through the IDE, you will build a small mailing list system, which will enable you to see VB.NET's productivity at an early stage.
Figure A-1 shows the first screen of the VB.NE T GUI upon startup, with the major parts identified.