FunnelWeb - Markdown and IoC

When I first started contributing to FunnelWeb, the only part of the project that rendered Markdown (the markup language we use to compose posts and comments) were the views, which used an extension method, like this:

<%= Html.Markdown(item.LatestRevision.Body, false) %>

So the Markdown method was an extension method on the HtmlHelper class. The first parameter was the text you wanted to convert from Markdown to HTML, and the second was true if you wanted to "sanitize" the input (used for rendering comments).

When I reworked the ATOM feeds such that they were generated directly from a controller rather than passing through the view engine, I needed to be able to render Markdown as HTML without the use of an HtmlHelper instance. I decided to create an IMarkdownProvider and use Autofac to pass it around as a dependency to anyone who needed it.

First step, then, was to create the IMarkdownProvider instance and its default implementation:

public interface IMarkdownProvider
{
    string Render(string text, bool sanitize = false);
}

public class MarkdownProvider : IMarkdownProvider
{
    public MarkdownProvider(string baseUrl)
    {
        _baseUrl = baseUrl;
    }

    string _baseUrl;

    public string Render(string text, bool sanitize = false)
    {
        return new MarkdownRenderer(sanitize, _baseUrl).Render(text);
    }
}

Pretty straight forward. I just piggy-backed onto the same MarkdownRenderer class that we'd been using all along, to keep things simple.

Notice I also make use of .NET 4's optional parameters, defaulting the sanitize parameter to false.

Next step was to register my new provider class with Autofac, which we do back in the ViewsModule class:

builder.Register(c => new MarkdownProvider(
    HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Authority))
    ).As<IMarkdownProvider>().InstancePerLifetimeScope();

I'm sure there's a nicer way to get hold of the "base URL" for the constructor parameter, but this works so it'll do for now.

Lastly, I needed to retool the views so that they, too, made use of IMarkdownProvider so I could get rid of the extension method. That turned out to be surprisingly easy, because every view ultimately derives from FunnelWeb.Web.Application.Views.ApplicationView<TModel>. I just needed to add this property:

private IMarkdownProvider _markdownProvider;
public IMarkdownProvider Markdown
{
    get { return _markdownProvider; }
}

... and this line to the IInjectable.Inject method:

_markdownProvider = container.Resolve<IMarkdownProvider>();

Now each of the views could replace this line:

<%= Html.Markdown(item.LatestRevision.Body, false) %>

... with this:

<%= Markdown.Render(item.LatestRevision.Body) %>

It's a small gain in the views, but a large gain in that it has increased the testability of controllers that need to render markdown, and nicely modularized the Markdown rendering engine making use of our inversion of control container.

funnelweb markdown autofac ioc .net
Posted by: Matt Hamilton
Last revised: 15 Oct, 2024 09:01 PM History