Creating a Guest Book


The guest book we're creating will allow users to post entries online. Here is a list of the functionality we're going to provide:

  • They will be able to provide their name, a subject for their messages, and the message itself.

  • Each entry will be logged with the date and time when it was created.

  • The list of entries can be sorted by the user by name, subject, and posted date.

  • The message can have multiple lines, can contain bolding and italics, and will have emoticons converted to graphics.

Create the Guest Book Database Table

First of all, we need to create the data table for the guest book. We're going to call the table GuestBookEntries. We looked at creating tables back in Chapter 8, so we're going to use the same technique here. The columns we are going to need are:

Column Name

Data Type

Properties

Value

GuestBookEntryID

Int

Required:

Yes

Primary Key:

Yes

Auto-increment:

Yes

First Value:

1

Increment:

1

Author

VarChar

Required:

Yes

Field Size:

50

Subject

VarChar

Required:

Yes

Field Size:

100

Message

Text

Required:

No

PostedDate

DateTime

Required:

No

Default:

getdate()

Create the new table with the above columns and properties, using the same CAM database you created in Chapter 8. This gives us enough details to store user comments.

Create Some Guest Book Entries

The first part of our guest book is going to show existing entries, so we need some data in the table. Add the following two rows of data – remember you shouldn't add anything to the ID column, as this will automatically be filled in for you:

Author

Subject

Message

PostedDate

Brian Berry

First Message

I am the first poster. Cool :). I am the man!

10/15/2002 10:33

Nicole Malseed

I'm next

Yes, Brian, you ARE the man!

10/16/2002 8:54

Don't worry if the dates turn into null values. In the early version of Web Matrix this is a bug, as the default value doesn't get inserted. When we enter dates from the web page this will work fine.

Centralizing Database Configuration

Before we go ahead and build the guest book page, it's worth thinking about the rest of our site, and the other pages that access data. All of these will get their data from the same database. Near the end of Chapter 8 we used a connection string to contain the details of our database server, and this connection string was in the actual page. Think about the situation of lots of pages that need the same connection details – you have to have the connection string in each page. There's nothing wrong with that, so don't worry if you've done it elsewhere, but what would happen if you wanted to use a different database? You'd have to edit all of the pages to change the connection string for each one individually, and that would be a pretty repetitive and boring process!

The solution to this is to store the database connection details in a central location. That way, if we want to change it we only need to do so in one place. The location we are going to use is the ASP.NET configuration file, which is called the web.config file. This is an XML text file, and is easy to read and create. However easy it is, Web Matrix makes it easier by creating a template for us, so let's give it a go in the next Try It Out. We will encounter the web.config file again in Chapter 15 and XML when we discuss Web Services in Chapter 16.

Try It Out—Creating a Configuration File

  1. Create a new file in Web Matrix, but instead of choosing an ASP.NET Page as we have been doing for most of the book so far, pick Web.Config from the (General) templates. Keep the file name as web.config (all lowercase) and then click OK to create the file.

  2. Change the first section of the XML file so that it looks like this:

    <?xml version="1.0" encoding="UTF-8" ?> <configuration>   <!--     The <appSettings> section is used to configure application-specific     configuration settings.  These can be fetched from within apps by     calling the "ConfigurationSettings.AppSettings(key)" method:  -->     <appSettings>  <add key="cam"  value="server=(local)\NetSDK;trusted_connection=true;database=cam"/>     </appSettings>   <system.web>

  3. Save the file and close it, and that's it; our application now has a centralized connection string.

How It Works

