Simple Blog Page

How to create a simple blog web page

When I decided to start adding technical articles to this site I looked for blogging software or a content management system that I could integrate fairly easily with my existing pages. That meant it had to run under IIS and use ASP.NET. While I found some really nice software, most of them were either intended as a stand alone product or used newer technologies that didn't easily integrate into my existing web site. I plan to incorporate one of those products into a future rewrite using Blazor, but for now I chose to build a rudimentary system that would meet my current needs without a lot of work.

I started out by defining an Article class that included the fields I thought I would need such as the article body and some metadata about the article. The class ended up looking like this:

using System;
using System.Collections.Generic;
using System.Web;

namespace MyWebSite.Classes
{
    public class Article : IComparable<Article>
    {
        public int Id { get; set; } = 0;
        public string Title { get; set; } = "Article not found";
        public string Author { get; set; } = "Me";
        public DateTime Date { get; set; } = DateTime.Now;
        public string Category { get; set; } = "Radio";
        public string Summary { get; set; } = "This article could not be found";
        public string Content { get; set; } = "";

        public int CompareTo(Article other)
        {
            if (other == null) 
                return 1;
            else
                return this.Date.CompareTo(other.Date);
        }
    }
}

Now that I had the article definition, I needed a way to load and store the articles.  For simplicity I used an XML file in the App_Data folder of my web site.  You could certainly choose to use a database, JSON, or whatever format you like.  This just seemed like a simple solution.  For now I only defined two methods.  The first returns a list of articles.  The second returns a specific article based on the Id.

using System;
using System.Collections.Generic;
using System.IO;
using System.Web;
using System.Xml.Serialization;

namespace MyWebSite.Classes
{
    public class ArticleRepository
    {
        public List<Article> GetArticles()
        {
            string folderPath = AppDomain.CurrentDomain.BaseDirectory;
            string blogFilePath = Path.Combine(folderPath, "App_Data", "articles.xml");

            using (var stream = System.IO.File.OpenRead(blogFilePath))
            {
                var serializer = new XmlSerializer(typeof(List<Article>));
                List<Article> list = serializer.Deserialize(stream) as List<Article>;

                list.Sort();
                list.Reverse();

                return list;
            }
        }

        public Article FindArticle(int id)
        {
            List<Article> posts = GetArticles();

            foreach(Article post in posts)
            {
                if (post.Id == id)
                {
                    return post;
                }
            }

            return null;
        }
    }
}

Now that I had my article repository ready I could create some web pages. The first page I created was a page to display the list of articles as links. Clicking on a link would open up a details page with the full article. Later I decided to add some basic filtering by category, author, or month. The list page is pretty simple. It retrieves the list of articles and builds the links for display on the page.

Here's the page:

<%@ Page Async="true" Title="Tech Articles" Language="C#" MasterPageFile="~/Site.Master" MetaKeywords="Technical Articles" MetaDescription="A list of technical articles/blog postings" AutoEventWireup="true" CodeBehind="ArticleList.aspx.cs" Inherits="MyWebSite.ArticleList" %>

<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">

    <div class="jumbotron shadow">
        <div class="text-center">
            <h1>Technical Articles</h1>
            <p class="lead">Articles about radio, programming, and creating web sites</p>
        </div>
    </div>

    <main role="main" class="container">
        <div class="row">
            <div class="col-md-8 blog-main">
                <h3 class="pb-4 mb-4 font-italic border-bottom">Article List</h3>
                <% foreach (var post in Articles)
                    { %>
                <div class="blog-post">
                    <h3 class="blog-post-title"><%= post.Title %></h3>
                    <a href="/ArticleDetails?id=<%=post.Id%>"><%=post.Summary%></a>
                    <p><small class="blog-post-meta">Author: <%=post.Author %> Date: <%=post.Date.ToString("MM/dd/yy") %> Category: <%=post.Category %></small></p>
                </div>

                <% } %>
            </div>
            <aside class="col-md-4 blog-sidebar">
                <div class="p-4 mb-3 bg-light rounded">
                    <h4 class="font-italic">Categories</h4>
                    <asp:DropDownList ID="DropDownListCategories" runat="server" AutoPostBack="true" OnSelectedIndexChanged="DropDownListCategories_SelectedIndexChanged"></asp:DropDownList>
                </div>
                <div class="p-4 mb-3 bg-light rounded">
                    <h4 class="font-italic">Authors</h4>
                    <asp:DropDownList ID="DropDownListAuthors" runat="server" AutoPostBack="true" OnSelectedIndexChanged="DropDownListAuthors_SelectedIndexChanged"></asp:DropDownList>
                </div>
                <div class="p-4 mb-3 bg-light rounded">
                    <h4 class="font-italic">Archives</h4>
                    <asp:DropDownList ID="DropDownListArchives" runat="server" AutoPostBack="true" OnSelectedIndexChanged="DropDownListArchives_SelectedIndexChanged"></asp:DropDownList>
                </div>
            </aside>
        </div>
    </main>
</asp:Content>

And the code behind:

