Need help with Westwind.AspNetCore.Markdown?
Click the “chat” button below for chat support from the developer who created it, or find similar developers for support.

About the developer

RickStrahl
144 Stars 18 Forks MIT License 65 Commits 2 Opened issues

Description

An ASP.NET Core Markdown support library that provides Markdown parsing, a Markdown TagHelper and Markdown Page Handler Middleware

Services available

!
?

Need anything else?

Contributors list

# 25,674
C#
.NET
razor
Angular
54 commits
# 514,669
Shell
TypeScr...
convers...
markdow...
2 commits
# 400,315
aspnet-...
c-sharp
Univers...
google-...
1 commit
# 1,830
c-sharp
unity3d
support...
ffmpeg
1 commit
# 76,822
TypeScr...
entity-...
net-sta...
prometh...
1 commit

ASP.NET Core Markdown Support

NuGet

This small package provides Markdown support for your ASP.NET Core applications. It has the following features:

  • Markdown Parsing
    • Parse Markdown to HTML Strings
      Markdown.Parse(markdown)

      @Markdown.ParseHtmlString(markdown)
    • Parse Markdown from Files
      Markdown.ParseFromFile("~/MarkdownPartial.md")

      Markdown.ParseFromFileAsync("~/MarkdownPartial.md")

      Markdown.ParseHtmlStringFromFile("~/MarkdownPartial.md")
    • Parse Markdown from Urls
      Markdown.ParseFromUrl("https://github.com/RickStrahl/Westwind.AspNetCore.Markdown/raw/master/readme.md")

      Markdown.ParseFromUrlAsync("https://github.com/RickStrahl/Westwind.AspNetCore.Markdown/raw/master/readme.md")
    • Configurable Markdown Parser
      Plug in your own or customize the Markdown Parser via
      IMarkdownParserFactory
      and
      IMarkdownParser
  • Markdown TagHelper
    • Embed Markdown text into Views and Pages
    • Databind Model data as Markdown text via markdown attribute
    • Render Markdown from files via filename attribute
    • Supports white space normalization
  • Markdown Page Processor Middleware
    • Serve
      .md
      files as Markdown
    • Serve mapped extensionless URLs as Markdown
    • Configure a Razor template to customize Markdown Page Container UI
  • Configuration and Support Features
    • Uses the awesome MarkDig Markdown Parser by default
    • Customizable Markdown Parsing Pipeline for Markdig
    • Pluggable Markdown Parser Support
    • Basic HTML Sanitation support built in

Related links:

Installing the NuGet Package

You can install the package from NuGet in Visual Studio or from the command line:

PM> install-package westwind.aspnetcore.markdown

or the

dotnet
command line:
dotnet add package westwind.aspnetcore.markdown

Startup Configuration

To use these components you need to add the following to your Startup class at minimum. The following is for ASP.NET Core 3.0 and later using endpoint routing:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMarkdown();

// We need to use MVC so we can use a Razor Configuration Template
services.AddMvc()
    // have to let MVC know we have a controller
    .AddApplicationPart(typeof(MarkdownPageProcessorMiddleware).Assembly);

}

public void Configure(IApplicationBuilder app) { // if you use default files make sure you do it before markdown middleware app.UseDefaultFiles(new DefaultFilesOptions() { DefaultFileNames = new List { "index.md", "index.html" } });

app.UseMarkdown();
app.UseStaticFiles();

// the following enables MVC and Razor Pages
app.UseRouting();

app.UseEndpoints(endpoints =>
{
    // endpoints.MapRazorPages();  // optional

    // MVC routing is required
    endpoints.MapDefaultControllerRoute();
});

}

There are additional configuration options for the

AddMarkdown()
method available which are discussed in more detail later.

Configuration is optional Static
Markdown
methods or the TagHelper

Note the above configuration is required only if you use the Markdown Middleware that processes loose Markdown pages. If you use the static Markdown functions or the TagHelper and you don't need custom Markdown configuration of the Markdown Parser, configuration is not required.

Markdown Parsing Helpers

At it's simplest this component provides Markdown parsing that you can use to convert Markdown text to HTML either inside of application code, or inside of Razor expressions.

Markdown to String

string html = Markdown.Parse(markdownText);

Markdown to Razor Html String

@Markdown.ParseHtmlString(Model.ProductInfoMarkdown)

Parse Markdown to String from a File

You can also convert Markdown using a file:

var html = Markdown.ParseFromFile("~/EmbeddedMarkdownContent.md");