The configuration file is automatically handled by ASP.NET, and can contain lots of things (we'll see how it can be used for security in the next chapter, when we implement logging into our site and controlling who has access). All we want it for at the moment is to centrally store the database connection, and we can use the <appSettings> section for this. This section allows us to specify a custom string, and then fetch that later from our code. When ASP.NET starts, it reads the configuration file so we can access this from any code in any page.

When the file is created by Web Matrix, some sections are commented out. In XML, comments start with <!-- and end with -->. Looking at the above configuration file, what we've done is to move the comment so that it only surrounds the text, which means the <appSettings> section is now live (and not commented out). We then change the values for key and value, substituting our values for what was there. We've just added the details of our database server (which is the MSDE instance installed with the .NET Framework samples).

Now that we've centralized our database details, let's crack on with creating the guest book page.

Displaying Guest Book Entries

To start with, we'll display the entries on a web page. In the next Try It Out, we're going to create a page from a template that displayed the guest book entries. We'll explore the kind of decisions that you as a programmer will have to make as you begin to develop your own web sites.

Try It Out—Displaying Posted Messages

  1. Create a new Data Page file of type Data Report with Paging and Sorting and name it GuestBook.aspx:

    click to expand

  2. In the code that is generated, you will need to remove the ConnectionString object in the BindGrid() subroutine. The template code suggests we should update it, but since we don't need it any more, you can just delete it. This means the start of this routine (in the Code view, of course) should look like this:

    Sub BindGrid()   Dim CommandText As String
  3. Now find the line where the connection object is declared:

      Dim myConnection As New SqlConnection(ConnectionString)

    and replace the use of connection string with the setting the web.config file, so that the line looks like the following:

      Dim myConnection As New _  SqlConnection(ConfigurationSettings.AppSettings("cam")) 
  4. The next thing we want to do in the BindGrid() subroutine is change the SQL commands. We want to show the author, subject, message, and posted date in the grid. So, we'll change it as shown below:

      ' TODO: update the CommandText value for your application   If SortField = String.Empty Then  CommandText = "select Author, Subject, Message, PostedDate" & _  " from GuestBookEntries order by PostedDate desc"   Else  CommandText = "select Author, Subject, Message, PostedDate" & _  " from GuestBookEntries order by " & SortField   End If

  5. Now save the page. When you run the page for the first time, you should see something like the following:

    click to expand

    Note

    As we pointed out earlier, don't worry if the PostedDate column is empty – it just reflects on the behavior when we inserted these two rows in the first place.

How It Works

The first thing we did was change the way we connect to the database. Instead of using an explicit connection string we now use the connection details stored in the configuration file. To access these we used the following line of code:

  Dim myConnection As New _     SqlConnection(ConfigurationSettings.AppSettings("cam"))

This simply reads in the value for the supplied key – in this case the key is cam. It returns the string from the value part of the configuration setting, and we used that in place of the ConnectionString variable.

Next, we changed the text used to fetch the data from the database. The default template text doesn't fit our database, so we just supplied our own table and column names:

  If SortField = String.Empty Then     CommandText = "select Author, Subject, Message, PostedDate" & _      " from GuestBookEntries order by PostedDate desc"   Else     CommandText = "select Author, Subject, Message, PostedDate" & _      " from GuestBookEntries order by " & SortField   End If

The template that we used automatically allows sorting on columns, and our code caters for this. If no sorting is being done, then the SortField property will be empty, so we pick a default sort order of the PostedDate in descending order. If SortField has a value, then we sort by that instead.

Fixing the Bug... What Bug?

We've got a bug in the page we just created though – try sorting by the different column headers. They all work except for the Message column. You'll get a SQL exception like the following:

click to expand

SQL doesn't allow sorting by columns of type Text, as the error clearly explains. Therefore, we need to turn off the ability to sort by that column. We also need to provide some enhanced formatting, which you'll see later in the chapter, so let's change the datagrid to use templated columns.

In the following example, we're going to take our existing page and explicitly tell ASP.NET which columns we want to display, and how we want to display them. This puts us in charge of the page, rather than relying on ASP.NET.

Try It Out—Preventing Sorting for Columns
  1. Go to the GuestBook.aspx page's Design view and pull up the properties for the datagrid. Click on the ellipsis (...) within the Columns property to pull up the DataGrid1 Properties dialog.

  2. Uncheck the Create columns automatically at run time box, and then add a Template Column to the right-hand listbox, by highlighting it in the left-hand column and clicking the > button once. Both the Header text and Sort expression should be set to Author:

    click to expand

  3. Next, add another Template Column, setting the Header Text to Subject this time. For the Sort expression, you need to set it to Subject, PostedDate. This is due to the fact that several people might have posted entries with the same subject – it is nice to have a second field to sort on as well. This way, for example, if several people post entries with the subject of Hello, they'll be in chronological order.

  4. Next comes the Message template column. We only need to set the Header text box to Message. We don't want people to be able to sort on this column, so we'll leave the Sort expression blank.

  5. Finally, we have the PostedDate template column, with Header text set to Posted Date and the Sort expression set to PostedDate.

  6. Click OK to close the dialog, and in Design view the grid should now look like the following:

    click to expand

  7. Right-click on the grid and choose Edit Templates... from the context menu. Then, choose to edit the ItemTemplate of the Author column. To do this, simply select the relevant option from the right-hand drop-down list:

    click to expand

  8. Drag a Label control onto the template's surface, right-click on it, and select Edit Tag. By default, Web Matrix, generates the following tag:

     <asp:Label  runat="server">Label</asp:Label> 

    Modify it to look like the following:

    <asp:Label  runat="server"> <%# Container.DataItem("Author") %> </asp:Label>

    We're using a Label control in this template to display the value of the Author field from the table.

    Note

    The template design dialog box is a bit bugged in the current release of Web Matrix. Most of the time, you can see the Label control graphically represented. However, sometimes, you'll only see a small glyph, and sometimes you won't see anything at all!

  9. Do a similar thing for the remaining Subject, Message, and Posted Date columns. Select the appropriate column from the left-hand drop-down list, select ItemTemplate from the right-hand template, add a Label control to the surface, right-click on it and select Edit Tag, and finally modify the tag. As a reminder, you should be including these lines in the tags for the labels in the different columns (changing the id of the label each time):

     <asp:Label  runat="server"> <%# Container.DataItem("Subject") %> </asp:Label> <asp:Label  runat="server"> <%# Container.DataItem("Message") %> </asp:Label> <asp:Label  runat="server"> <%# Container.DataItem("PostedDate") %>  </asp:Label>
  10. Now, when you save and run the page, you will see something similar to before, except that the Message column is no longer a hyperlink. This means that this column no longer supports sorting, which is exactly what we wanted:

    click to expand

How It Works

What we've actually done here is change the default behavior of the grid. By default, the grid automatically shows all columns, and because the template page we used allows sorting, we automatically get sorting on all fields. However, since the Message column is a Text column, we can't sort on it, so we needed to remove the sorting.

If you look at the HTML for the data grid, you should now see the following. First of all, there is the declaration of the grid and the styles:

    <asp:datagrid  runat="server" AllowSorting="True"                   OnSortCommand="DataGrid_Sort" AllowPaging="True"                   PageSize="6" OnPageIndexChanged="DataGrid_Page"                   ForeColor="Black" BackColor="White" CellPadding="3"                   GridLines="None" CellSpacing="1" width="80%"                   AutoGenerateColumns="False">       <HeaderStyle font-bold="True" forecolor="White"                    backcolor="#4A3C8C"></HeaderStyle>       <PagerStyle horizontalalign="Right" backcolor="#C6C3C6"                   mode="NumericPages"></PagerStyle>       <ItemStyle backcolor="#DEDFDE"></ItemStyle>

Next, we have the columns, each of which is a TemplateColumn. This means that the grid doesn't automatically display any content for it – we have to provide the content. For each column we are using a Label, and within that Label we are binding to a column in the database:

      <Columns>         <asp:TemplateColumn SortExpression="Author" HeaderText="Author">           <ItemTemplate>             <asp:Label  runat="server">               <%# Container.DataItem("Author") %>             </asp:Label>           </ItemTemplate>         </asp:TemplateColumn>         <asp:TemplateColumn SortExpression="Subject, PostedDate"                             HeaderText="Subject">           <ItemTemplate>             <asp:Label  runat="server">               <%# Container.DataItem("Subject") %>              </asp:Label>           </ItemTemplate>         </asp:TemplateColumn>         <asp:TemplateColumn HeaderText="Message">           <ItemTemplate>             <asp:Label  runat="server">               <%# Container.DataItem("Message") %>              </asp:Label>           </ItemTemplate>         </asp:TemplateColumn>         <asp:TemplateColumn SortExpression="PostedDate"                             HeaderText="Posted Date">           <ItemTemplate>             <asp:Label  runat="server">               <%# Container.DataItem("PostedDate") %>              </asp:Label>           </ItemTemplate>         </asp:TemplateColumn>       </Columns> 

Entering New Guest Book Entries

Now that we can display guest book entries, we need to allow users to create them. A user will need to be able to enter their name, a subject for their message, and the message itself. When designing functionality like this where the user can view a list of records and also add new ones, you have to decide whether to keep the view and edit functionality on one page, or split the functionality into two separate pages. There are many decision points to determine which is best, and it's beyond the scope of what we're doing. For the purposes of our example, we'll leave everything on the same page, since a guest book can be sort of a conversation. As a result of this, it's good for the user to be able to refer to the previous entries while creating a new one.

First, let's design the graphical user interface for this functionality.

Try It Out—A User Interface for the Guest Book

We're going to be reusing the GuestBook.aspx page we created just now, so open it if you haven't already got it open, and go to the Design view. Now do the following:

  1. Under the DataGrid on the page, place an HTML table (HTML | Insert Table...) onto the page and then place the following controls and text, and set the appropriate properties for them:

Control type

Property

Value

Label

ID

lblStatusMsg

Text

Textbox

ID

txtAuthor

MaxLength

50

RequiredFieldValidator

ControltoValidate

txtAuthor

ErrorMessage

You must enter your name!

Textbox

ID

txtSubject

MaxLength

100

RequiredFieldValidator

ControlToValidate

txtSubject

ErrorMessage

You must enter a subject!

Textbox

ID

txtMessage

TextMode

MultiLine

Rows

5

Columns

50

Button

ID

btnOK

Text

Create Entry

You should also add some text in the table so that your page should look like the following:

click to expand

So that's our graphical user interface completed we'll now move on to the code that saves the new entries on the page to the database.

  1. Double-click on the Create Entry button to create a handler for the button's Click event, which fires, of course, when the button is pressed. Add the following code to the currently empty handler:

    Sub btnOK_Click(sender As Object, e As EventArgs)  Dim NumRecords As Integer = 0  If IsValid Then  NumRecords = _  InsertGuestBookEntry(txtAuthor.text, txtSubject.text, txtMessage.text)  If NumRecords = 0 Then  lblStatusMsg.visible = True  lblStatusMsg.text = _  "An error occurred while trying to create the entry."  Else  lblStatusMsg.text = _  "Entry created successfully. " & _  "Thanks for letting us know what you think."  InitPostForm  End If  BindGrid  End If End Sub

  2. You now need to add the following function, which is called from the btnOK button's event handler and saves the details entered by the user into the guest book:

     Function InsertGuestBookEntry(ByVal author As String, ByVal subject _   As String, ByVal message As String) As Integer  Dim conn As New _  SqlConnection(ConfigurationSettings.AppSettings("cam"))  Dim queryString As String = _  "INSERT INTO GuestBookEntries (Author, Subject, Message)" & _  " VALUES (@Author, @Subject, @Message)"  Dim cmd As New SqlCommand(queryString, conn)  Dim rowsAffected As Integer = 0  cmd.Parameters.Add( _  "@Author", System.Data.SqlDbType.VarChar).Value = author  cmd.Parameters.Add( _  "@Subject", System.Data.SqlDbType.VarChar).Value = subject  cmd.Parameters.Add( _  "@Message", System.Data.SqlDbType.Text).Value = message  conn.Open()  Try  rowsAffected = cmd.ExecuteNonQuery()  Finally  conn.Close()  End Try  Return rowsAffected End Function 

  3. The final function we need to add will initialize the form to allow the user to post another message after they have just created a new one:

     Sub InitPostForm()  txtAuthor.text = ""  txtSubject.text = ""  txtMessage.text = "" End Sub 
  4. Test the page out now. When you create entry, you should see it appear at the top of the grid, and you should see the status message stating that it was successfully created.

  5. Add the top three entries. For the Message for Peter Armore just enter <g>. Also, for the Ryan Amburgy entry make sure you press the Return key between the two lines – between the ! and the I listen should do it. You'll see why later, but the fact that the <g> doesn't show up should give you some clue.

    Your screen should look like this now:

    click to expand

How It Works

Everything about the user interface form should be fairly straightforward. We've set the maximum lengths on the name and subject TextBoxes to match the maximum field lengths in the database. Also, since the database requires name and subject, we've put RequiredFieldValidators next to them to ensure that the user specifies them. The lblStatusMsg label will be used to provide textual feedback to the user as they attempt to create new entries.

Let's now look at the button's click event handler.

First, we initialize the NumRecords object to 0 – this object will hold the function's return value. Then, we need to make sure the user has entered their name and the subject of the message:

  Dim NumRecords As Integer = 0     If IsValid Then

The InsertGuestBookEntry() function is going to return the number of records created. We can use this to ensure that a record did get successfully created. Here, we call the InsertGuestBookEntry() function. We pass the author's name, the subject, and the message itself to the function:

    NumRecords = _       InsertGuestBookEntry(txtAuthor.text, txtSubject.text, txtMessage.text)

If NumRecords is 0, no record got created. If this is the case, we want to alert the user to this problem by displaying error information in the status message label:

    If NumRecords = 0 Then       lblStatusMsg.visible = True       lblStatusMsg.text = _         "An error occurred while trying to create the entry."

If a record was created, we'll let the user know that as well. Since it was created, we can call InitPostForm(), which will clear the form. We'll also reload the grid from the database through a call to BindGrid():

    Else       lblStatusMsg.text = _         "Entry created successfully. " & _         "Thanks for letting us know what you think."       InitPostForm     End If     BindGrid   End If

Now, let's take a look at the InsertGuestBookEntry() function.

The parameters we expect are strings that contain the values for the author, subject, and the message. We will return an integer to the calling function. This integer will contain the number of records created by our insert statement, as we'll see in just a minute:

Function InsertGuestBookEntry(ByVal author As String, ByVal subject _    As String, ByVal message As String) As Integer 

We create a SQL connection using the connection string from the configuration file.

  Dim conn As New _     SqlConnection(ConfigurationSettings.AppSettings("cam"))

Next, the queryString object is set to the SQL statement:

  Dim queryString As String = _     "INSERT INTO GuestBookEntries (Author, Subject, Message)" & _     " VALUES (@Author, @Subject, @Message)"

In this case, we're inserting a new record into table GuestBookEntries. We will set the Author, Subject, and Message fields to the values contained in the @Author, @Subject, and @Message parameters. Remember that when we created the PostedDate field, we set the default value to getdate() – this allows SQL to default to the current date, so we don't need to set it to anything here.

Next, we create a new command object and initialize it with the queryString and SQL connection created earlier:

  Dim cmd As New SqlCommand(queryString, conn)

The rowsAffected integer will contain the number of rows created by the INSERT statement, and we default it to 0. This way, if the execution of the INSERT statement in the Try ... Finally block below fails, 0 will be returned to the calling function, indicating that no row was created:

  Dim rowsAffected As Integer = 0

The three-parameter objects containing the values for the author, subject, and message are
as follows:

  cmd.Parameters.Add( _     "@Author", System.Data.SqlDbType.VarChar).Value = author   cmd.Parameters.Add( _     "@Subject", System.Data.SqlDbType.VarChar).Value = subject   cmd.Parameters.Add( _     "@Message", System.Data.SqlDbType.Text).Value = message

Now we open the connection to the database and then execute the command:

  conn.Open()   Try     rowsAffected = cmd.ExecuteNonQuery()   Finally     conn.Close()   End Try 

Since we don't expect anything back from the INSERT statement that's being executed, we use the ExecuteNonQuery() method. We determine the number of rows created by setting rowsAffected to the return value from ExecuteNonQuery(). Regardless of whether the statement successfully executed or not, we want to close the connection, and this is done in the Finally block.

Finally, we return the number of rows created:

  Return rowsAffected

Lastly in the code for the page, we have the InitPostForm() function, which simply resets all of the text fields to empty string values.

Formatting the DataGrid Text

Now that we're allowing users to create entries, we can add some additional functionality to enhance the formatting of the values within the grid. In Chapter 5 you learned about the need to use HTMLEncode() to allow the display of special characters such as < and >. You also learned about converting carriage returns in SQL text fields into <br /> HTML tags so that multi-line text values would display correctly. Let's build a single function that can handle doing both of these things. That will help keep our code more concise.

Try It Out—Formatting Our Entries

  1. Add the following code to GuestBook.aspx:

     Function MyCstr(ByVal o As Object, Optional ByVal EncodeText As Boolean = _  False, Optional ByVal ConvertCRToBR As Boolean = False) As String  Dim str As String  If o Is Nothing Then  Return Nothing  ElseIf o Is System.DBNull.Value Then  Return Nothing  Else  str = CStr(o)  If str = String.Empty Then  str = Nothing  Else  If EncodeText Then  str =server.HtmlEncode(str)  End If  If ConvertCRToBR Then  str = Replace(str, ControlChars.CrLf, "<br />")  str = Replace(str, ControlChars.Cr, "<br />")  str = Replace(str, ControlChars.Lf, "<br />")  End If  End If  Return str  End If End Function 

  2. Modify the Author, Subject, and Message template columns to call the MyCstr() function with the Container.DataItems as parameters. You can do this by simply going to the HTML view for the page and modifying the code as follows:

            <Columns>           <asp:TemplateColumn SortExpression="Author" HeaderText="Author">             <ItemTemplate>               <asp:Label  runat="server">  <%# MyCstr(Container.DataItem("Author"), True, True) %>                </asp:Label>             </ItemTemplate>           </asp:TemplateColumn>           <asp:TemplateColumn SortExpression="Subject, PostedDate"                               HeaderText="Subject">             <ItemTemplate>               <asp:Label  runat="server">  <%# MyCstr(Container.DataItem("Subject"), True, True) %>                </asp:Label>             </ItemTemplate>           </asp:TemplateColumn>           <asp:TemplateColumn HeaderText="Message">             <ItemTemplate>               <asp:Label  runat="server">  <%# MyCstr(Container.DataItem("Message"), True, True) %>                </asp:Label>             </ItemTemplate>           </asp:TemplateColumn>           <asp:TemplateColumn SortExpression="PostedDate"                               HeaderText="Posted Date">             <ItemTemplate>               <asp:Label  runat="server">                 <%# Container.DataItem("PostedDate") %>                </asp:Label>             </ItemTemplate>           </asp:TemplateColumn>         </Columns>

    You can pass False to ConvertCRToBR because they are single-line values. Also, we pass True for Message.

  3. We have already inserted some extra test rows into the table as part of the previous Try It Out. All we need to do to test our latest modifications is to rerun the page, remembering to refresh the page if need be. You should see something like this:

    click to expand

    Note how the entry that spans multiple lines is now displayed correctly. Also, the <g> has been formatted and displayed correctly too. Let's talk about the function we created.

How It Works

We call the function MyCstr()and we pass three parameters to it:

Function MyCstr(ByVal o As Object, Optional ByVal EncodeText As Boolean = _   False, Optional ByVal ConvertCRToBR As Boolean = False) As String

O is an object we pass to be formatted. We make it type Object because Container.DataItem could return several different types of values, including a null value if the field value in the database doesn't contain anything. If we made O to be of type String, we couldn't handle this situation.

EncodeText is a Boolean flag specifying whether or not we want to HTMLEncode the string. Here, we've used the Optional keyword, so that we can skip this parameter if we want to. If we don't specify a value for the parameter, the default will be used.

ConvertCRToBR is another Boolean flag. This one specifies whether we want to search out the carriage-return and linefeed characters, and replace them with the <br /> HTML tag (which is the HTML equivalent of a return).

Now let's look at the code. If we pass an object that is Nothing, we want to stop immediately. There's no need to continue any processing, and many text functions will raise an exception when one is passed to them:

  If o Is Nothing Then     Return Nothing

If the value in the database is System.DBNull.Value, we also want to stop immediately, and we'll return a string that is Nothing. We might get this value if the entry from the database doesn't have a value:

  ElseIf o Is System.DBNull.Value Then     Return Nothing

We then convert the passed object to a String:

    str = CStr(o)

If str is Empty, we return Nothing. This indicates that we didn't have a string to process:

    If str = String.Empty Then       str = Nothing

Now that we are sure that we have some text, we need to see if the text is to be encoded. If so, we use HTMLEncode (see Chapter 5 for more details). Note that we have to do this before converting the carriage returns to <br />s, because if we did it in the reverse order, the <br /> itself would be encoded by HTMLEncode:

      If EncodeText Then         str =server.HtmlEncode(str)

You've seen the next part in Chapter 5 – this is where we use the Replace() function to replace one set of characters in a string with another:

      If ConvertCRToBR Then         str = Replace(str, ControlChars.CrLf, "<br />")         str = Replace(str, ControlChars.Cr, "<br />")         str = Replace(str, ControlChars.Lf, "<br />")

Here, we are replacing several things, all with the <br /> tag. We are replacing the carriage-return/linefeed combination (CrLf), then carriage-return (Cr), and then linefeed (Lf) – these are all possible entries for producing a new line. Note that you need to search for both together (CrLf) before searching for each individually, or you might get multiple <br />s.

More Formatting Options

Now that we can ensure what's been entered into the field is displayed correctly, let's take it a little further. A little formatting goes a long way to letting users express themselves. So, to give them more flexibility when entering messages into the guest book, let's support the three most common formatting options: italics, bolding, and underlining. We know that the HTML for these are <i> and </i>, <b> and </b>, and <u> and </u>.

Let's allow the users to enter these tags into the message entry box. We also want to allow users to add emoticons to their messages. Emoticons are the keyboard characters that we use in messages to symbolize the way we feel. The most common is :), which represents a smiley face. When a user enters this emoticon or any of several others in their messages, we'll convert them to images to be displayed in the actual message.

Try It Out—Formatting and Emoticons

Let's see how to implement this functionality:

  1. Create the following function, called ConvertEmoticonsAndFormatting(), in the GuestBook.aspx page we've been using throughout the chapter:

     Function ConvertEmoticonsAndFormatting(ByVal str as String) as String  str = Replace(str, "&lt;b&gt;", "<b>")  str = Replace(str, "&lt;/b&gt;", "</b>")  str = Replace(str, "&lt;i&gt;", "<i>")  str = Replace(str, "&lt;/i&gt;", "</i>")  str = Replace(str, "&lt;u&gt;", "<u>")  str = Replace(str, "&lt;/u&gt;", "</u>")  str = Replace(str, ":)", "<img src="/books/3/257/1/html/2/"Images/smiley.gif"">")  str = Replace(str, ";)", "<img src="/books/3/257/1/html/2/"Images/wink_smiley.gif"">")  str = Replace(str, ":(", "<img src="/books/3/257/1/html/2/"Images/sad_smiley.gif"">")  str = Replace(str, ":o", "<img src="/books/3/257/1/html/2/"Images/omg_smiley.gif"">")  str = Replace(str, ":D", "<img src="/books/3/257/1/html/2/"Images/big_smiley.gif"">")  str = Replace(str, ":p", "<img src="/books/3/257/1/html/2/"Images/tongue_smiley.gif"">")  return str End Function 

  2. Modify the Message template column in the datagrid to call this function:

              <asp:TemplateColumn HeaderText="Message">             <ItemTemplate>               <asp:Label  runat="server">  <%# ConvertEmoticonsAndFormatting(  MyCstr(Container.DataItem("Message"), True, True)) %>               </asp:Label>             </ItemTemplate>           </asp:TemplateColumn>

  3. Now refresh the page and you should see the following:

    click to expand

    See how the :) characters have been converted into a smiley. You might like to try adding some more messages, using the underline or bold options, just to see these working too.

