Published: 21 August 2023

Simple Blazor Blog

I recently started exploring Blazor. It's very intersting to be able to build interactive client applications using C# for the front end and use C# as full-stack technology. Something that Microsoft tried to achieve before with SilverLight but this time around we have more mature and standard web technologies to work with. Namely, WebAssembly and WebSockets.

To get more familiarized with Blazor, I decided to try to build a simple blogging platform. I wanted to host it on GitHub Pages, therefore Blazor WASM was the obvious choice as it can be hosted in a static site. I also wanted to be able create my posts as individual pages (Ideally in Markdown), and let Blazor generate the navigation during build process.

First thing that I immediatley noticed is the delayed rendering of the website while the WebAssembly assets are downloaded. That also meant that web crawlers wouldn't be able to render my website if they couldn't load WebAssembly assets properly. And while .NET 8 is going to introduce Server Side Rendering (SSR), that wouldn't work for a static host like GitHub Pages. Luckily I found a solution quickly!

Server Side Rendering

Thanks to Niels Swimberghe's detailed Blog Post, I learned about using react-snap, which works well with Blazor WASM apps despite the name suggesting it works for React apps only. All you have to do is follow the instructions in the blog and finally run the below command:

npx react-snap

Since I had some external libraries in my app, which interefered with the site generation. I had to make a small modification to the configuration by adding "skipThirdPartyRequests": true. That's all for SSR!

{
    "reactSnap": {
        "source": "output/wwwroot",
        "skipThirdPartyRequests": true,
        "minifyHtml": {
            "collapseWhitespace": true,
            "removeComments": true
        },
        "puppeteerArgs": ["--no-sandbox", "--disable-setuid-sandbox"]
    }
}    

Blog Navigation

Next I wanted to have a way that allows me to navigate the blog posts without having to manually update the navigation each time. Ideally, whenever I want to add a new post I should add a new file that contains my blog post and thats it! Luckily I found an interesting way to do it using reflections.

Blazor components can support Routing by using the @page directive which assigns a RouteAttribute to the component. Therefore it is possible to create a simple service that scans the assembly and find the components that have a RouteAttribute.

To make things more organized we can structure the posts as below:

Pages/
    Posts/
        2023/
            2023-08-21-Post-Title.razor
        2022/
            ...
    _Imports.razor

Inside _Imports.razor, we define the layout for the posts and the default base class.

@layout PostLayout
@inherits BlogPostComponent

Each post name follows a pattern that defines the post date in the format yyyy-mm-dd. Followed by the post title. We can then use a library like Humanizer to generate the post title from the URL. Each post can be assigned a route that starts with a common prefix, e.g. /posts/.

@page '/posts/Post-Title'

Finally, we create a service that provides us the list of available blog posts.

using System.Reflection;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Components;
using Humanizer;

namespace BlogEngine;

public record BlogPost(string Title, string Url, DateTime Timestamp, Type Type);

public class BlogPostService
{
    const string pattern = @"[0-9]{4}_[0-9]{2}_[0-9]{2}_";

    public List<BlogPost> GetBlogPosts(Assembly assembly)
    {
        var components = assembly
            .ExportedTypes
            .Where(t => t.IsSubclassOf(typeof(BlogPostComponent)));

        var blogPosts = components
            .Select(component => GetBlogPost(component))
            .Where(post => post is not null)
            .ToList();

        return blogPosts;
    }

    public BlogPost GetBlogPost(Type component)
    {
        var attributes = component.GetCustomAttributes(inherit: true);

        var routeAttribute = attributes.OfType<RouteAttribute>().FirstOrDefault();

        if (routeAttribute != null)
        {
            var route = routeAttribute.Template;
            if (!string.IsNullOrEmpty(route) && route.StartsWith("/posts/"))
            {
                var name = Regex.Replace(component.Name, pattern, "");

                var match = Regex.Match(component.Name, pattern);
                var date = DateTime.MinValue;
                if(match.Success) {
                    DateTime.TryParseExact(match.Value,"yyyy_MM_dd_", null, System.Globalization.DateTimeStyles.None, out date);
                }
                return new BlogPost(name.Humanize(), route, date, component);
            }
        }

        return null;
    }
}    

And that's it! Now we can have a list of the blog posts which we can display the way we want. We can add the list to the navigation menu or create a blog archive.

If we wanted to display the post content, we can user Blazor's DynamicComponent. By Simply passing the type.

@foreach (var blogPost in BlogPosts)
{
    
   <DynamicComponent Type="@blogPost.Type" />
           
}

Next Steps

The source code is available at GitHub. Next I will be looking for a way to use Markdown for authoring the blog posts which could be more convenient. I'm excited to keep learning about Blazor and believe it has good potential to be a contender client side library.