Creating Blog using Blazor
Introduction
Creating a blog using Blazor is an interesting exercise to learn and explore the possibilities of Blazor. It’s also fun to discover a new way to create a frontend app using C#, Razor, Wasm, MudBlazor and DotNet.
Blazor Wasm has been chosen as the framework allows hosting the blog on GitHub pages. Since Blazor is the latest feature of ASP.NET for building interactive web UIs, it provides a valuable opportunity to try and learn this cutting-edge technology.
This article is not a step-by-step guide on how to build the website/blog. You can explore the code and its history in the source code repository. Instead, the article focuses on the issues and interesting scenarios encountered during the app’s development process.
Create projects and solution
Used version of dotnet and VScode
Dotnet version 7.0 has been used and VSCode has been the chosen development tool for creating the app. However, it’s worth noting that there have been some challenges when developing Blazor apps using VSCode. For a smoother experience and full support of all Blazor features, it is recommended to use Visual Studio as the integrated development environment (IDE).
How to create blazor wasm solution
To create a Blazor app, you can start by running the following command dotnet new blazorwasm
. This command will generate a new Blazor application.
To create a project that generates HTML code from Markdown, you can use dotnet new console
command. After creating the console project, you can follow the instructions provided in the Source Generators article to implement the necessary functionality.
Both of these projects can be combined in a single solution, allowing you to have a Blazor app alongside the project that generates HTML code from Markdown.
Why source generator feature as it doesn’t generate separate pages for each blog post
In the beginning, the idea was to generate a separate Blazor component page for each Markdown file. However, it was discovered that source generators do not support the generation of non-C# files. As a result, the decision was made to generate a single file that contains a map with the blog post names and their corresponding HTML content.
SourceGenerator
How to start with it and examples
Source generators were introduced in .NET 5. They are a type of code that runs during compilation and enables the creation of additional files that are compiled alongside the rest of the code. There is useful cookbook available that can be used to learn more about source generators.
Source generator reload - dotnet build-server shutdown; dotnet clean; dotnet run
There is an interesting behavior regarding how source generators work. If you make changes to the code in the source generator project and expect those changes to immediately reflect in the project that uses the generated code, you will be disappointed. By simply running dotnet build
or
dotnet run``, the generated code in the target project will not be updated. In order to see the changes, you will need to execute the command dotnet build-server shutdown to clear the cache.
I encountered this issue and found a solution here.
To provide further context, the way it works is that the C# compiler, csc.exe
, typically starts a “compilation server” named VBCSCompiler.exe
to avoid the overhead of starting the process repeatedly. csc.exe
forwards the parameters to VBCSCompiler.exe
via interprocess communication (IPC) to perform the compilation. The compiler process, once loaded, will not pick up changes to the source generator DLL because it is loaded dynamically. Shutting down the VBCSCompiler.exe
process or waiting for the idle timeout is necessary to load the updated DLL.
Issue with debugging and tracing
Debugging source generators can be challenging, but there is a trick that can help:
- Add the following code at the beginning of the
Execute
method:
while (!System.Diagnostics.Debugger.IsAttached)
System.Threading.Thread.Sleep(500);
- Ensure that your
launch.json
file has a configuration to attach to the running dotnet process. Add the following configuration:
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
},
- Rebuild the entire project by running
dotnet build-server shutdown; dotnet clean; dotnet run
for your Blog web app. This command will pause at the building phase, allowing you time to attach to the building process. The process that needs to be attached to is typicallydotnet.exe
, which executes (exec
) the VBCSCompiler.dll. For example, it could be something likedotnet.exe exec C:\Program Files\dotnet\sdk\7.0.203\Roslyn\bincore\VBCSCompiler.dll
.
The solution was found on StackOverflow.
Using SourceGenerator to create Html from MD files
The ArticleGenerator
` class contains the logic to convert Markdown files to their HTML representation. When the main project is built, a dictionary is created with article names as keys and HTML content and metadata as values. The Markdig library is utilized to perform the conversion from Markdown to HTML.
Using TargetPathWithTargetPlatformMoniker
Since Markdig is used in the source generator project, it is necessary to inform the main project during the build process that Markdig is required. This can be accomplished by following a similar approach as shown in this sample.
To achieve this, the NuGet reference needs to be marked with GeneratePathProperty="true"
, and the generated names, such as PKGMarkdig
, PKGMarkdown_ColorCode
, PKGColorCode_Core
, and PKGColorCode_Html
, should be used in the TargetPathWithTargetPlatformMoniker
property. Note that the names should have a PKG prefix, and any dots should be replaced with underscores.
Referencing SourceGenerator from Blazor app
To reference a source generator project in the project that will use it, you need to add manually additional attributes to the reference definition. These attributes include ReferenceOutputAssembly
and OutputItemType
. Additionally, the IncludeAssets
attribute should have a value all
.
<ProjectReference Include="..\Generators\Generators.csproj" ReferenceOutputAssembly="false" OutputItemType="Analyzer">
<IncludeAssets>all</IncludeAssets>
</ProjectReference>
By including these attributes and setting IncludeAssets
to all
, you ensure that the source generator project is properly referenced and that its assets are available for use in the consuming project.
Use AdditionalFiles to provide access to blogs from SourceGenerator
As Markdown files are converted to HTML during the compilation of the main Blazor project, it is necessary to reference them in the main project so that the source generator project can find them. To achieve this, you can use the special construct called AdditionalFiles.
Here’s how you can include the Markdown files as AdditionalFiles in the project file (XML format):
<AdditionalFiles Include="..\Blog\**\*.md" />
By adding this line to the project file, you are telling the compiler to consider all Markdown files (*.md) located in the ..\Blog directory and its subdirectories as AdditionalFiles. These files will then be accessible to the source generator project during the build process.
Blazor app
Using MudBlazor
The decision has been made to use MudBlazor, a Blazor Component Library that offers a wide range of components and is very user-friendly. With MudBlazor, there is no need to invest significant time in creating raw HTML and CSS code. Instead, developers can leverage the well-designed and convenient components provided by the library, making the development process much more efficient and enjoyable.
Adding styles to MudText title
To add custom styling to a MudBlazor component, you can use the Style
property:
<MudText Typo="Typo.h5" Class="flex-grow-1" Style="white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">@articleService.ArticleName</MudText>
In this example, the Style
property is used to apply custom CSS styling to the MudText
component. The specified CSS styles will make sure that the content of the MudText
component does not wrap, and any overflow is hidden with an ellipsis. The Class
property is also used here to apply additional CSS classes to the component for more styling options.
Copy assets and images to output folder
To copy images and assets from the Blog folder to the output folder, you can use the following commands in the project file:
<ItemGroup>
<ContentWithTargetPath Include="..\Blog\images\*.*">
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>wwwroot/images/%(RecursiveDir)%(Filename)%(Extension)</TargetPath>
</ContentWithTargetPath>
<ContentWithTargetPath Include="..\Blog\assets\*.*">
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>wwwroot/assets/%(RecursiveDir)%(Filename)%(Extension)</TargetPath>
</ContentWithTargetPath>
</ItemGroup>
In this code snippet, the ItemGroup
element is used to specify the content that needs to be copied. The ContentWithTargetPath
item includes the source path for the images and assets in the Blog folder. The Link
element specifies the target path for the copied files. The CopyToOutputDirectory
element with the value PreserveNewest
ensures that only newer files are copied. The TargetPath
element sets the output path for the copied files within the wwwroot/images
and wwwroot/assets
folders, respectively.
Use Katex in blog posts
To support Katex for displaying formulas in the blog app, you need to make modifications to the index.html
file and the Blazor component.
- Include the Katex script in the head of the
index.html
file:
<!-- The loading of KaTeX is deferred to speed up page rendering -->
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.7/dist/katex.min.js" integrity="sha384-G0zcxDFp5LWZtDuRMnBkk3EphCK1lhEf4UEyEM693ka574TZGwo4IWwS6QLzM/2t" crossorigin="anonymous"></script>
- Add the following script to the end of the
index.html
file:
window.callKatex = function() {
var tex = document.querySelectorAll('.math');
for (const el of tex) {
const txt = el.textContent.trim().replace(/^\\\(/, '').replace(/\\\)$/, '').replace(/^\\\[/, '').replace(/\\\]$/, '');
const displayMode = txt.indexOf('begin') > -1;
katex.render(txt, el, { displayMode: displayMode });
}
};
- Call the
callKatex
function in theOnAfterRenderAsync
method of the Blazor component:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await JSRuntime.InvokeVoidAsync("callKatex");
articleService.ArticleName = SelectedArticleName;
}
By following these steps, you will be able to support Katex and render formulas in your Blazor app.
Using recurssion to render folder structure
The Blog app has a navigation panel on the left side that contains a list of articles. The articles are organized using folders, and this organization is represented as a hierarchy in the navigation.
FolderComponent
is a Blazor component that renders the hierarchy by calling itself to render child folders:
@foreach (var folder in FolderValue.Folders)
{
<FolderComponent FolderValue="@folder" SearchTerm="@SearchTerm"></FolderComponent>
}
In this code snippet, the FolderComponent
iterates through the child folders of the current folder (FolderValue). For each child folder, it recursively calls itself (
FolderComponent) to render the sub-folders and articles present in the hierarchy. The
SearchTerm` parameter is also passed to the child components, enabling search functionality within the folders and articles.
Update blog title in a layout based on article
To notify the MainLayout
component about changes in the article name, a service is used with an event that the MainLayout
component subscribes to. Here’s the corrected code:
In the service class:
public event Action OnArticleChange = () => {};
public string ArticleName
{
get { return _articleName; }
set {
if (_articleName != value)
{
if (Blog.Articles.Value()[value].Item1.TryGetValue("title", out var title))
{
_articleName = title;
}
else
{
_articleName = value;
}
NotifyArticleNameChanged();
}
}
}
private void NotifyArticleNameChanged() => OnArticleChange?.Invoke();
In the MainLayout
component:
protected override void OnInitialized()
{
articleService.OnArticleChange += StateHasChanged;
}
public void Dispose()
{
articleService.OnArticleChange -= StateHasChanged;
}
In the Article
component:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await JSRuntime.InvokeVoidAsync("callKatex");
articleService.ArticleName = SelectedArticleName;
}
With these changes, when the SelectedArticleName
changes in the Article
component, it will trigger a change in the MainLayout
component, causing it to re-render itself and reflect the updated article name.
Using MarkupString to render blog html
As the HTML representation of the article is created during the building of the app, it is required to render the raw HTML in the Article
component. To achieve that, you can use MarkupString
:
@(new MarkupString(articleService.Content(SelectedArticleName)))
In this code snippet, MarkupString
is used to render the HTML content obtained from the articleService.Content(SelectedArticleName)
method. The MarkupString
class allows you to render raw HTML as markup in a Blazor component. By wrapping the articleService.Content(SelectedArticleName)
with MarkupString
, the raw HTML will be displayed properly in the Article
component.
GitHub actions to deploy blog to GitHub pages
As it is a Blazor WebAssembly app, you can host it on GitHub Pages. To achieve this, you need to build and publish the application using GitHub Actions. Below is the corrected YAML configuration for the GitHub Actions workflow:
name: Deploy to GitHub Pages
# Run workflow on every push to the master branch
on:
push:
branches: [ master ]
jobs:
deploy-to-github-pages:
# use ubuntu-latest image to run steps on
runs-on: ubuntu-latest
steps:
# uses GitHub's checkout action to checkout code form the master branch
- uses: actions/checkout@v2
- name: Setup .NET SDK
uses: actions/setup-dotnet@v3
with:
dotnet-version: 7.0.x
- name: Clear NuGet package cache
run: dotnet nuget locals all --clear
- name: Restore NuGet packages
run: dotnet restore
- name: Build
run: dotnet build
# publishes Blazor project to the release-folder
- name: Publish .NET Core Project
run: dotnet publish BlazorBlog/BlazorBlog.csproj -c Release -o release --nologo
# copy index.html to 404.html to serve the same file when a file is not found
- name: copy index.html to 404.html
run: cp release/wwwroot/index.html release/wwwroot/404.html
# add .nojekyll file to tell GitHub pages to not treat this as a Jekyll project. (Allow files and folders starting with an underscore)
- name: Add .nojekyll file
run: touch release/wwwroot/.nojekyll
- name: Commit wwwroot to GitHub Pages
uses: JamesIves/github-pages-deploy-action@v4
with:
branch: gh-pages
folder: release/wwwroot
With this YAML configuration, the GitHub Actions workflow will trigger on pushes to the main
branch. It will build the Blazor project, publish it to the release
folder, and deploy the contents of the release/wwwroot
directory to the gh-pages
branch. This will host your Blazor WebAssembly app on GitHub Pages.
Summary
In this article, I share my experience creating a blog application using Blazor WebAssembly, exploring its versatile capabilities with C#, Razor, WebAssembly, and .NET. Throughout the development process, I encountered and addressed various challenges, documenting the solutions for readers to learn from. Utilizing MudBlazor’s component library expedited the frontend development, while source generators optimized file generation during compilation. I also navigated the integration of KaTeX for displaying formulas and managed Markdown files and assets seamlessly. Ultimately, the blog application was successfully hosted on GitHub Pages with GitHub Actions. This article serves as a concise guide for building a Blazor WebAssembly blog and offers insights into overcoming common hurdles, inspiring developers to explore Blazor’s potential for dynamic web applications.
Source code is available by link.