StarBlog Development Notes (7): Page Development — Article Detail Page
Series articles Developing a Blog Project with .NetCore: StarBlog - (1) Why write your own blog? Developing a Blog Project with .NetCore: StarBlog - (2) Environment setup and creating the project Developing a Blog Project with .NetCore: StarBlog - (3) Model design Developing a Blog Project with .NetCore: StarBlog - (4) Batch importing markdown blogs Developing a Blog Project with .NetCore: StarBlog - (5) Starting to build the Web project Developing a Blog Project with .NetCore: StarBl
目录9 个章节
Series Articles
- Developing a Blog Project with .NetCore: StarBlog - (1) Why write your own blog?
- Developing a Blog Project with .NetCore: StarBlog - (2) Environment setup and creating the project
- Developing a Blog Project with .NetCore: StarBlog - (3) Model design
- Developing a Blog Project with .NetCore: StarBlog - (4) Batch importing markdown blogs
- Developing a Blog Project with .NetCore: StarBlog - (5) Starting to build the Web project
- Developing a Blog Project with .NetCore: StarBlog - (6) Page development: blog post list
- Developing a Blog Project with .NetCore: StarBlog - (7) Page development: article detail page
- ...
Preface
The previous post finished developing the article list. Now it's time to write the article detail page (this update isn't late, is it? hehe).
The most important part of a blog site is arguably the article detail page. When users come to read a blog, they care first about the content, then about the reading experience, so the design of this article detail page cannot be sloppy~
Approach
The article body is stored in markdown format. To display it on a web page, the markdown needs to be rendered into HTML.
So there are two approaches:
- One is server-side rendering: use C# to convert markdown to HTML and then render it as a web page
- The other is to have the backend output markdown directly, and use some open-source JS libraries to render the markdown
At first I went with the first approach, server-side rendering, using the C# library Markdig. After using it more deeply, I found that some features I wanted were rather troublesome to implement. In particular, this library has almost no documentation, so customizing anything all came down to reading the source + guessing. In the end I had to give up and switch to the second approach.
This article will cover the implementation of both approaches, with the focus on the second one: client-side rendering.
Server-side Rendering
I've written a detailed introduction to the Markdig library in a previous post, so I won't repeat it here. Interested readers can check it out: Parsing Markdown documents in C# and replacing image links
First, install the Markdig library via NuGet
Markdown can be converted to HTML with a single line of code
Markdig.Markdown.ToHtml(markdownContent);
Of course, the page rendered this way is quite bare: no code highlighting, no blockquote styling, no list styles, and so on. So this alone is definitely not enough.
As the only actively maintained Markdown library for C# at present, Markdig naturally takes extensibility into account. It has an extension system, ships with more than 20 built-in extensions, and you can also install extensions developed by others to achieve effects such as code highlighting.
Using extensions is also simple: just add a pipeline parameter
var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
var result = Markdown.ToHtml("This is a text with some *emphasis*", pipeline);
Markdig itself does not come with a code-highlighting extension, so you need a third-party component. I tested the following two and they work
- Markdig.Prism: client-side rendering, but needs a server-side component to cooperate
- Markdown.ColorCode: server-side rendering
Client-side Rendering
This project ultimately went with client-side rendering. The frontend ecosystem has many markdown components; after looking around, I eventually chose the Editor.md component.
The main reasons were that it makes it relatively convenient to implement a TOC (table of contents) for the article, and it has decent highlighting.
It's simple to use
First, output the markdown into the page
<div id="test-editormd-view" class="post-content">
<textarea id="append-test" style="display:none;">@Model.Content</textarea>
</div>
With display:none added, this textarea is not shown — there's no point showing markdown source to the user
Include the edtor.md stylesheet
<link rel="stylesheet" href="~/lib/editormd/css/editormd.preview.min.css">
Include the editor.md JS — you read that right, that's how many there are. The static assets were already installed in a previous article, so I won't repeat that here. See: (5) Starting to build the Web project
<script src="~/lib/editormd/examples/js/jquery.min.js"></script>
<script src="~/lib/editormd/lib/marked.min.js"></script>
<script src="~/lib/editormd/lib/prettify.min.js"></script>
<script src="~/lib/editormd/lib/raphael.min.js"></script>
<script src="~/lib/editormd/lib/underscore.min.js"></script>
<script src="~/lib/editormd/lib/sequence-diagram.min.js"></script>
<script src="~/lib/editormd/lib/flowchart.min.js"></script>
<script src="~/lib/editormd/lib/jquery.flowchart.min.js"></script>
<script src="~/lib/editormd/editormd.min.js"></script>
Then, use JS to call editor.md's render method
let testEditormdView = editormd.markdownToHTML("test-editormd-view", {
// htmlDecode: "style,script,iframe", // you can filter tags decode
htmlDecode: true,
//toc : false,
tocm: true, // Using [TOCM]
tocContainer: "#custom-toc-container", // 自定义 ToC 容器层
//gfm : false,
//tocDropdown : true,
// markdownSourceCode : true, // 是否保留 Markdown 源码,即是否删除保存源码的 Textarea 标签
emoji: true,
taskList: true,
tex: true, // 默认不解析
flowChart: true, // 默认不解析
sequenceDiagram: true, // 默认不解析
})
Done.
ViewModel
The Post model is just the data stored in the database. Displaying it directly cannot fully meet the needs of the page design, so as before, we need to define a ViewModel to use.
Still placed in StarBlog.Web/ViewModels
The code is as follows
public class PostViewModel {
public string Id { get; set; }
public string Title { get; set; }
public string Summary { get; set; }
public string Content { get; set; }
public string ContentHtml { get; set; }
public string Path { get; set; }
public DateTime CreationTime { get; set; }
public DateTime LastUpdateTime { get; set; }
public Category Category { get; set; }
public List<Category> Categories { get; set; }
}
Compared with the Post model, it adds ContentHtml, and Categories is changed to a list
Service
That's the key rendering part. Now for some of the secondary bits~
The Service's role is to convert the Post model into a ViewModel
Let's just get to the code
public PostViewModel GetPostViewModel(Post post) {
var vm = new PostViewModel {
Id = post.Id,
Title = post.Title,
Summary = post.Summary,
Content = post.Content,
ContentHtml = Markdig.Markdown.ToHtml(post.Content),
Path = post.Path,
CreationTime = post.CreationTime,
LastUpdateTime = post.LastUpdateTime,
Category = post.Category,
Categories = new List<Category>()
};
foreach (var itemId in post.Categories.Split(",").Select(int.Parse)) {
var item = _categoryRepo.Where(a => a.Id == itemId).First();
if (item != null) vm.Categories.Add(item);
}
return vm;
}
Although we're not using the server-side rendering approach, I still kept Markdig's server-side rendering.
View
PS: I skipped the Controller part — it's really too simple, no need to paste the code
There doesn't seem to be much to introduce here either, so I won't paste the full code. The detailed code is here: https://github.com/Deali-Axy/StarBlog/blob/master/StarBlog.Web/Views/Blog/Post.cshtml
Use Bootstrap's Grid layout for two columns: the left column shows the article's TOC, and the right column shows the article's main content.
The top of the page should show the category hierarchy, with different categories separated by "/", but there should be no slash before the first category (a complicated way of putting it)
The code that implements this requirement looks like this
<div>
分类:
@foreach (var category in Model.Categories) {
@if (Model.Categories.IndexOf(category) > 0) {
<span> / </span>
}
<a asp-controller="Blog" asp-action="List"
asp-route-categoryId="@category.Id">
@category.Name
</a>
}
</div>
The result looks roughly like this:

Then we also need to improve how the time is displayed
@Model.LastUpdateTime.ToShortDateString()
@Model.LastUpdateTime.ToString("hh:mm")
The result after this is as follows
The Result

That's about it. I may further polish the page later.
Done~
_EOF_
StarBlog Development Notes (7): Page Development — Article Detail Page
- 本文作者:
- 程序设计实验室
- 版权声明:
- 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
相关文章
StarBlog博客开发笔记:接口返回值包装
我的做法: 自己定义一套返回值包装对象 用过滤器处理状态码 用过滤器包装错误信息 参考资料 【5min+】美化API,包装AspNetCore的返回结果:https://www.cnblogs.com/uoyo/p/12895627.html HTTP 状态码:https://www.runoob.com/http/http-status-codes.html Filters in ASP.NET
2022-02-28 · StarBlog博客开发笔记 · 约 1 分钟
StarBlog博客开发笔记(2):环境准备和创建项目
系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 ... 基于.NetCore开发博客项目 StarBlog - 接口返回值包装 基于.NetCore开发博客项目 StarBlog - 上传图片功能 基于.NetCore开发博客项目 StarBlog - 身份认证
2022-04-22 · StarBlog博客开发笔记 · 约 1 分钟
StarBlog开源博客项目 - 2023年底更新内容一览
前台UI勉强能看,后台总算不那么辣眼睛了。数据库分页改对了,不然性能差得离谱。新增slug检查?哦,终于知道避免重复了。
2023-12-16 · StarBlog博客开发笔记 · 约 1 分钟
StarBlog博客开发笔记(21):开始开发RESTFul接口
前言 最近电脑坏了,开源项目的进度也受到一些影响 这篇酝酿很久了,作为本系列第二部分(API接口开发)的第一篇,得想一个好的开头,想着想着就鸽了好久,索性不扯那么多了,直接开写吧~ 关于RESTFul 网上很多相关的文章都要把RESTFul历史来龙去脉给复制一遍,所以我这就不重复了,现在主要的HTTP接口风格就俩:RPC和RESTFul。 举个例子就可以看出这俩的区别 RPC风格 分别是增删改查的
2023-01-02 · StarBlog博客开发笔记 · 约 1 分钟
讨论
共 0 条加载评论中…