// async html = await Markdown.ParseFromFileAsync("~/EmbeddedMarkdownContent.md");

To embed in Razor Views:

@Markdown.ParseHtmlStringFromFile("~/EmbeddedMarkdownContent.md")

Parse Markdown to String from a Url

You can also load Markdown from a URL as long as it's openly accessible via a URL:

// sync
parsedHtml = Markdown.ParseFromUrl("https://github.com/RickStrahl/Westwind.AspNetCore.Markdown/raw/master/readme.md")

// async parsedHtml = await Markdown.ParseFromUrlAsync("https://github.com/RickStrahl/Westwind.AspNetCore.Markdown/raw/master/readme.md");

HtmlString versions are also available of this method.

SanitizeHtml in Parser Methods to mitigate XSS

Both of the above methods include a few optional parameters including a

sanitizeHtml
parameter which defaults to
false
. If set to
true
any
 tags that are not embedded inside of inline or fenced code blocks are stripped. Additionally any reference to 
javascript:
inside of a tag is replaced with
unsupported:
rendering the script non-functional.
string html = Markdown.Parse(markdownText, sanitizeHtml: true)

MarkDig Pipeline Configuration

This component uses the MarkDig Markdown Parser which allows for explicit feature configuration via many of its built-in extensions. The default configuration enables the most commonly used Markdown features and defaults to Github Flavored Markdown for most settings.

If you need to customize what features are supported you can override the pipeline creation explicitly in the

Startup.ConfigureServices()
method:
services.AddMarkdown(config =>
{
    // Create custom MarkdigPipeline 
    // using MarkDig; for extension methods
    config.ConfigureMarkdigPipeline = builder =>
    {
        builder.UseEmphasisExtras(Markdig.Extensions.EmphasisExtras.EmphasisExtraOptions.Default)
            .UsePipeTables()
            .UseGridTables()                        
            .UseAutoIdentifiers(AutoIdentifierOptions.GitHub) // Headers get id="name" 
            .UseAutoLinks() // URLs are parsed into anchors
            .UseAbbreviations()
            .UseYamlFrontMatter()
            .UseEmojiAndSmiley(true)                        
            .UseListExtras()
            .UseFigures()
            .UseTaskLists()
            .UseCustomContainers()
            .UseGenericAttributes();

        //.DisableHtml();   // don't render HTML - encode as text
};

});

When set this configuration is used every time the Markdown parser instance is created instead of the default behavior.

Markdown TagHelper

The Markdown TagHelper allows you to embed static Markdown content into a

 tag. The TagHelper supports both embedded content, or an attribute based value assignment or model binding via the 
markdown
attribute.

To get started with the Markdown TagHelper you need to do the following:

  • Register TagHelper in
    _ViewImports.cshtml
  • Place a
     TagHelper on the page
  • Use
    Markdown.ParseHtmlString()
    in Razor Page expressions
  • Rock on!

After installing the NuGet package you have to register the tag helper so MVC can find it. The easiest way to do this is to add it to the

_ViewImports.cshtml
file in your
Views\Shared
folder for MVC or the root for your
Pages
folder.
@addTagHelper *, Westwind.AspNetCore.Markdown

Literal Markdown Content

To use the literal content control you can simply place your Markdown text between the opening and closing

 tags:
    #### This is Markdown text inside of a Markdown block

* Item 1
* Item 2

### Dynamic Data is supported:
The current Time is: @DateTime.Now.ToString("HH:mm:ss")

```cs
// this c# is a code block
for (int i = 0; i < lines.Length; i++)
{
    line1 = lines[i];
    if (!string.IsNullOrEmpty(line1))
        break;
}
```

The TagHelper turns the Markdown text into HTML, in place of the TagHelper content.

Razor Expression Evaluation

Note that Razor expressions in the markdown content are supported - as the

@DateTime.Now.ToString()
expression in the example - and are expanded before the content is parsed by the TagHelper. This means you can embed dynamic values into the markdown content which gives you most of the flexibilty of Razor code now in Markdown. Embedded expressions are not automatically HTML encoded as they are embedded into the Markdown. Additionally you can also generate additional Markdown as part of a Razor expression to make the Markdown even more dynamic.

TagHelper Attributes

filename

You can specify a filename to pull Markdown from and render into HTML. Files can be referenced:

  • Physical paths:
    /temp/somefile.md
  • Relative paths:
    somefolder/somefile.md
    - relative to current page
  • Virtual paths:
    ~/somefolder/somefile.md
    - relative to site root

