How to Build a Blog using Blazor WASM and Strapi

Shada - Nov 8 '21 - - Dev Community

Using Blazor, you can build an interactive web application using C#. In this article, I will show you how to create a simple but blazing fast blog using Blazor WebAssembly, Tailwind CSS, and Strapi. We will be using Strapi, the leading open-source headless CMS, to store our posts and Blazor for the frontend.

Prerequisites

Before you can jump into this content, you need to have a basic understanding of the following.

  1. Installed the latest .NET Core SDK.
  2. Install Blazor project templates
  3. Basic knowledge of Blazor
  4. Basic understanding of Strapi - get started here.
  5. Download and install Node.js

What is Blazor?

Blazor is a web framework from Microsoft which allows creating a web application using C# and HTML. If you are a developer using C# and want to build a blazing web application, you should check out Blazor.

Accourding to Blazor's docs: “Blazor is a free and open-source web framework that enables developers to create web apps using C# and HTML. Microsoft is developing it.

Blazor lets you build interactive web UIs using C# instead of JavaScript. Blazor apps are composed of reusable web UI components implemented using C#, HTML, and CSS.

Both client and server code is written in C#, allowing you to share code and libraries. Blazor can run your client-side C# code directly in the browser, using WebAssembly. Because it's real .NET running on WebAssembly, you can re-use code and libraries from server-side parts of your application.”

Blazor applications can run directly in the browser, C# code runs in the browser with the help of webassembly or can on the server-side, i.e., client-side code runs on the server-side and communicates with the browser by sending events using SignalR. So in this article, we will use Blazor Web assembly to create a client side-blog app.

What is Strapi - A Headless CMS?

Strapi is a leading open-source headless CMS based on Node.js to develop and manage content using Restful APIs and GraphQL.

With Strapi, we can scaffold our API faster and consume the content via APIs using any HTTP client or GraphQL enabled frontend.

Scaffolding a Strapi project

To scaffold a new Strapi project is very simple and works precisely as installing a new frontend framework.

We are going to start by running the following commands and testing them out in our default browser.

    npx create-strapi-app strapi-api --quickstart
    # OR
    yarn create strapi-app strapi-api --quickstart
Enter fullscreen mode Exit fullscreen mode

The command above will scaffold a new Strapi project in the directory you specified.

Next, run yarn build to build your app and yarn develop to run the new project if it doesn't start automatically.

The last command will open a new tab with a page to register your new system admin. Go ahead and fill out the form and click on the submit button to create a new Admin.

Build the Posts collection

Next, we will create a new CollectionType that will store the details of each post.

For instance, we will create a Collection Type called posts with fields like title, content, author, and image.

To create our first CollectionType, log into the Admin dashboard and follow these steps.

Click Collection Type Builder on the left side of the page. Click on Create New Collection Type still at the left side of the page and fill in Posts as the display name.

Click on Continue to create a new Posts collection. That's the flexibility of Strapi.

We need to fill the Posts collection with lots of post data. You can achieve this in two ways: using the Admin UI and using Strapi generated API.

We will use the Admin UI to create a post (on it soon). Click on Continue, and it will present you with another modal to select fields for your Collection Type.

Select Text and fill in Title in the Text field. Click on Add another field and select Rich Text for the content and again, Click on Add another field and select Media for the image field and finally select Text for Author field.

After adding all the required fields, click on Save to save the collection and click on the Posts name on the left side.

Next, click on the Add New Posts button to add a new post. So for this blog application, we will add a couple of posts. You can add any post of your choice and click on the Save and Publish buttons afterward.

Setting up Blazor project

We can create a Blazor project using two ways:

  1. Using dot CLI commands
  2. Using Visual Studio Editor

1. Using dot CLI commands

Open a command prompt and go to the location where you want to create your Blazor project and run the below commands:

    > dotnet new blazorwasm -o StrapiBlazorBlog
    > cd StrapiBlazorBlog
    > dotnet run
Enter fullscreen mode Exit fullscreen mode

Now open the http://localhost:5000/ to see the StrapiBlazorBlogblazor app.

2. Using Visual Studio Editor

Open a visual studio editor, click on Create new project, and search for Blazor.

Select Blazor WebAssembly app and click on the Next button. After that, provide a project name and click on the Next button.

After that, click on the Create button. That's it, and you have successfully created a Blazor project.
Now to run the app, click on the IIS Express button.

You can see the app running as below:

Building a Blog Application

Now we have created our Blazor project, so the next step is to create a blog application.

Integrating Strapi API with Blazor App

After creating the Cryptos collection successfully, it's time to allow public access to the collection because access will be denied if we try to access it with our public HTTP client.

To allow public access, follow these steps to enable permissions and roles in the Strapi Admin dashboard.

Click on the Settings item on the sidebar menu, then on the Roles item on the second sidebar menu that appears. On the right section, click on the Public item and scroll down.

You will see all the APIs with their handlers. Click on the Select all checkbox and click on the Save button at the top. This setting will allow public access to all the Posts APIs in our Strapi project.

