Last month I decided to no longer use WordPress for my blog. So I started to create a custom web application (reinvent the wheel) using ASP.NET Core. One of the primary features I wanted to have was to be able to write posts in Markdown language. I found a great library called CommonMark.NET which is also .NET Core (.NET Standard) compatible. It also offers an easy to use extensibility feature which makes it great for custom html output.
In this post I will explain how I integrated CommonMark.NET in my application and my implementation of syntax highlighting.
To use CommonMark.NET in your project, add the following dependency in the project.json file and save the file to restore the newly added package.:
"dependencies": {
...
"CommonMark.NET": "0.14.0"
...
}
To implement syntax highlighting I used google code prettify javascript library. Code prettify with markdown works when each code block in markdown is prefixed with <?prettify ...?> tag. One option is to simply go over all my blog posts written in markdown and add this tag before each code snippet. But not only it's tedious, it is also polluting the markdown document with additional markup:
The key design goal is readability – that the language [markdown] be readable as-is, without looking like it has been marked up with tags or formatting instructions.
Alternatively, I decided to write a custom html formatter for CommonMark.NET and let it automatically insert the tag before each code block.
Lets start by creating a simple service for working with Markdown documents to use in my application:
namespace arminkarimi.Services
{
public interface IMarkdownService
{
string ConvertMarkdownToHtml(string filename);
}
}
The following code is the implementation of this interface using CommonMark.NET (explanation follows):
using System.IO;
using System.Text;
using CommonMark;
using CommonMark.Syntax;
using Microsoft.AspNetCore.Hosting;
namespace arminkarimi.Services
{
public class CommonMarkDotNetService : IMarkdownService
{
private readonly IHostingEnvironment _env;
public CommonMarkDotNetService(IHostingEnvironment env)
{
_env = env;
}
public string ConvertMarkdownToHtml(string filename)
{
//Reading the file content can be externalized to remove the dependency
//But let's keep it simple in this example
var fileFullPath = $"{_env.ContentRootPath}/Markdown/{filename}.md";
string markdownContent = File.ReadAllText(fileFullPath, Encoding.UTF8);
var commonMarkSettings = CommonMarkSettings.Default.Clone();
commonMarkSettings.OutputDelegate = (doc, output, settings) =>
new CustomHtmlFormatter(output, settings).WriteDocument(doc);
var htmlResult = CommonMarkConverter.Convert(markdownContent, commonMarkSettings);
return htmlResult;
}
private class CustomHtmlFormatter : CommonMark.Formatters.HtmlFormatter
{
public CustomHtmlFormatter(System.IO.TextWriter target, CommonMarkSettings settings)
: base(target, settings)
{
}
protected override void WriteBlock(Block block, bool isOpening, bool isClosing, out bool ignoreChildNodes)
{
if ((block.Tag == BlockTag.FencedCode || block.Tag == BlockTag.IndentedCode)
&& !this.RenderPlainTextInlines.Peek())
{
ignoreChildNodes = false;
if (isOpening)
{
this.Write("<?prettify?>");
}
}
base.WriteBlock(block, isOpening, isClosing, out ignoreChildNodes);
}
}
}
}
ConvertMarkdownToHtml implementation is quite straightforward. I get the markdown file content and pass it to CommonMarkConverter.Convert method to convert it to Html string. To add the <?prettify ...?> tag, I am also customizing the CommonMarkSettings to use a custom html formatter following the CommonMark extensibility.
Code snippets are block of content in CommonMark specification. So, in CustomHtmlFormatter implementation, I am overriding WriteBlock method. Then I check if the block tag is a Fenced Code block and write <?prettify ...?> tag before code block opening.
Now I can use a code like this to convert my markdown blog posts to syntax highlighting enabled html:
post.Content = _markdownService.ConvertMarkdownToHtml("using-commonmark-in-asp-dot-net-core-application.md");
And don't forget to register the service in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSingleton<IMarkdownService, CommonMarkDotNetService>();
...
}
If you want to learn more about dependency injection in .NET Core I highly recommend reading this article.
and add the prettify script to the layout file:
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>
UPDATE: I refactored ConvertMarkdownToHtml to remove reading the file dependency.