How To Render Markdown On The Server In .NET Using Marked
This is mostly a work in progress but here it goes. I’ve been working on a site that uses markdown instead of an html/wysiwyg editor. I first chose PageDown for the client side which is a bootstrap version of the editor used on Stack Overflow, then on the server I used MarkdownSharp to pre-render the html and store it along side the markdown.
This setup was working fine for me but I was looking for some more advanced features, such as GitHub flavored markdown. I also wanted to write custom renderers once and have them run on the client and server.
In another project I’m working on I’ve been using marked and rendering the html on the client each time the page loads. It’s not ideal but it gave me the advanced parsing the other libraries didn’t. Now if I was using NodeJS I could run this on both the client and server, but since I’m using .NET that narrows down my options a bit.
Wanting to use marked for this site as well, and thinking about how in Node this would be run through V8, lead me to find ClearScript. This is a .NET wrapper for V8 which lets you run JavaScript on the server similar to how it would in Node. This means with a bit of code I could use marked to replace MarkdownSharp and PageDown.
The end result is used like so
public class ResumeModule : NancyModule{ public ResumeModule(IDatabase db, IMarkdown markdown) : base("/resume") { Get["/"] = _ => View["Index", db.ContentPages.Resume()];
Get["/edit"] = _ => View["Edit", new EditResumeModel(db.ContentPages.Resume())];
Post["/edit"] = _ => { var model = this.BindAndValidate<EditResumeModel>();
if (!ModelValidationResult.IsValid) { return View["Edit", model]; }
using (var compiler = markdown.CreateCompiler()) { var contentPage = db.ContentPages.Resume();
contentPage.Content = model.Content; contentPage.ContentHtml = compiler.Compile(model.Content);
db.Update(contentPage); }
Request.AddAlertMessage(MessageType.Success, "Resume successfully updated.");
return Response.AsRedirect("~/"); }; }}
And this is how it’s setup in my bootstrapper
public class Bootstrapper : DefaultNancyBootstrapper{ protected override void ConfigureApplicationContainer(TinyIoCContainer container) { base.ConfigureApplicationContainer(container);
container.Register<IJsEngine>((c, p) => new V8JsEngine()); container.Register<IMarkdown>((c, p) => new Markdown(c.Resolve<IJsEngine>)); }}
To handle the rendering we’ll need two things, the first is a class to pass our markdown to the JavaScript engine.
public interface IMarkdown{ IMarkdownCompiler CreateCompiler();}
public interface IMarkdownCompiler : IDisposable{ string Compile(string markdown);}
public class Markdown : IMarkdown{ private readonly Func<IJsEngine> _jsEngine;
public Markdown(Func<IJsEngine> createJsEngineInstance) { _jsEngine = createJsEngineInstance; }
public IMarkdownCompiler CreateCompiler() { return new MarkdownCompiler(_jsEngine()); }}
public class MarkdownCompiler : IMarkdownCompiler{ private readonly object _compilationSynchronizer = new object();
private IJsEngine _jsEngine; private bool _initialized; private bool _disposed;
internal MarkdownCompiler(IJsEngine jsEngine) { _jsEngine = jsEngine; }
private void Initialize() { if (!_initialized) { var type = GetType();
_jsEngine.ExecuteResource("Foliata.Resources.marked.js", type); _jsEngine.ExecuteResource("Foliata.Resources.markedHelper.js", type);
_initialized = true; } }
public string Compile(string markdown) { string result;
lock (_compilationSynchronizer) { Initialize();
_jsEngine.SetVariableValue("_markdownString", markdown);
result = _jsEngine.Evaluate<string>("markedHelper.compile(_markdownString)"); }
return result; }
public void Dispose() { if (!_disposed) { _disposed = true;
if (_jsEngine != null) { _jsEngine.Dispose(); _jsEngine = null; } } }}
Then we’ll need a JavaScript wrapper to call marked and any custom renderers we have.
Since I’m using Bootstrap I’ve added a custom renderer that modifies all tables to include the class table
.
var markedHelper = (function (marked) { "use strict";
var exports = {}, defaultOptions = { gfm: true, tables: true, breaks: false, pedantic: false, sanitize: false, smartLists: true, silent: false, highlight: null, langPrefix: "lang-", smartypants: false, headerPrefix: "", renderer: new marked.Renderer(), xhtml: false, };
// add the class *table* so they'll be styled correctly defaultOptions.renderer.table = function (header, body) { return ( '<table class="table">\n' + "<thead>\n" + header + "</thead>\n" + "<tbody>\n" + body + "</tbody>\n" + "</table>\n" ); };
function extend(destination, source) { var propertyName;
destination = destination || {};
for (propertyName in source) { if (source.hasOwnProperty(propertyName)) { destination[propertyName] = source[propertyName]; } }
return destination; }
exports.compile = function (markdown, options) { var compilationOptions;
options = options || {}; compilationOptions = extend(extend({}, defaultOptions), options);
return marked(markdown, compilationOptions); };
return exports;})(marked);
The JavaScript wrapper is in a file called markedHelper.js
and is in a folder called Resources
along with marked.js
.
Both files have their Build Action set to Embedded Resource.
You’ll also need to adjust the resource namespaces in the MarkdownCompiler
based on your project setup.
Instead of using ClearScript directly I chose to use JavaScript Engine Switcher. This project gives you a common interface and wrappers for Jurassic, MSIE, and V8. You can use which ever you like but I chose V8 since this will most closely resemble if I was running in Node.
So there you have it, rendering markdown on the client and server using marked. The next step is making this load the same files that are being served to the site, but for now this is a huge step forward from the previous setup.