How It Works

This function takes a String, modifies it, and returns the modified string to the calling function:

Function ConvertEmoticonsAndFormatting(ByVal str as String) as String

We search the strings for the encoded versions of the formatting tags. We then replace them with their unencoded versions. We do this because we encode the message text before passing it to this function – encoding the message is a good thing to do as it stops people adding potentially harmful HTML code into our application. If what the user types in is encoded, then it's safe for us to display. However, we do want the HTML formatting to show correctly, so we convert these from the encoded form back to HTML. The strings you see above (&lt;,b&gt;, and others) are what the encoded versions look like. All of the other special characters in the message stay encoded, but these get put back to their original state, meaning that they will the formatting will be correctly displayed within the data grid:

  str = Replace(str, "&lt;b&gt;", "<b>")   str = Replace(str, "&lt;/b&gt;", "</b>")   str = Replace(str, "&lt;i&gt;", "<i>")   str = Replace(str, "&lt;/i&gt;", "</i>")   str = Replace(str, "&lt;u&gt;", "<u>")   str = Replace(str, "&lt;/u&gt;", "</u>") 

Next, we search for the emoticon characters and replace them with an HTML image tag that points to the GIF that we want to display to represent the emoticon:

  str = Replace(str, ":)", "<img src="/books/3/257/1/html/2/"Images/smiley.gif"">")   str = Replace(str, ";)", "<img src="/books/3/257/1/html/2/"Images/wink_smiley.gif"">")   str = Replace(str, ":(", "<img src="/books/3/257/1/html/2/"Images/sad_smiley.gif"">")   str = Replace(str, ":o", "<img src="/books/3/257/1/html/2/"Images/omg_smiley.gif"">")   str = Replace(str, ":D", "<img src="/books/3/257/1/html/2/"Images/big_smiley.gif"">")   str = Replace(str, ":p", "<img src="/books/3/257/1/html/2/"Images/tongue_smiley.gif"">")

