cshttp Tutorial - Day 7: Your First C# Class (`GuestbookEntry`)

Back to cshttp Tutorial Main Menu

Day 7: Your First C# Class (GuestbookEntry)

In Day 5 and Day 6, every guestbook comment was stored in our list as one long, glued-together string:

[2026-10-22 09:05] Alice: First comment! C# web dev is awesome.

Storing everything in one string was a quick way to get started, but notice the limitation: the timestamp, the author's name, and the message are all mashed together into a single piece of text!

What if we want to display the author's name in bold (<b>Alice</b>), display the timestamp in a smaller grey font (<small style='color: gray;'>), and place the message on its own line underneath? With a single string, we can't style them separately without awkwardly slicing the string apart.

In Day 7, we will solve this by creating our very first C# Class (GuestbookEntry).

Today, we will:

  1. Learn what a C# class is and how to define properties (Name, Message, Time).
  2. Create objects from a class using the new keyword.
  3. Upgrade our server list from List<string> to List<GuestbookEntry>.
  4. Update HandleGuestbook() to save GuestbookEntry objects and style each property (entry.Name, entry.Time, entry.Message) individually!

1. What Is a Class? (A Blueprint with Labelled Boxes)

In C#, a built-in type like string holds a single piece of text, and int holds a single number.

When you want to group multiple related pieces of data together into one neat package, you create a class.

Think of a class like a blank paper form with labelled boxes:

  • Box 1 (Name): holds the author's name (string).
  • Box 2 (Message): holds the comment text (string).
  • Box 3 (Time): holds the exact date and time the comment was posted (DateTime).

Here is how we write that blueprint in C#:

internal class GuestbookEntry
{
    public string Name { get; set; }
    public string Message { get; set; }
    public DateTime Time { get; set; }
}

Understanding Each Part:

  • internal class GuestbookEntry: Declares a new custom data type named GuestbookEntry.
  • public string Name { get; set; }: This is called a Property.
    • public means other parts of our code (like Program) are allowed to access it.
    • string (or DateTime) is the type of data this box can hold.
    • { get; set; } means our code is allowed to both read (get) the value in the box and write/change (set) the value in the box!

2. Creating an Object from a Class (new)

A class is just the blueprint. Whenever a visitor posts a comment, we use the new keyword to create an actual object (an instance of GuestbookEntry) and fill in its three boxes:

GuestbookEntry entry = new GuestbookEntry();
entry.Name = "Alice";
entry.Message = "First comment! C# web dev is awesome.";
entry.Time = DateTime.Now;

C# also gives us a neat shortcut called an Object Initializer, which creates the object and fills in the properties all in one block:

GuestbookEntry entry = new GuestbookEntry
{
    Name = "Alice",
    Message = "First comment! C# web dev is awesome.",
    Time = DateTime.Now
};

Both ways do the exact same thing!


3. Upgrading Our List from List<string> to List<GuestbookEntry>

Now let's look at guestbookEntries at the top of class Program.

Before (Day 5 & Day 6 — List<string>):

static List<string> guestbookEntries = new List<string>()
{
    "[2026-10-22 09:00] System: Welcome to our new Guestbook!",
    "[2026-10-22 09:05] Alice: First comment! C# web dev is awesome."
};

After (Day 7 — List<GuestbookEntry>):

Instead of a list of string, we change the type inside <...> to List<GuestbookEntry>, and initialize our two starter entries as new GuestbookEntry objects:

static List<GuestbookEntry> guestbookEntries = new List<GuestbookEntry>()
{
    new GuestbookEntry
    {
        Name = "System",
        Message = "Welcome to our new Guestbook!",
        Time = new DateTime(2026, 10, 22, 9, 0, 0)
    },
    new GuestbookEntry
    {
        Name = "Alice",
        Message = "First comment! C# web dev is awesome.",
        Time = new DateTime(2026, 10, 22, 9, 5, 0)
    }
};

(Note: new DateTime(2026, 10, 22, 9, 0, 0) creates a DateTime value for October 22, 2026 at 09:00:00 AM.)


4. Saving a New GuestbookEntry on POST

Next, let's update the if (Request.HttpMethod == "POST") section inside HandleGuestbook().

Previously, we glued timestamp, author, and comment into a single string. Now, we simply create a new GuestbookEntry object and add it to guestbookEntries:

if (Request.HttpMethod == "POST")
{
    string author = Request.Form["author"];
    string comment = Request.Form["comment"];

    if (!string.IsNullOrWhiteSpace(author) && !string.IsNullOrWhiteSpace(comment))
    {
        // NEW IN DAY 7: Create a GuestbookEntry object and fill in its properties
        GuestbookEntry entry = new GuestbookEntry
        {
            Name = author,
            Message = comment,
            Time = DateTime.Now
        };

        guestbookEntries.Add(entry);
    }

    Response.Redirect("/guestbook");
    return;
}

Notice how clean that is! We don't even have to convert DateTime.Now into a string when saving anymore—we store the real DateTime value directly in entry.Time.


5. Styling Each Property in the foreach Loop