using MyWebSite.Classes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace MyWebSite
{
    public partial class ArticleList : System.Web.UI.Page
    {
        public List<Article> Articles;

        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
            {
                LoadArticles();
                RegisterAsyncTask(new PageAsyncTask(LoadDropDownLists));
            }
        }

        private void LoadArticles()
        {
            ArticleRepository repo = new ArticleRepository();
            Articles = repo.GetArticles();
        }

        private async Task LoadDropDownLists()
        {
            List<string> categories = new List<string>();
            List<string> authors = new List<string>();
            List<string> archives = new List<string>();

            await Task.Run(() =>
            {
                // find each unique values in the articles list
                foreach (Article art in Articles)
                {
                    if (!categories.Contains(art.Category))
                    {
                        categories.Add(art.Category);
                    }
                    if (!authors.Contains(art.Author))
                    {
                        authors.Add(art.Author);
                    }

                    string monthYear = art.Date.ToString("MMMM yyyy");
                    if (!archives.Contains(monthYear))
                    {
                        archives.Add(monthYear);
                    }
                }

                // load dropdown lists
                DropDownListCategories.Items.Add("All");
                foreach (string cat in categories)
                {
                    DropDownListCategories.Items.Add(cat);
                }
                DropDownListAuthors.Items.Add("All");
                foreach (string aut in authors)
                {
                    DropDownListAuthors.Items.Add(aut);
                }
                DropDownListArchives.Items.Add("All");
                foreach (string arc in archives)
                {
                    DropDownListArchives.Items.Add(arc);
                }
            });
        }

        protected void FilterList()
        {
            ArticleRepository repo = new ArticleRepository();
            List<Article> articles = repo.GetArticles();

            Articles = new List<Article>();
            foreach (Article article in articles)
            {
                bool select = true;
                string category = DropDownListCategories.SelectedValue;
                string author = DropDownListAuthors.SelectedValue;
                string archive = DropDownListArchives.SelectedValue;

                if (!category.Equals("All"))
                {
                    select = article.Category.Equals(category);
                }
                if (select && !author.Equals("All"))
                {
                    select = article.Author.Equals(author);
                }
                if (select && !archive.Equals("All"))
                {
                    string monthYear = article.Date.ToString("MMMM yyyy");
                    select = archive.Equals(monthYear);
                }

                if (select) Articles.Add(article);
            }

        }

        protected void DropDownListCategories_SelectedIndexChanged(object sender, EventArgs e)
        {
            FilterList();
        }

        protected void DropDownListAuthors_SelectedIndexChanged(object sender, EventArgs e)
        {
            FilterList();
        }

        protected void DropDownListArchives_SelectedIndexChanged(object sender, EventArgs e)
        {
            FilterList();
        }
    }
}

In the Page_Load event I load the article list.  I also start an async task to load the drop down lists with the unique author, category, and month values from the list. The page then loops through the list of articles and displays a link for each one. If one of the drop downs is selected the article list is filtered and the page is rebuilt based on the newly filtered list. 

When the user selects an article the article detail page is displayed.  The article Id is passed to this page as part of the URL.  The body of the article is pre-formatted as HTML and stored in a CDATA block withing the xml file.  So this page just retrieves the specified article from the repository and displays it.

Here's the detail page:

<%@ Page Title="Article Detaiils" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="ArticleDetails.aspx.cs" Inherits="MyWebSite.ArticleDetails" %>

<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">

    <div class="jumbotron shadow">
        <div class="text-center">
            <h1><%=post.Title %></h1>
            <p class="lead"><%=post.Summary%></p>
        </div>
    </div>

    <div class="row">
        <div class="col-md-12">
            <%=post.Content %>
        </div>
    </div>

    <div class="row">
        <div class="col-md-12 text-right">
            Written by <%=post.Author%> on <%=post.Date.ToString("MM/dd/yyyy") %>
        </div>

    </div>

</asp:Content>

And this is the code behind:

using MyWebSite.Classes;
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;

namespace MyWebSite
{
    public partial class ArticleDetails : System.Web.UI.Page
    {
        public Article post;

        protected void Page_Load(object sender, EventArgs e)
        {
            post = new Article();

            string strId = Request.QueryString["id"];
            if (strId != null)
            {
                if (int.TryParse(strId, out int id))
                {
                    ArticleRepository repo = new ArticleRepository();
                    Article foundPost = repo.FindArticle(id);
                    if (foundPost != null) post = foundPost;

                    // add description metadata
                    HtmlMeta meta = new HtmlMeta
                    {
                        Name = "description",
                        Content = post.Summary
                    };
                    this.Page.Header.Controls.Add(meta);

                    // add keywords metadata
                    meta = new HtmlMeta
                    {
                        Name = "keywords",
                        Content = post.Title
                    };
                    this.Page.Header.Controls.Add(meta);

                    Page.Title = post.Title;
                }

            }
        }
    }
}

These pages are only for display of the articles. For now I use a simple text file editor to create the articles and store them in the XML file. I may decide to build an editor page at some point in time, but for now this rudimentary system meets my needs.

In the interest of completeness I am including a sample xml file.

<?xml version="1.0"?>
<ArrayOfArticle xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Article>
    <Id>1</Id>
    <Title>Sample Article Title</Title>
    <Author>Me</Author>
    <Date>2020-07-17T15:04:41.1463148-04:00</Date>
    <Category>Web Development</Category>
    <Summary>Summary for a sample article</Summary>
    <Content>
      <![CDATA[<p>This is a sample article</p>]]>
    </Content>
  </Article>
</ArrayOfArticle>

I hope you find this useful. And in case you were wondering, this technique was how I built the page you are reading right now.

Written by KB3HHA on 07/19/2020