Blazor

Building a blogging app with Blazor: Listing Posts

This post is part of a series, Building a blogging app with Blazor.

In part 1 of the series I did a load of setup work, getting the solution and project structures in place. I also added a theme to the site to make things look a bit prettier.

In this post, I'm going to work on the home page of the app. I'll create the first endpoint on the API and get the Blazor app making requests to it. By the end of this post, the app will be able to show a list of blog posts on the home screen.

The API

I'm going to start by creating the API to return the list of blog posts. The first thing I want to do is define a class to represent a blog post. In the shared project I'm going to create a new class called BlogPost.cs and add the following code.

using System;

namespace WordDaze.Shared
{
    public class BlogPost
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Author { get; set; }
        public DateTime Posted { get; set; }
        public string Post { get; set; }
        public string PostSummary 
        { 
            get {
                if (Post.Length > 50)
                    return Post.Substring(0, 50);

                return Post;
            }
        }
    }
}

One of the great things about Blazor is I can now reference this class from both my Server and Client projects. No more code duplication, how cool is that!

Now I'm going to define the endpoint which will return the list of blog posts. I'm going to create a new controller in the server project, called BlogPostsController.cs, with the following code.

using WordDaze.Shared;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;

namespace WordDaze.Server.Controllers
{
    public class BlogPostsController : Controller
    {
        private List<BlogPost> _blogPosts { get; set; } = new List<BlogPost> {
            new BlogPost {
                Id = 1,
                Title = "If only C# worked in the browser",
                Post = "Lorem ipsum dolor sit amet...",
                Author = "Joe Bloggs",
                Posted = DateTime.Now.AddDays(-30)
            },
            new BlogPost { 
                Id = 2, 
                Title = "400th JS Framework released", 
                Post = "Lorem ipsum dolor sit amet...",
                Author = "Joe Bloggs",
                Posted = DateTime.Now.AddDays(-25)
            },
            new BlogPost { 
                Id = 3, 
                Title = "WebAssembly FTW", 
                Post = "Lorem ipsum dolor sit amet...",
                Author = "Joe Bloggs",
                Posted = DateTime.Now.AddDays(-20)
            },
            new BlogPost { 
                Id = 4, 
                Title = "Blazor is Awesome!", 
                Post = "Lorem ipsum dolor sit amet...",
                Author = "Joe Bloggs",
                Posted = DateTime.Now.AddDays(-15)
            },
            new BlogPost { 
                Id = 5, 
                Title = "Your first Blazor App", 
                Post = "Lorem ipsum dolor sit amet...",
                Author = "Joe Bloggs",
                Posted = DateTime.Now.AddDays(-10)
            },
        };

        [HttpGet(Urls.BlogPosts)]
        public IActionResult BlogPosts()
        {
            return Ok(_blogPosts);
        }
    }
}

There is nothing special here. I've just added a single endpoint which will return some hardcoded test data. I will replace this test data with real data in due course, but this will do for now.

The only thing I will point out is how I've defined the route for this endpoint. I'm taking advantage of the code sharing between client and server once again. And I'm defining my API routes in a URLs class in the Shared project.

namespace WordDaze.Shared
{
    public static class Urls
    {
        public const string BlogPosts = "api/blogposts";
    }
}

I like this as it gets rid of magic strings which I'm not a great fan of. Plus, if I change my URLs for any reason I can do it in one place.

The Client

The first thing I want to do is to add a new file in the Home feature called Home.cshtml.cs. Now this may seem a little odd so let me explain.

When creating a Blazor component both markup and logic go into the same cshtml file. While there is nothing particularly wrong with this, I've spent a lot of time using both MVC and Angular. Because of this I've really grown to like the separation of the template or view, from the logic behind it.

There have been many conversations around creating Blazor components as partial classes to allow a code behind file. This would allow the view code and the logic to be separated, however, this is not currently possible. But there is an @inherits directive available which does allow this separation to be achieved now.

The idea is to have two files for each component. One file contains the view template, the other contains the C# logic. The view then inherits from the logic class via the @inherits directive.

In the new Home.cshtml.cs file I'm going to add the following code.

using Microsoft.AspNetCore.Blazor.Components;
using Microsoft.AspNetCore.Blazor.Services;
using Microsoft.AspNetCore.Blazor;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using WordDaze.Shared;
using System.Linq;

namespace WordDaze.Client.Features.Home
{
    public class HomeModel : BlazorComponent 
    {
        [Inject] private HttpClient _httpClient { get; set; }
        
        protected List<BlogPost> blogPosts { get; set; } = new List<BlogPost>();

        protected override async Task OnInitAsync() 
        {
            await LoadBlogPosts();
        }

        private async Task LoadBlogPosts() 
        {
            var blogPostsResponse = await _httpClient.GetJsonAsync<List<BlogPost>>(Urls.BlogPosts);
            blogPosts = blogPostsResponse.OrderByDescending(p => p.Posted).ToList();
        }
    }
}

I'm overriding the OnInitAsync method and calling LoadBlogPosts. This is using the injected HttpClient to call the API endpoint I created earlier. As in the controller I'm getting the URL from the shared Urls class. I'm then ordering the posts so the newest posts show first. That's it for the HomeModel.

Next I want to adjust the markup in the Home.cshtml file.

@page "/"
@layout MainLayout
@inherits HomeModel

<header class="masthead" style="background-image: url('img/home-bg.jpg')">
    <div class="overlay"></div>
    <div class="container">
        <div class="row">
            <div class="col-lg-8 col-md-10 mx-auto">
            <div class="site-heading">
                <h1>WordDaze</h1>
                <span class="subheading">A Blazor Powered Blogging App</span>
            </div>
            </div>
        </div>
    </div>
</header>

<div class="container">
    <div class="row">
        <div class="col-lg-8 col-md-10 mx-auto">
            @foreach (var post in blogPosts)
            {
                <BlogPostPreview BlogPost=@post></BlogPostPreview>
                <hr />
            }
        </div>
    </div>
</div>

I've now added the @inherits directive as I explained earlier. I've also added a foreach to display each of the blog posts that were returned from the API. I want to encapsulate the markup for a blog post preview as I may want to use it elsewhere in the future, so I'm going to create a BlogPostPreview component.

using Microsoft.AspNetCore.Blazor.Components;
using WordDaze.Shared;

namespace WordDaze.Client.Features.Home
{
    public class BlogPostPreviewModel : BlazorComponent 
    {
        [Parameter] protected BlogPost blogPost { get; set; }
    }
}
@inherits BlogPostPreviewModel

<div class="post-preview">
  <NavLink href="@($"posts/{blogPost.Id}")">
    <h2 class="post-title">
      @blogPost.Title
    </h2>
    <h3 class="post-subtitle">
      @blogPost.PostSummary
    </h3>
  </NavLink>
  <p class="post-meta">Posted by
    <NavLink href="/">@blogPost.Author</NavLink>
    on @blogPost.Posted</p>
</div>

Following the same format as the Home component, the model class is very simple and just defines a property marked with Blazors Parameter attribute. If you are new to Blazor, any property on a component which is populated from a parent must be decorated with this attribute and be either private or protected. If I'm honest I'm still trying to understand why this is better than just having public and private properties. But anyway, this is the way things are.

The view file then just defines the template for a blog post summary and outputs the various bits of blog post information where required.

With that in place I can now spin up the app by running the following command in the server project directory.

dotnet run

I now have a home page that is displaying a list of blog posts.

Wrapping Up

I think that's some good progress and the app can show a list of blog posts on the home page, albeit hardcoded for now. In the next instalment I'll move onto displaying individual blog posts.

Enjoying things so far? If you have any questions or suggestions then please let me know in the comments below. As always all the source code to accompany this series is available on GitHub.