Parsing Markdown Using Custom TagHelper in ASP.NET MVC 6

DZone 's Guide to

Parsing Markdown Using Custom TagHelper in ASP.NET MVC 6

CommonMark.NET makes rendering Markdown in ASP.NET easy. Read on for the full tutorial.

· Web Dev Zone ·
Free Resource

Previous versions of MVC allowed us to write HtmlHelpers, which did a pretty good job then and are doing it now as well. But in MVC 6, the ASP.NET team has introduced TagHelpers.

Parsing Mardown in .NET is way simpler than one can imagine, thanks to Stackoverflow's MardownSharp and Karlis Gangis's CommonMark.NET. I use CommonMark.NET as it provides a much faster parsing than other libraries. The blogging platform I use is a custom blogging engine I wrote in MVC4. The post content is saved in HTML, which makes my raw HTML way too messy when compared to simple Markdown syntax. I have no plans to change the way it is right now, but for other simple applications which are quite similar, such as note-taking or blogging apps, I would like to save the content in markdown.

I will start with a simple implementation of this custom TagHelper and then we can look into other ways to enhance it. Here is how easy it is to create your custom TagHelper.

Create a new class file MarkdownTagHelper.cs. Inside the file, rename the class name to match the file name or you can change the name to something you like. In my case I am keeping the class name as same as the file name.

Pay attention to the name of the custom TagHelper. By design, the compiler will remove the word TagHelper from the class name and the rest of the name will become your custom TagHelper name.

The next step is to inherit our class from the TagHelpers class. Every custom TagHelper will inherit from this class, just like the UserControl class when creating a custom user control. The TagHelper provides us two virtual methods, Process and ProcessAsync, which we will override to implement our custom logic for our custom markdown TagHelper. The first parameter is the TagHelperContext, which holds the information about the current tag. The second parameter is TagHelperOutput, which represents the output being generated by the TagHelper. As we need to parse the Markdown in our razor view pages, we need to reference the CommonMark.Net library. Use the below Nuget command to add it to your current project.

Install-Package CommonMark.Net

This is how the code will look:

So, now we have our custom TagHelper that will let us parse the Markdown, but to use it in our views we need to opt-in for this TagHelper in the _ViewImports.cshtml file. To enable your custom TagHelper:

@addTagHelper "*, WebApplication1"

Your custom tag helper will turn purple in color on the view page. It is similar to the line above it where @addTagHelper is importing all the TagHelpers from the given namespace. If you are not interested in opting-in for all the TagHelpers in the given namespace, then use @removeTagHelper to disable the TagHelpers you don’t need. For this, I want all the tag helpers I have created to be a part of the application and hence the "*" symbol.

In your view, where you want to use this, just type in <markdown> and inside this tag you should have your markdown. To test it, you can view any raw file in Github and copy the text. I am using README.md from CommonMark.NET and it rendered perfectly.

Caution: When copy-pasting the markdown code from anywhere to your view make sure that you do not have a whitespace in the front of the line. This is only applicable when you are working with the in-line markdown. Here is the screenshot with comparison.

Hit F5 and see the markdown tag helper in action. Below is the output I get.

Image title

This is the simplest of all. Now let’s add some prefix to our custom TagHelper. To add a custom tag prefix to the TagHelper, we just need to pay a visit to _ViewImports.cshtml file again and add a new line like so:

@tagHelperPrefix "blog-"

After adding the above line in the file, go to the view page where you have used your custom TagHelper and you can notice that the <markdown> tag isn’t purple anymore. This is because we now have to add a custom tag-prefix that we just defined in the _ViewImports.cshtml file. Change it from <markdown> to <blog-markdown> and it is purple again.

By design, the TagHelper will take <markdown> as a tag to be processed. But, it can be easily ignored by using the TargetElement attribute at the top of the class, which allows the use of another name rather than <markdown>. This does not mean that you cannot use <markdown>, but instead you can also use the custom TagHelper with the name specified in this attribute.

Now, let’s add some attributes to my Markdown TagHelper. Let’s try to add a url attribute which will help users to render the markdown on the view from a remote site like Github. To add an attribute, simply add a new public property of type string and call it url. When you create a public property in the TagHelper class it is automatically assumes it is an attribute. To make use of this property, my view is now:

<blog-markdown url="https://raw.githubusercontent.com/Knagis/CommonMark.NET/master/README.md">

The url attribute value is being read by the TagHelper, which in turn reads the whole string of Markdown from Github and renders the HTML on the page. Let’s focus again on TargetElement attribute for a while. Consider a scenario where you don’t want your custom TagHelper to render or work if the attributes are not passed or are missing. This is where TargetElement attribute comes into the picture. If I don’t want my TagHelper to work if the url parameter is missing, then I can simply write:

[TargetElement("markdown", Attributes = "url")]

Notice the Attributes parameter. The Attributes parameter allows you to set the name of all the attributes which should be processed by your TagHelper or else the TagHelper will not work. For instance, if I just use the <markdown> TagHelper but do not pass the url attribute, the TagHelper will not execute and you will see the raw markdown code. My requirement is to have this TagHelper working with or without the use of the url attribute. I can comment out or remove the TargetElement attribute or just remove the Attributes parameter to get going.

Here is the complete MarkdownTagHelper.cs:

using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace WebApplication1.TagHelpers
    //[TargetElement("markdown", Attributes = "url")]
    public class MarkdownTagHelper : TagHelper
        //Attribute for our custom markdown
        public string Url { get; set; }

        private string parse_content = string.Empty;

        //Stolen from: http://stackoverflow.com/questions/7578857/how-to-check-whether-a-string-is-a-valid-http-url
        private bool isValidURL(string URL)
            Uri uriResult;
            return Uri.TryCreate(URL, UriKind.Absolute, out uriResult)
                && (uriResult.Scheme.ToLowerInvariant() == "http" || uriResult.Scheme.ToLowerInvariant() == "https");

        public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
            if (context.AllAttributes["url"] != null)
                string url = context.AllAttributes["url"].Value.ToString();
                string webContent = string.Empty;
                if (url.Trim().Length > 0)
                    if (isValidURL(url))
                        using (HttpClient client = new HttpClient())
                            webContent = await client.GetStringAsync(new Uri(url));
                            parse_content = CommonMark.CommonMarkConverter.Convert(webContent);
                //Gets the content inside the markdown element
                var content = await context.GetChildContentAsync();

                //Read the content as a string and parse it.
                parse_content = CommonMark.CommonMarkConverter.Convert(content.GetContent());

                //Render the parsed markdown inside the tags.

I found the full TagHelper feature in MVC 6 a lot more convenient and powerful. I hope you like it as well.

asp.net ,markdown ,web dev ,ASP.NET MV6

Published at DZone with permission of Prashant Khandelwal , DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}