Creating a Custom Class

We've now created a couple of functions, MyCstr() and ConvertEmoticonsAndFormatting(), that could be useful on more than just the guest book page. It's important to reuse your code as much as possible so that you're not reinventing the wheel each time you write a new application, as we saw in the previous chapter. Now, you could just copy and paste this code to each new page that needs it, but what happens if you enhance the functionality of the code, or if you fix a bug? If you do use the copy and paste method, you'd have to go to each page where the code existed and make the changes more than once. The better way is to create a custom class that is compiled and can be reused as needed.

When creating a custom class, we need to organize it into a namespace. A namespace is a logical method of organizing the functionality within applications. The .NET Framework has hundreds of classes. Without the ability to organize them somehow, there would be naming conflicts with the class names, and finding the functionality you needed would become unwieldy.

You will see namespaces and classes all throughout .NET, and you have seen some in the book already. System.Data.SqlClient and System.Data.OleDb are examples of namespaces. Within System.Data.SqlClient, there are many classes, such as SqlError and SqlCommand. All of .NET is organized in this way. When you come up with your own namespaces and classes, you can name them just about anything you want. We're going to create a namespace and class now. The namespace will be CAM and the class will be Formatting.

Note

We covered namespaces way back in Chapter 6 so if you've forgotten some of the details then please refer back to that chapter.