Relative File Links load resources as Host Page Relative

Any relative links and resources - images, relative links - that are referenced are relative to the host page not relative to the Markdown document. Make sure you take into account paths for any related resources and either ensure they are relative to the host page or use absolute URLs.

url

You can also load Markdown documents from URL and process them as markdown to HTML for embedding into the page. The Url has to be openly accessible (ie. no authentication). This is great for loading and embedding content from remote sources such as from Github and rendering it as part of your application. It's very useful for CMS and Documentation solutions where a remote repository serves as the document store.

url-fixup-basepath

When

true
(default) any relative Markdown images and links are converted to absolute URLs relative to the Markdown document that is being loaded via the
url=
attribute. Typically you'll want images and links to be fixed up so images show and you don't end up with dead links. However in some situations you might want to render the page with the original links in which case you can explicitly force the attribute to
false
.

markdown (Model Binding)

In addition to the content you can also bind to the

markdown
attribute which allows for programmatic assignment and data binding.
@model MarkdownPageModel
@{
    Model.MarkdownText = "This is some **Markdown**!";
}

The

markdown
attribute accepts binding expressions so you can bind Markdown for display from model values or other expressions easily.

normalize-whitespace

Markdown is sensitive to leading spaces and given that you're likely to enter Markdown into the literal TagHelper in a code editor there's likely to be a leading block of white space. Markdown treats leading white space as significant - 4 spaces or a tab indicate a code block so if you have:

    #### This is Markdown text inside of a Markdown block

* Item 1
* Item 2

### Dynamic Data is supported:
The current Time is: @DateTime.Now.ToString("HH:mm:ss")

without special handling Markdown would interpret the entire markdown text as a single code block.

By default the TagHelper sets

normalize-whitespace="true"
which automatically strips common white space to all lines from the code block. Note that this is based on the formatting of the first non-blank line of code and works only if all code lines start with the same formatted white space.

Optionally you can also force justify your code and turn the setting to

false
:
#### This is Markdown text inside of a Markdown block

  • Item 1
  • Item 2

Dynamic Data is supported:

The current Time is: @DateTime.Now.ToString("HH:mm:ss")

This also works, but is hard to maintain in some code editors due to auto-code reformatting.

sanitize-html

By default the Markdown tag helper strips

 tags and 
href="javascript"
directives from the generated HTML content. The default for this property is
true
If you would like to explicitly include script tags because your content requires it you can enable that functionality by setting
sanitize-html=false
.

The default behavior strips the script content shown below (

sanitize-html
tag only shown by reference -
true
is the default so not required here):
    ### Rudimentary XSS Support
    Below are a script tag, and some raw HTML alert with an onclick handler which 
    are potential XSS vulnerable. Default is `strip-script-tags="true"` to remove
    script and other vulnerabilities.

<a href="javascript:%20alert('Gotcha!%20javascript:%20executed.');">Malicious Link</a>

<script>
    alert("GOTHCHA! Injected code executed.")
</script>

<div class="alert alert-info" onmouseover="alert('Gotcha! onclick handler fired');">
    XSS Alert: You shouldn't be able to click me and execute the onclick handler.
</div>

With the flag set to

false
the script does execute and the link will trigger the second alert. The default of
true
removes the entire script block and replaces
javascript:
in the href with
unsupported:
which effectively doesn't do anything.

no-http-exception

Optional parameter that can be set if you are using a URL bound to the tag helper. When

true
causes the TagHelper to render empty instead of throwing an HTTP load exception and break the page.

Markdown Page Processor Middleware

The Markdown middleware allows you drop

.md
files into a configured folder and have that folder parsed directly from disk. The middleware merges the Markdown into a pre-configured Razor template you provide so your Markdown text can be rendered in the proper UI context of your site chrome.

To use this feature you need to do the following:

  • Use
    AddMarkdown()
    to configure the page processing
  • Use
    UseMarkdown()
    to hook up the middleware
  • Create a Markdown View Template (default is:
    ~/Views/__MarkdownPageTemplate.cshtml
    )
  • Create
    .md
    files for your content
  • Rock on!

Note that the default template location can be customized for each folder and it can live anywhere including the

Pages
folder.

Startup Configuration

As with any middleware components you need to configure the Markdown middleware and hook it up for processing which is a two step process.

First you need to call

AddMarkdown()
to configure the Markdown processor. You need to specify the folders that the processor is supposed to work on.

At it's simplest you can just do:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMarkdown(config =>
    {
        // just add a folder as is
        config.AddMarkdownProcessingFolder("/docs/");

    services.AddMvc();
}

}