So now, to access the posts, we have to use URLs such as http://localhost:1337/posts. In our Blazor project to store the Strapi API URL, create a new file app settings.json in the wwwroot folder and add the below code:

    {
      "AppSettings": {
        "STRAPI_API_URL": "http://localhost:1337/" // replace your local strapi url
      }
    }
Enter fullscreen mode Exit fullscreen mode

Create a folder called Models and create a class called AppSettings.cs inside it and add the below code:

    namespace StrapiBlazorBlog.Models
    {
        public class AppSettings
        {
            public string STRAPI_API_URL { get; set; }
        }
    }
Enter fullscreen mode Exit fullscreen mode

Now open the Program.cs file to add below code to which is required to read the data from the appsettings.json file anywhere in the application.

    using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using StrapiBlazorBlog.Models;
    using System;
    using System.Net.Http;
    using System.Threading.Tasks;

    namespace StrapiBlazorBlog
    {
        public class Program
        {
            public static async Task Main(string[] args)
            {
                var builder = WebAssemblyHostBuilder.CreateDefault(args);
                builder.RootComponents.Add<App>("#app");

                builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

                await builder.Build().RunAsync();
            }

            public static void ConfigureServices(IServiceCollection services)
            {
                // Example of loading a configuration as configuration isn't available yet at this stage.
                services.AddSingleton(provider =>
                {
                    var config = provider.GetService<IConfiguration>();
                    return config.GetSection("App").Get<AppSettings>();
                });
            }
        }
    }
Enter fullscreen mode Exit fullscreen mode

So we have set up the integration with Strapi API with the Blazor app. Now the next step is to create a component for our blog application.

Display all blogs on the home page

We are using Tailwind CSS to style our awesome blog. So open the index.html file and add below CDN:

    <link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">
Enter fullscreen mode Exit fullscreen mode

Create a class called Post.cs with the same property in our Strapi posts collection.

    namespace StrapiBlazorBlog.Models
    {
        public class Post
        {
            public int Id { get; set; }
            public string Title { get; set; }
            public string Content { get; set; }
            public string Author { get; set; }
            public Image Image { get; set; }
        }

        public class Image
        {
            public string Url { get; set; }
        }
    }
Enter fullscreen mode Exit fullscreen mode

Now open Pages/inde.razor file and add below code to show all the posts fetch from Strapi into our client app:

    @page "/"
    @inject HttpClient Http
    @using Microsoft.Extensions.Configuration;
    @using Models
    @inject IConfiguration Configuration

    @if (allPosts == null)
    {
        <p><em>Loading...</em></p>
    }
    else
    {
    <section class="text-gray-600 body-font">
        <div class="container px-5 py-4 mx-auto">
            <div class="text-center mb-20">
                <h1 class="sm:text-3xl text-2xl font-medium title-font text-gray-900 mb-4">Strapi Blazor Blog App</h1>
                <p class="text-base leading-relaxed xl:w-2/4 lg:w-3/4 mx-auto text-gray-500s">The perfect blog application starter build using leading open source headless CSM called Strapi and Blazor.</p>
                <div class="flex mt-6 justify-center">
                    <div class="w-16 h-1 rounded-full bg-indigo-500 inline-flex"></div>
                </div>
            </div>
            <div class="flex flex-wrap -m-4">
                @foreach (var post in allPosts)
                {
                    <div class="xl:w-1/4 md:w-1/2 p-4">
                        <div class="bg-gray-100 p-6 rounded-lg">
                            <img class="h-40 rounded w-full object-cover object-center mb-6" src="@post.Image.Url" alt="content">
                            <h2 class="text-lg text-gray-900 font-medium title-font mb-4">@post.Title</h2>
                            <NavLink href="@($"post/{post.Id.ToString()}")">
                                <a class="text-indigo-500 inline-flex items-center">
                                    Read More
                                    <svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-4 h-4 ml-2" viewBox="0 0 24 24">
                                        <path d="M5 12h14M12 5l7 7-7 7"></path>
                                    </svg>
                                </a>
                            </NavLink>
                        </div>
                    </div>
                }
            </div>
        </div>
    </section>
    }


    @code {
        private Post[] allPosts = null;
        public string strapi_api_url;

        protected override async Task OnInitializedAsync()
        {
            strapi_api_url = Configuration["AppSettings:STRAPI_API_URL"];
            var url = "{STRAPI_API_URL}/posts";
            allPosts = await Http.GetFromJsonAsync<Post[]>(url.Replace("{STRAPI_API_URL}", strapi_api_url));
            if (allPosts != null && allPosts.Length >0)
            {
                foreach(var post in allPosts)
                {
                    post.Image.Url = strapi_api_url + post.Image.Url;
                }
            }
        }

    }
Enter fullscreen mode Exit fullscreen mode