Now comes the best part! When we loop through guestbookEntries to build our HTML list, each entry has separate .Name, .Message, and .Time properties.

That means we can:

  1. Safely HTML-encode entry.Name and entry.Message using WebApp.HtmlEncode(...).
  2. Format entry.Time however we want using .ToString("yyyy-MM-dd HH:mm").
  3. Style the name in bold (<b>), the time in small grey text (<small style='color: gray;'>), and place the message on a new line (<br />)!
StringBuilder listHtml = new StringBuilder();
listHtml.Append("<ul>");
foreach (GuestbookEntry entry in guestbookEntries)
{
    // NEW IN DAY 7: Access and style each property individually!
    string safeName = WebApp.HtmlEncode(entry.Name);
    string safeMessage = WebApp.HtmlEncode(entry.Message);
    string time = entry.Time.ToString("yyyy-MM-dd HH:mm");

    listHtml.Append($@"
        <li style='margin-bottom: 12px;'>
            <b>{safeName}</b> 
            <small style='color: gray;'>({time})</small><br />
            {safeMessage}
        </li>");
}
listHtml.Append("</ul>");

Why Is a Class So Much Better Than a Plain String?

Storing One Long string (Day 5–6)Storing a GuestbookEntry Class (Day 7)
"[2026-10-22 09:05] Alice: Hello"entry.Name, entry.Message, entry.Time
Impossible to style the name in bold without cutting the string apart.Easy to style each field: <b>{safeName}</b> and <small>({time})</small>.
The timestamp is frozen as text once created.entry.Time is a real C# DateTime—you can sort by it, filter by date, or change its display format anytime!

6. Complete Code for Day 7

Here is the complete Program.cs with class GuestbookEntry placed right inside our ConsoleApp1 namespace:

using System;
using System.Collections.Generic;
using System.Text;
using cshttp;

namespace ConsoleApp1
{
    // NEW IN DAY 7: Our very first C# class!
    internal class GuestbookEntry
    {
        public string Name { get; set; }
        public string Message { get; set; }
        public DateTime Time { get; set; }
    }

    internal class Program
    {
        // NEW IN DAY 7: The list now holds GuestbookEntry objects instead of plain strings
        static List<GuestbookEntry> guestbookEntries = new List<GuestbookEntry>()
        {
            new GuestbookEntry
            {
                Name = "System",
                Message = "Welcome to our new Guestbook!",
                Time = new DateTime(2026, 10, 22, 9, 0, 0)
            },
            new GuestbookEntry
            {
                Name = "Alice",
                Message = "First comment! C# web dev is awesome.",
                Time = new DateTime(2026, 10, 22, 9, 5, 0)
            }
        };

        static void Main(string[] args)
        {
            WebApp.Run(8080, OnRequest);
        }

        static void OnRequest()
        {
            string path = Request.Path.ToLower();

            switch (path)
            {
                case "/":
                case "/home":
                    HandleHome();
                    break;
                case "/guestbook":
                    HandleGuestbook();
                    break;
                case "/greet":
                    HandleGreet();
                    break;
                case "/about":
                    HandleAbout();
                    break;
                case "/contact":
                    HandleContact();
                    break;
                default:
                    HandleNotFound();
                    break;
            }
        }

        static void RenderPage(string title, string bodyContent)
        {
            string html = $@"
<html>
    <head>
        <title>{title}</title>
    </head>
    <body>
        <nav>
            <a href='/'>Home</a> | 
            <a href='/guestbook'>Guestbook</a> | 
            <a href='/greet'>Greet</a> | 
            <a href='/about'>About</a> | 
            <a href='/contact'>Contact</a>
        </nav>
        <hr />
        
        {bodyContent}

        <hr />
        <footer>
            <small>Server Time: {DateTime.Now:yyyy-MM-dd HH:mm:ss}</small>
        </footer>
    </body>
</html>";

            Response.Write(html);
        }

        static void HandleGuestbook()
        {
            if (Request.HttpMethod == "POST")
            {
                string author = Request.Form["author"];
                string comment = Request.Form["comment"];

                if (!string.IsNullOrWhiteSpace(author) && !string.IsNullOrWhiteSpace(comment))
                {
                    // NEW IN DAY 7: Create a new GuestbookEntry object and fill in its properties
                    GuestbookEntry entry = new GuestbookEntry
                    {
                        Name = author,
                        Message = comment,
                        Time = DateTime.Now
                    };

                    guestbookEntries.Add(entry);
                }

                Response.Redirect("/guestbook");
                return;
            }

            StringBuilder listHtml = new StringBuilder();
            listHtml.Append("<ul>");
            foreach (GuestbookEntry entry in guestbookEntries)
            {
                // NEW IN DAY 7: Each part has its own property, so we can style each part differently
                string safeName = WebApp.HtmlEncode(entry.Name);
                string safeMessage = WebApp.HtmlEncode(entry.Message);
                string time = entry.Time.ToString("yyyy-MM-dd HH:mm");

                listHtml.Append($@"
                    <li style='margin-bottom: 12px;'>
                        <b>{safeName}</b> 
                        <small style='color: gray;'>({time})</small><br />
                        {safeMessage}
                    </li>");
            }
            listHtml.Append("</ul>");

            string content = $@"
                <h1>Community Guestbook</h1>
                <p>Leave a message for everyone visiting this site!</p>

                <form action='/guestbook' method='post' style='background: #f9f9f9; padding: 15px; border: 1px solid #ddd; max-width: 450px;'>
                    <div>
                        <label>Your Name:</label><br />
                        <input type='text' name='author' required style='width: 100%;' />
                    </div>
                    <br />
                    <div>
                        <label>Your Comment:</label><br />
                        <textarea name='comment' rows='3' required style='width: 100%;'></textarea>
                    </div>
                    <br />
                    <button type='submit'>Sign Guestbook</button>
                </form>

                <hr />
                <h2>Recent Entries ({guestbookEntries.Count})</h2>
                {listHtml.ToString()}";

            RenderPage("Guestbook", content);
        }

        static void HandleContact()
        {
            if (Request.HttpMethod == "POST")
            {
                string senderName = Request.Form["sender"];
                string email = Request.Form["email"];
                string message = Request.Form["message"];

                Console.WriteLine($"[New Message] From: {senderName} ({email}) - {message}");

                string safeName = WebApp.HtmlEncode(senderName);
                string safeEmail = WebApp.HtmlEncode(email);
                string safeMessage = WebApp.HtmlEncode(message);

                string thankYouContent = $@"
                    <h1>Thank You, {safeName}!</h1>
                    <p>We received your message and will reply to <b>{safeEmail}</b> soon.</p>
                    <div style='background: #f0f0f0; padding: 10px; border-radius: 4px;'>
                        <i>""{safeMessage}""</i>
                    </div>
                    <p><a href='/contact'>Send another message</a></p>";

                RenderPage("Message Received", thankYouContent);
                return;
            }

            string formContent = @"
                <h1>Contact Us</h1>
                <p>Send us a message below:</p>
                <form action='/contact' method='post'>
                    <div>
                        <label>Your Name:</label><br />
                        <input type='text' name='sender' required style='width: 300px;' />
                    </div>
                    <br />
                    <div>
                        <label>Email Address:</label><br />
                        <input type='email' name='email' required style='width: 300px;' />
                    </div>
                    <br />
                    <div>
                        <label>Message:</label><br />
                        <textarea name='message' rows='5' cols='40' required></textarea>
                    </div>
                    <br />
                    <button type='submit'>Send Message</button>
                </form>";

            RenderPage("Contact", formContent);
        }

        static void HandleGreet()
        {
            string name = Request.QueryString["name"];
            string greetingMessage = "";

            if (string.IsNullOrEmpty(name))
            {
                greetingMessage = "<p>Please enter your name below, or try clicking a quick link!</p>";
            }
            else
            {
                string safeName = WebApp.HtmlEncode(name);
                greetingMessage = $"<h2>Hello, {safeName}! Welcome to C# Web Development!</h2>";
            }

            string content = $@"
                <h1>Interactive Greeting</h1>
                {greetingMessage}

                <form action='/greet' method='get'>
                    <label>Your Name:</label>
                    <input type='text' name='name' placeholder='Type your name...' />
                    <button type='submit'>Say Hello</button>
                </form>

                <p>Or test query links directly: 
                    <a href='/greet?name=Alice'>Greet Alice</a> | 
                    <a href='/greet?name=Bob'>Greet Bob</a>
                </p>";

            RenderPage("Greet", content);
        }

        static void HandleHome()
        {
            string content = @"
                <h1>Welcome cshttp!</h1>
                <p>Visit the <a href='/guestbook'>Guestbook</a> to see entries built from a C# class!</p>";

            RenderPage("Home", content);
        }

        static void HandleAbout()
        {
            string content = @"
                <h1>About Us</h1>
                <p>It's Me!</p>";

            RenderPage("About Us", content);
        }

        static void HandleNotFound()
        {
            Response.StatusCode = 404;
            string content = @"
                <h1>404 Not Found</h1>
                <p>The page that you are looking for does not exist.</p>";

            RenderPage("404 Not Found", content);
        }
    }
}

7. Test It in Your Browser

  1. Run the application (dotnet run or press Start in Visual Studio).
  2. Open http://localhost:8080/guestbook.
  3. Look at how much cleaner the Guestbook entries look: the author's name is in bold, the timestamp is in small grey text, and the comment message sits neatly on the line below!
  4. Post a new comment and watch it appear with the exact same structured layout.
  5. Bonus Experiment: Try changing "yyyy-MM-dd HH:mm" on line 142 to "dddd, d MMMM yyyy - h:mm tt" and run the program again to see how easily a DateTime property lets you change date formatting!

In Day 8, we will solve a big limitation: right now, every time you stop and restart your server, all posted guestbook comments disappear because List<GuestbookEntry> only lives in temporary RAM. We will learn how to save and load our guestbook entries permanently on disk using a file!