This configures the Markdown processor for default behavior which handles

.md
files and extensionless Urls in the
/docs/
folder as Markdown files (if they exist) and it assumes a default Razor Markdown host template at
~/Views/__MarkdownPageTemplate.cshtml
.

All of these values can be customized with additional configuration options in

ConfigureServices()
:
services.AddMarkdown(config =>
{
    // optional Tag BlackList
    config.HtmlTagBlackList = "script|iframe|object|embed|form"; // default

// Simplest: Use all default settings
var folderConfig = config.AddMarkdownProcessingFolder("/docs/", "~/Pages/__MarkdownPageTemplate.cshtml");

// Customized Configuration: Set FolderConfiguration options
folderConfig = config.AddMarkdownProcessingFolder("/posts/", "~/Pages/__MarkdownPageTemplate.cshtml");

// Optionally strip script/iframe/form/object/embed tags ++
folderConfig.SanitizeHtml = false;  //  default

// folderConfig.BasePath = "http://othersite.com";

// Optional configuration settings
folderConfig.ProcessExtensionlessUrls = true;  // default
folderConfig.ProcessMdFiles = true; // default

// Optional pre-processing - with filled model
folderConfig.PreProcess = (model, controller) =&gt;
{                    
    // controller.ViewBag.Model = new MyCustomModel();
};

// optional custom MarkdigPipeline (using MarkDig; for extension methods)
config.ConfigureMarkdigPipeline = builder =&gt;
{
    builder.UseEmphasisExtras(Markdig.Extensions.EmphasisExtras.EmphasisExtraOptions.Default)
        .UsePipeTables()
        .UseGridTables()                        
        .UseAutoIdentifiers(AutoIdentifierOptions.GitHub) // Headers get id="name" 
        .UseAutoLinks() // URLs are parsed into anchors
        .UseAbbreviations()
        .UseYamlFrontMatter()
        .UseEmojiAndSmiley(true)                        
        .UseListExtras()
        .UseFigures()
        .UseTaskLists()
        .UseCustomContainers()
        //.DisableHtml()   // renders HTML tags as text including script
        .UseGenericAttributes();
};

});

There are additional options including the ability to hook in a pre-processor that's fired on every controller hit. In the example I set a custom model to the ViewBag that the template can potentially pick up and work with. For applications you might have a stock View model that provides access rights and other user logic that needs to fire to access the page and display the view. Using the

PreProcess
Action hook you can run just about any pre-processing logic and get values into the View if necessary via the
Controller.Viewbag
.

In addition to the configuration you also need to hook up the Middleware in the

Configure()
method:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{  
    ...
    app.UseMarkdown();

app.UseStaticFiles();
app.UseMvc();

}

The

UseMarkdown()
method hooks up the middleware into the pipeline.

Note that both

ConfigureServices()
and
Configure()
are required to reference the MVC middleware -
services.AddMvc()
and
app.UseMvc()
respectively - as the middleware relies on MVC and Razor to render the Razor host view template.

Create a Markdown Page View Template

Markdown is just an HTML fragment, not a complete document, so a host template is required into which the rendered Markdown is embedded. To accomplish this the middleware uses a well-known MVC controller endpoint that loads the configured Razor view template that embeds the Markdown text.

The middleware reads in the Markdown file from disk, and then uses a generic MVC controller method call the specified template to render the page containing your Markdown text as the content. The template is passed a

MarkdownModel
that includes
RenderedMarkdown
and
Title
properties.

Typically the template page is pretty simple and only contains the rendered Markdown plus a reference to a

_Layout
page, but what goes into this template is really up to you.

The actual rendered Markdown content renders into HTML is pushed into the page via

ViewBag.RenderedMarkdown
, which is an
HtmlString
instance that contains the rendered HTML.

A minimal template page can do something like this:

@model Westwind.AspNetCore.Markdown.MarkdownModel
@{
    ViewBag.Title = Model.Title;
    Layout = "_Layout";
}
@Model.RenderedMarkdown

This template can be a self contained file, or as I am doing here, it can reference a

_layout
page so that the layout matches the rest of your site.

A more complete template might also add a code highlighter (highlightJs here) and custom styling something more along the lines of this:

@model Westwind.AspNetCore.Markdown.MarkdownModel
@{
    Layout = "_Layout";
}
@section Headers {
    @if (!string.IsNullOrEmpty(Model.BasePath))
    {
        
    }
    

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.