Update the Shared/NavMenu.razor file to remove extra menu from sidebar.

    <header class="text-gray-600 body-font">
        <div class="container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center">
            <a class="flex order-first lg:order-none lg:w-1/5 title-font font-medium items-center text-gray-900 lg:items-center lg:justify-center mb-4 md:mb-0">
                <img src="/blazor_strapi_logo.png" alt="logo" style="height:60px" />
                <span class="ml-3 text-3xl">StrapiBlazorBlog</span>
            </a>
        </div>
    </header>
Enter fullscreen mode Exit fullscreen mode

Update MainLayout.razor file as below:

    @inherits LayoutComponentBase

    <div>
    <NavMenu />

    <div>
        @Body
    </div>
    </div>
Enter fullscreen mode Exit fullscreen mode

Now run the application and see the output as below:

Wow 👌👌 We have now displayed all our posts on the home page successfully.

Display a single blog post

So next part is when we click on the Read more button from each post, we have to display details of the particular post. So we will create a new page called PostDetails.razor under the Pages folder and below code:

    @page "/post/{Id}"
    @inject HttpClient Http
    @inject NavigationManager NavigationManager
    @using System.Text.Json.Serialization
    @using Microsoft.Extensions.Configuration;
    @using Models
    @inject IConfiguration Configuration

    @if (postDetails == null)
    {
        <p><em>Loading...</em></p>
    }
    else
    {
        <section class="text-gray-700 body-font">
            <div class="container mx-auto flex px-5 pb-24 items-center justify-center flex-col">
                <h1 class="title-font sm:text-4xl text-3xl mb-4 font-medium text-gray-900">@postDetails.Title</h1>
                <img class=" mb-10 object-cover object-center rounded" alt="hero" src="@postDetails.Image.Url" style="height:400px;width:900px">
                <div class=" w-full">
                    <div class="mb-8 leading-relaxed">@((MarkupString)postDetails.Content)</div>
                </div>
                <div class="p-2 w-full">
                    <button class="flex mx-auto text-white bg-indigo-500 border-0 py-2 px-8 focus:outline-none hover:bg-indigo-600 rounded text-lg" @onclick="NavigateToIndexComponent">Back</button>
                </div>
            </div>
        </section>
    }

    @code {
        [Parameter] public string Id { get; set; }

        private Post postDetails = null;

        public string strapi_api_url;

        protected override async Task OnInitializedAsync()
        {

            strapi_api_url = Configuration["AppSettings:STRAPI_API_URL"];
            var url = "{STRAPI_API_URL}/posts/{Id}";
            url = url.Replace("{STRAPI_API_URL}", strapi_api_url);
            url = url.Replace("{Id}", Id);
            postDetails = await Http.GetFromJsonAsync<Post>(url);

            if (postDetails != null)
            {
                postDetails.Image.Url = strapi_api_url + postDetails.Image.Url;
            }
        }

        private void NavigateToIndexComponent()
        {
            NavigationManager.NavigateTo("");
        }

    }
Enter fullscreen mode Exit fullscreen mode

Now rerun the app and click on the Read More button from any post, and you will see details of that post.

Deployment

Now we have our Strapi backend API and our Blazor frontend app. So we will deploy strapi to Heroku and the Blazor app to Netlify. To deploy Starpi API on Heroku, check out this article.

We can now deploy our Blazor application on any hosting platform. We are deploying our app on Netlify using Github Actions.

  1. First, create a repo in GitHub and commit your code to that repository.
  2. Login to Netlify and create a new site.
  3. We need a Personal Access Token and Site Id to deploy our app to Netlify. So Go to Profile and generate a Personal Access Token.

Go to a new site created in Netlify and navigate to site details and copy the AppId.

Go to repository settings → Click on Secrets → add the above two secrets.

The next step is to create a Github action. So click on Actions and then select New Workflow and select the .NET Core template. After that, add the below code to the yml file.

    name: .NET

    on:
      push:
        branches: [ master ]
      pull_request:
        branches: [ master ]

    jobs:
      build:

        runs-on: ubuntu-latest

        steps:
        - uses: actions/checkout@v2
        - name: Setup .NET
          uses: actions/setup-dotnet@v1
          with:
            dotnet-version: 5.0.x
        - name: Restore dependencies
          run: dotnet restore
        - name: Build
          run: dotnet build --configuration Release --no-restore
        - name: Publish Blazor webassembly using dotnet 
          #create Blazor WebAssembly dist output folder in the project directory
          run: dotnet publish -c Release --no-build -o publishoutput
        - name: Publish generated Blazor webassembly to Netlify
          uses: netlify/actions/cli@master #uses Netlify Cli actions
          env: # These are the environment variables added in GitHub Secrets for this repo
              NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
              NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
          with:
              args: deploy --dir=publishoutput/wwwroot --prod #push this folder to Netlify
              secrets: '["NETLIFY_AUTH_TOKEN", "NETLIFY_SITE_ID"]'
Enter fullscreen mode Exit fullscreen mode

After deployment you can see the site as live.

Conclusion

This article demonstrated how to build a simple blog application using Blazor for frontend and Strapi as the Backend. Strapi has lots of integration with other frameworks, so please check out Strapi blog for more information.

Let me know you have any suggestions and what you will be building with the knowledge.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .