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博客开发笔记:部署
Nginx 反向代理 后端配置中间件 app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto }); Nginx配置 server { listen
2022-12-05 · StarBlog博客开发笔记 · 约 1 分钟
StarBlog博客开发笔记(15):生成随机尺寸图片
系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetCore开发博客项目 StarBlog - (3) 模型设计 基于.NetCore开发博客项目 StarBlog - (4) markdown博客批量导入 基于.NetCore开发博客项目 StarBl
2022-07-18 · StarBlog博客开发笔记 · 约 1 分钟
StarBlog博客开发笔记(24):统一接口数据返回格式
前言 开发接口,是给客户端(Web前端、App)用的,前面说的RESTFul,是接口的规范,有了统一的接口风格,客户端开发人员在访问后端功能的时候能更快找到需要的接口,能写出可维护性更高的代码。 而接口的数据返回格式也是接口规范的重要一环,不然一个接口返回JSON,一个返回纯字符串,客户端对接到数据时一脸懵逼,没法处理啊。 合格的接口返回值应该包括状态码、提示信息和数据。 就像这样: { st
2023-01-02 · StarBlog博客开发笔记 · 约 1 分钟
基于.NetCore开发博客项目 StarBlog - (31) 发布和部署
部署?老生常谈。dotnet8的AOT吹得天花乱坠,StarBlog还没用上,只能选框架依赖或自包含。supervisor、docker、流水线,随便选,反正都一样麻烦。
2024-02-08 · StarBlog博客开发笔记 · 约 1 分钟
讨论
共 0 条加载评论中…