Try It Out—Creating a Class and Namespace

  1. Create a new Class file using Web Matrix, name it CAM.vb, and specify CAM for the Namespace and Formatting for the Class:

    click to expand

  2. Move (cut and paste) the MyCstr() and ConvertEmoticonsAndFormatting() functions from GuestBook.aspx to the skeleton code that was generated by Web Matrix in the CAM.vb file, as shown below:

    Imports System Namespace CAM   Public Class Formatting     Public Sub New()     End Sub  ' Place MyCstr() and ConvertEmoticonsAndFormatting() functions here   End Class End Namespace

  3. Change the function declarations by placing the keywords Public and Shared in front of them:

      Public Shared Function MyCstr(ByVal o As Object, _       Optional ByVal EncodeText As Boolean = False, _       Optional ByVal ConvertCRToBR As Boolean = False) As String    ...  Public Shared Function ConvertEmoticonsAndFormatting( _       ByVal str as String) as String 

  4. At the top of the page, we need to import a couple of other namespaces. Add the two Imports as follows:

    Imports System Imports Microsoft.VisualBasic Imports System.Web 
  5. Modify the following line in the MyCstr() function:

              If EncodeText Then  str = HttpUtility.HtmlEncode(str)           End If
  6. That's it; our custom class is finished so just save the file for the time being.

