Introduction
This article shows an easy way to create a guestbook built using ASP.NET and XML serialization. The guestbook is a simple last-entry-last log, and the number of entries is limited to 20. When the 21st entry is submitted, the first one in the list is removed. To format the data, I use the DataList
control that comes with Visual Studio .NET. The article is written to demonstrate the following technologies:
- ASP.NET Web Forms
- The
DataList
control
- XML serialization of objects
- Generics
The guestbook should be created as a Web Site project in Visual Studio 2005 or 2008. This is how the guestbook will look when it's ready:
The Code
The GuestBookEntry Class
To start with, we will create a new class file named GuestBookEntry
. This class will contain the code for the entries in the guest book. An array of GuestBookEntry
objects will be serialized to XML later and stored on the server.
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
public class GuestBookEntry
{
public string Message;
public string Name;
public DateTime Date;
public string IPAddress;
public GuestBookEntry()
{
}
public GuestBookEntry(string message, string name, DateTime date, string ipAddress)
{
this.Message = message;
this.Name = name;
this.Date = date;
this.IPAddress = ipAddress;
}
public override string ToString()
{
return "<b>" + Message + "</b><br>" + "Name: <b>"
+ Name + "</b><br>" + "Date: " + Date.ToString();
}
}
Note the overriding of the ToString()
method. In .NET, every class inherits from Object
, and therefore inherits implementations of several methods. One of those methods is ToString()
, which returns a string that represents the current object. The default implementation isn’t very helpful, though. If you don’t override ToString()
, then calling it will return the class name.
In this implementation, we override ToString()
in order to get an HTML representation of the GuestBookEntry
object. We are also storing the IP-address of the submitter with each entry. This information is not shown in the guest book, but can be using for blocking addresses if the guestbook is vandalized.
The GuestBookHandler Class
The second class we are going to create is the GuestBookHandler
. The main responsibility for this class is to store the guestbook to the server as XML. The class also takes care of limiting the number of entries to 20 and encoding the text to HTML (replacing new lines with <br />).
The class contains the following code:
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Collections.Generic;
using System.Xml.Serialization;
using System.Xml;
using System.IO;
using System.Web.Hosting;
public class GuestBookHandler
{
private List<GuestBookEntry> entrys = new List<GuestBookEntry>();
private const string fileName = "~\\App_Data\\GuestBook.xml";
private const int maxNumberOfEntries = 20;
private static object writeLock = new object();
public GuestBookHandler()
{
Load();
}
public GuestBookEntry[] Entrys {
get { return entrys.ToArray(); }
}
public void Add(string message, string name, DateTime date, string ipAddress)
{
if (entrys.Count >= maxNumberOfEntries) {
entrys.RemoveAt(0);
}
GuestBookEntry ge = new GuestBookEntry(message.Replace(Environment.NewLine,
"<br />"),
name, date, ipAddress);
entrys.Add(ge);
}
public void Save()
{
lock(writeLock) {
XmlSerializer serializerObj = new XmlSerializer(entrys.GetType());
StreamWriter writeFileStream = null;
try {
string mappedPath = HostingEnvironment.MapPath(fileName);
writeFileStream = new StreamWriter(mappedPath);
serializerObj.Serialize(writeFileStream, entrys);
writeFileStream.Flush();
}
finally {
if (writeFileStream != null) {
writeFileStream.Close();
}
}
}
}
private void Load()
{
XmlSerializer serializer = new XmlSerializer(typeof(List<GuestBookEntry>));
FileStream fs = null;
try {
try {
fs = new FileStream(HostingEnvironment.MapPath(fileName), FileMode.Open);
}
catch (System.IO.FileNotFoundException) {
return;
}
XmlReader reader = XmlReader.Create(fs);
entrys = (List<GuestBookEntry>)serializer.Deserialize(reader);
}
finally {
if (fs != null) {
fs.Close();
}
}
}
}
Note the use of generic lists in the source code: List<GuestBookEntry>
.
Generics is a new feature introduced with .NET 2.0, which provides type safety at compile time. Generics allows you to create data structures without committing to a specific data type in your code at design time. At compile time, the compiler ensures that the types used with the data structure are consistent with type safety. In other words, generics provides type safety, but without any loss of performance or code bloat. Generics is similar to templates in C++, even though the implementation is very different.
The GuestBookHandler
class also stores data to the server by using XML serialization. Serialization is the process of persisting an object to disk. Another application can deserialize your object, and it will be in the same state it was before the serialization. This tutorial uses XML serialization. The namespace containing the classes and methods suitable for such a serialization is System.Xml.Serialization
.
In order to serialize an object, we first create an XmlSerializer
object. We also create a stream that will write to or read from a file. Then, you call the appropriate method of serialization, passing it the stream object you created. To deserialize an XML serialized object you created, you simply call the Deserialize
method, passing it the stream that reads from the XML document. The final step is to cast the object into the correct type.
XML provides the following benefits over standard serialization techniques:
- Greater interoperability: XML is a text file based format, and all modern Operating Systems and developing environments include libraries for processing such files.
- Administrator friendly: by storing objects in XML format, it gives the administrators the opportunity to view and edit XML files. So, an administrator can easily modify your object or troubleshoot problems.
- Better forward compatibility: XML-serialized objects are self-described. When you need to replace your application with a newer one, the transition will be straightforward.
The GuestBook.aspx File
The GuestBook.aspx file contains the following HTML code:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="GuestBook.aspx.cs"
Inherits="GuestBook" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<h1>
Simple Guestbook</h1>
<asp:DataList ID="DataList1" runat="server" CellPadding="4"
ForeColor="#333333" Width="100%">
<ItemTemplate>
<%# Container.DataItem %>
</ItemTemplate>
<FooterStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" />
<AlternatingItemStyle BackColor="White" />
<ItemStyle BackColor="#EFF3FB" />
<SelectedItemStyle BackColor="#D1DDF1" Font-Bold="True"
ForeColor="#333333" />
<HeaderStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" />
</asp:DataList><br />
</div>
Message<br />
<asp:TextBox ID="txtMessage" runat="server" Height="75px" Width="301px"
TextMode="MultiLine"></asp:TextBox><br />
<br />
Name<br />
<asp:TextBox ID="txtName" runat="server" Width="138px"></asp:TextBox>
<br />
<br />
<asp:Button ID="butSubmit" runat="server" Text="Submit" OnClick="butSubmit_Click" />
</form>
</body>
</html>
Note the following code under the DataList
tag.
<asp:DataList ID="DataList1" runat="server" CellPadding="4" Width="305px">
<ItemTemplate>
<%# Container.DataItem %>
</ItemTemplate>
</asp:DataList>
The <%# %>
means this is a data binding expression, and Container.DataItem
is an alias for the current item in the data source. In other words, when we are binding to a collection of objects, Container.DataItem
is the current row of that collection.
The Code-behind: Guestbook.aspx.cs
using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
public partial class GuestBook : System.Web.UI.Page
{
private GuestBookHandler guestBookHandler;
protected void Page_Load(object sender, EventArgs e)
{
guestBookHandler = new GuestBookHandler();
BindData();
}
protected void butSubmit_Click(object sender, EventArgs e)
{
if (txtMessage.Text != "" && txtName.Text != "") {
guestBookHandler = new GuestBookHandler();
guestBookHandler.Add(txtMessage.Text,
txtName.Text, DateTime.UtcNow,
HttpContext.Current.Request.UserHostAddress);
guestBookHandler.Save();
txtMessage.Text = "";
txtName.Text = "";
BindData();
}
}
private void BindData()
{
DataList1.DataSource = guestBookHandler.Entrys;
DataList1.DataBind();
}
}
Conclusion
As you can see, it is not very difficult to create a guestbook in .NET. This guestbook is based on XML serialization and is a last-entry-last guestbook. I could contain the base code for developing your own with more advanced functionality. With minor changes, this guestbook can also be made to a last-entry-first guestbook. All comments and improvement suggestions are welcome.
History
- November 22, 2008: Article first published.
- January 14, 2009: Updated with support for newline in input and error handling improved.