How It Works

Let's first look at the changes to the function declarations. The Public keyword allows us to call this function from outside the class itself. We didn't need this when the functions were within the ASP.NET page, but now that we've moved them into their own class we need a way to allow them to be accessed externally to the class.

The Shared keyword allows us to call the function without creating an instance of the class. Normally, when you have a class you create an 'instance' of it, which gives you access to properties and methods. However, our class is only a container for our formatting functions, and these don't need any properties – they run standalone. Making them Shared allows them to be called directly.

Next, we added two Imports statements. The Microsoft.VisualBasic namespace contains, among many other things, the Replace() function and the ControlChars enumeration. The System.Web namespace contains the HttpUtility class, in which is contained the HttpEncode() function.

Finally, we changed the way in which we called HtmlEncode(). The HttpUtility class contains the HttpEncode() function. When we had the function on the ASP.NET page itself, we could use the server object to call the HttpEncode() function, because the server object is intrinsic to all ASP.NET pages. Our new class, however, doesn't have this automatically available, so we have to use the HttpUtility class directly.

Compiling the Custom Class

Now we need to compile this class. Before code within the .NET Framework can be run, it must be compiled from the high-level language code we write to instructions that the machine can understand and execute. Up until now, we've placed all code and objects onto distinct ASPX pages. The .NET Framework is very helpful with this type of development. It will compile ASPX pages on the fly, as, and when, they are requested by the web server. Once we get outside of this scenario, however, we must take the responsibility for compiling our code. When we compile code, a file is returned to us that contains the compiled instructions. Since we're compiling a class, we will get something called a Dynamic Link Library or a DLL for short.

Any supporting DLLs have to be accessible to whatever application will use them. The ASP.NET Framework requires that DLLs be placed into a directory called bin under the root of your application.

Try It Out—Compiling and Using the Class
  1. Create a new folder called bin within your current working folder. This is probably C:\BegWebMatrix\ if you've used the same directory structure as we described earlier in the book, so you will now also have a C:\BegWebMatrix\bin folder.

    Note

    The folders used in the code download separate out the code into chapter by chapter folders, so the root folder may in fact be C:\BegWebMatrix\Chapter13 on your system. The simple method to remember is to create the bin directory within the folder you use to run your examples.

  2. Create a new text file, either using Web Matrix, or Notepad, and enter the following code:

     Set PATH=%SystemRoot%\Microsoft.NET\Framework\v1.0.3705 cd c:\BegWebMatrix\ vbc CAM.vb /t:library /r:System.Web.dll /out:bin\CAM.dll pause 
    Note

    Important – if you have installed the .NET Framework version 1.1, the version number of the Framework will be different. Navigate to your system directory, which will be either C:\Windows, or C:\WINNT, for example, then navigate to the Microsoft.NET\Framework folder, and look for the sub folder with the highest number, and use that number in the path instead of v1.0.3705. Also, if your code root isn't C:\BegWebMatrix, you need to change that line too.

  3. Save this file as Compile.bat within your code directory, and double-check in your code folder to make sure this has been saved with the correct file extension (make sure a .txt hasn't been added to the end of the file name!)

    Note

    A .bat file, referred to as a batch file, is a way of processing a series of commands on the operating system.

  4. To compile CAM.vb, simply double-click the Compile.bat file and you will see a screen similar to the following:

    click to expand

    Press a key when complete to close the window. We'll look at what this did in just a moment. This is all we need to create our DLL. Once we've done this, we need to change the GuestBook.aspx file. Let's do this now.

  5. Change the template columns as shown below:

            <Columns>           <asp:TemplateColumn SortExpression="Author" HeaderText="Author">             <ItemTemplate>               <asp:Label  runat="server">  <%# CAM.Formatting.MyCstr(  Container.DataItem("Author"), True, False) %>               </asp:Label>             </ItemTemplate>           </asp:TemplateColumn>           <asp:TemplateColumn SortExpression="Subject, PostedDate"                               HeaderText="Subject">             <ItemTemplate>               <asp:Label  runat="server">  <%# CAM.Formatting.MyCstr(  Container.DataItem("Subject"), True, False) %>                </asp:Label>             </ItemTemplate>           </asp:TemplateColumn>           <asp:TemplateColumn HeaderText="Message">             <ItemTemplate>               <asp:Label  runat="server">  <%# CAM.Formatting.ConvertEmoticonsAndFormatting(  CAM.Formatting.MyCstr(  Container.DataItem("Message"), True, True)) %>                </asp:Label>             </ItemTemplate>           </asp:TemplateColumn>           <asp:TemplateColumn SortExpression="PostedDate"                               HeaderText="Posted Date">             <ItemTemplate>               <asp:Label  runat="server">                 <%# Container.DataItem("PostedDate") %>                </asp:Label>             </ItemTemplate>           </asp:TemplateColumn>         </Columns> 

    We now have to specify the fully qualified path to the function. In this case, it's CAM.Formatting.functionname.

How It Works

Let's look at how the compilation process worked by taking apart our batch file line-by-line:

Set PATH=%SystemRoot%\Microsoft.NET\Framework\v1.0.3705

The first part of our command creates a temporary shortcut to a location containing the file that compiles our code, which is a file called vbc.exe. The %SystemRoot% bit of the path refers to the directory Windows is installed in, which could be C:\Windows, or C:\WINNT. The final location is version-specific, so you may need to alter this path if you install .NET version 1.1 when it's released.

cd c:\BegWebMatrix\

The next line changes the working directory for the compiler to match the working directory of our web application.

vbc CAM.vb /t:library /r:System.Web.dll /out:bin\CAM.dll

The next line is where the compilation actually occurs. Notice that we are using the vbc command, which runs the vbc.exe file we mentioned earlier. Let's look at the options we specify in the compilation command:

Parameter

Description

CAM.vb

This is the file we're compiling.

/t:library

Next, we specify the type of file we're creating. In this case, we're creating a DLL, which is specified as a library.

/r:System.Web.dll

We use functionality contained within System.Web.dll. This parameter tells the compiler that it should be referenced.

/out:bin\CAM.dll

We're naming and saving the DLL to a file at the specified location. Since our working directory is C:\BegWebMatrix, the file will be saved to the bin directory we created earlier within this location.

The final statement "pauses" the window that displays our progress, so that it doesn't simply disappear once execution of the file is completed:

pause

This gives us a chance of seeing any error messages that may be generated. This line also gives us the "Press any key to continue . . ." prompt.

This may seem like a lot of work, and it definitely is more involved than simply keeping the code on the same page where it's going to be used. However, as your websites get more sophisticated, you'll save tremendous amounts of time by reusing your code in this manner.

Finishing the Guest Book

The last thing we need to do is fit the guest book into the rest of the site. To do this, we need to put the menu user control on the page and add a reference to the site's style sheet.

Try It Out—Tidying up the Guest Book

  1. First, let's add a <Head> tag to the page that contains the stylesheet link (make sure you have Style.css from Chapter 11 available in your code directory):

    <html> <head> <% = "<link href=""Style.css"" type=""text/css"" rel=""stylesheet"" />" %> </head>

    One thing to note is that you need to put the link to the stylesheet inside <% %> tags. This is because there's a bug in Web Matrix at the time of this writing that prevents design mode from working correctly on some systems when a stylesheet reference is in the header section. The design mode will show glyphs for controls instead of the controls themselves. (We've seen this bug earlier in the chapter, when editing item templates.)

  2. Now we need to add the menu control. First, add the @ Register page directive for the user control to the top of the page in the All view:

    <%@ Page Language="VB" %> <%@ Register TagPrefix="CAM" TagName="Menu" src="/books/3/257/1/html/2/Header.ascx" %> <%@ import Namespace="System.Data" %> <%@ import Namespace="System.Data.SqlClient" %>

    You need to make sure that Header.ascx from Chapter 12, and its associated XML file, menu.xml, are both in your code directory.

  3. Back in the HTML view, add the user control itself to the page in place of the default title:

        <h2>  <CAM:Menu  title="Guest Book" runat="server"></CAM:Menu>     </h2>

  4. Finally, we need to change the XML file that defined the links, menu.xml, to point to the available pages in our site. Amend the file so that it contains the following code:

    <?xml version="1.0" encoding="utf-8" ?> <menuItems>  <menuItem Title="Home" src="/books/3/257/1/html/2/Default.aspx" />  <menuItem Title="Discography" src="/books/3/257/1/html/2/Discs.aspx" />  <menuItem Title="Biography" src="/books/3/257/1/html/2/Bio.aspx" />  <menuItem Title="Download" src="/books/3/257/1/html/2/Download.aspx" />  <menuItem Title="Guest Book" src="/books/3/257/1/html/2/GuestBook.aspx" /> </menuItems>

    That's it. When you run your page now, it will look consistent with pages we have created earlier in the book:

    click to expand




Beginning Dynamic Websites with ASP. NET Web Matrix
Beginning Dynamic Websites: with ASP.NET Web Matrix (Programmer to Programmer)
ISBN: 0764543741
EAN: 2147483647
Year: 2003
Pages: 141

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net