Appearance
Are you an LLM? You can read better optimized documentation at /api_reference/tag_helpers.md for this page in Markdown format
Tag helpers
Overview
This document provides a comprehensive technical reference for the custom MarkdownTagHelper used within the application. It details the Tag Helper's API, usage patterns, internal logic, and important considerations for developers regarding performance and security. The primary purpose of this component is to render Markdown content, typically fetched from the Contentful CMS, into HTML within Razor views.
For more context on how Markdown is used throughout the application, see the Markdown Rendering feature documentation.
MarkdownTagHelper: Complete API reference
The MarkdownTagHelper is a server-side component that transforms Markdown text into HTML during the Razor view rendering process. It is defined in the TheExampleApp.TagHelpers namespace and inherits from the base Microsoft.AspNetCore.Razor.TagHelpers.TagHelper class.
The component must be made available to all Razor views by registering its assembly in _ViewImports.cshtml.
File: TheExampleApp/Pages/_ViewImports.cshtml
csharp
@addTagHelper *, TheExampleApp1
Class Definition
File: TheExampleApp/TagHelpers/MarkdownTagHelper.cs
csharp
namespace TheExampleApp.TagHelpers
{
/// <summary>
/// Taghelper for turning markdown into html.
/// </summary>
[HtmlTargetElement("markdown")]
[HtmlTargetElement(Attributes = "markdown")]
public class MarkdownTagHelper : TagHelper
{
// ... properties and methods
}
}1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
Properties
| Property | Type | Description |
|---|---|---|
Content | ModelExpression | Binds to a model property containing the Markdown string to be rendered. This is the primary mechanism for rendering dynamic content. |
HtmlTargetElement attributes: Supported usage patterns
The MarkdownTagHelper is configured to activate in two distinct scenarios, defined by HtmlTargetElement attributes on the class. This provides flexibility in how the helper is used within Razor views.
As a custom element:
[HtmlTargetElement("markdown")]This allows the helper to be invoked using a custom<markdown>tag. This is the preferred and most common usage pattern, especially when binding to a model.html<markdown content="@Model.Copy"></markdown>1As an attribute on any element:
[HtmlTargetElement(Attributes = "markdown")]This allows the helper to be triggered by adding amarkdownattribute to any standard HTML element. In this mode, the helper processes the inner content of the element it's attached to.html<div markdown> This **inner content** will be processed. </div>1
2
3
Content property: Model expression binding
The Content property is the key to rendering dynamic data from your view model.
File: TheExampleApp/TagHelpers/MarkdownTagHelper.cs
csharp
/// <summary>
/// The model expression for which property to convert.
/// </summary>
public ModelExpression Content { get; set; }1
2
3
4
2
3
4
By using the ModelExpression type, ASP.NET Core enables strong-typed data binding directly from the Razor view. When you write content="@Model.Copy", the framework populates the Content property with metadata about the Copy property on your model, including its value.
The helper's internal logic prioritizes this property. If Content is not null, its model value is used as the source for Markdown conversion.
File: TheExampleApp/TagHelpers/MarkdownTagHelper.cs
csharp
private async Task<string> GetContent(TagHelperOutput output)
{
if (Content == null)
return (await output.GetChildContentAsync()).GetContent();
return Content.Model?.ToString();
}1
2
3
4
5
6
7
2
3
4
5
6
7
This design pattern cleanly separates the presentation logic in the view from the data model. For details on the models used, such as LessonCopy, refer to the API Models Reference.
ProcessAsync method: Internal processing logic
The ProcessAsync method contains the core logic for the tag helper. It executes during server-side rendering and orchestrates the transformation from a custom tag into rendered HTML.
File: TheExampleApp/TagHelpers/MarkdownTagHelper.cs
csharp
public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
// 1. If used as <markdown>, suppress the tag itself from the output.
if (output.TagName == "markdown")
{
output.TagName = null;
}
// 2. Remove the 'markdown' attribute to clean up the final HTML.
output.Attributes.RemoveAll("markdown");
// 3. Get the raw markdown string from the model or inner content.
var content = await GetContent(output);
var markdown = content;
// 4. Use Markdig to convert the markdown string to an HTML string.
var html = Markdown.ToHtml(markdown ?? "");
// 5. Set the output's content to the raw, unencoded HTML.
output.Content.SetHtmlContent(html ?? "");
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Execution Flow:
- Tag Suppression: If the helper is used as
<markdown>,output.TagNameis set tonull. This ensures the<markdown>tag itself is not rendered, leaving only its processed content. If used as an attribute (e.g.,<div markdown>), the original tag (div) is preserved. - Attribute Removal: The
markdownattribute is removed from the output element's attribute list to prevent it from appearing in the final HTML. - Content Retrieval: The private
GetContentmethod is called to asynchronously retrieve the Markdown string, either from theContentmodel expression or the tag's inner content. - Markdown Conversion: The static
Markdown.ToHtmlmethod from theMarkdiglibrary (v0.15.4) is invoked. The null-coalescing operator (?? "") provides a safeguard against null reference exceptions if the content is null. - HTML Output:
output.Content.SetHtmlContent()is called to write the resulting HTML string to the response. Crucially,SetHtmlContentdoes not HTML-encode its input, which is necessary to render tags like<h1>,<p>, and<ul>correctly.
Usage syntax: Element form vs. attribute form
The tag helper supports two syntaxes, each suited for different scenarios.
Element Form (<markdown>)
This is the most powerful and recommended form. It can render content from a model property or its own inner content.
Binding to a model property:
html
<markdown content="@Model.SomeMarkdownProperty"></markdown>1
Processing inline content:
html
<markdown>
# My Title
This is a paragraph with **bold** text.
</markdown>1
2
3
4
5
2
3
4
5
Attribute Form (markdown)
This form is used to process the inner content of a standard HTML element. It cannot be used to bind to a model property via the content attribute. The host element (e.g., div) is preserved in the final output.
Processing inner content of a div:
html
<div markdown class="my-styles">
The content inside this div will be parsed by Markdig,
and the div will be rendered with its `my-styles` class.
</div>1
2
3
4
2
3
4
Examples: Various usage scenarios
1. Rendering a Model Property from Contentful
This is the primary use case in the application. A Razor view receives a model populated with data from Contentful, and the MarkdownTagHelper renders a specific field.
Model: TheExampleApp/Models/Lesson - Copy.cs
csharp
public class LessonCopy : ILessonModule
{
// ... other properties
public string Copy { get; set; }
}1
2
3
4
5
2
3
4
5
View: TheExampleApp/Views/Shared/LessonCopy.cshtml
html
@model LessonCopy
<div class="lesson-module lesson-module-copy">
<div class="lesson-module-copy__copy">
<markdown content="@Model.Copy"></markdown>
</div>
</div>1
2
3
4
5
6
7
2
3
4
5
6
7
2. Rendering Static, Inline Markdown
This is useful for small, static blocks of formatted text within a view that don't need to be managed in the CMS.
html
<div class="sidebar-info">
<markdown>
### Need Help?
Contact support at [support@example.com](mailto:support@example.com)
or check out our:
- FAQ
- User Guides
</markdown>
</div>1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
3. Applying Markdown to an Existing Element
This pattern is less common but can be useful for progressively enhancing static content.
Input Razor:
html
<blockquote markdown class="pull-quote">
"Simplicity is the ultimate sophistication." - Leonardo da Vinci
</blockquote>1
2
3
2
3
Approximate HTML Output:
html
<blockquote class="pull-quote">
<p>"Simplicity is the ultimate sophistication." - Leonardo da Vinci</p>
</blockquote>1
2
3
2
3
Note: Markdig wraps standalone text in <p> tags by default.
Performance characteristics: Async processing details
The MarkdownTagHelper is implemented asynchronously to ensure it integrates efficiently into the ASP.NET Core rendering pipeline.
- Asynchronous Signature: The method signature
public async override Task ProcessAsync(...)makes the helper non-blocking. - I/O Unlocking: The
awaitkeyword is used when retrieving child content:await output.GetChildContentAsync(). This is the primary asynchronous operation. If the inner content of the tag helper involves other async operations (like rendering partial views), thisawaitallows the current thread to be released back to the thread pool to handle other incoming requests, improving server scalability. - CPU-Bound Conversion: The
Markdown.ToHtml()call itself is a synchronous, CPU-bound operation. For the vast majority of content, this conversion is extremely fast and its synchronous nature has a negligible impact on performance. The overall asynchronous pattern is primarily beneficial for I/O-bound work within the Razor engine.
Security notes: HTML sanitization considerations
This is a critical consideration for all developers using this component.
The MarkdownTagHelper uses output.Content.SetHtmlContent(), which explicitly bypasses ASP.NET Core's default HTML encoding. This is necessary to render the HTML generated by Markdig (e.g., <h1>, <strong>). However, it creates a potential Cross-Site Scripting (XSS) vulnerability if the source Markdown contains malicious scripts.
The Markdig library, by default, allows raw HTML blocks and potentially dangerous attributes within the Markdown source. For example, content like this would be rendered directly to the page:
markdown
This is safe. <script>alert('XSS attack!');</script> This is not.1
The current implementation of MarkdownTagHelper does NOT perform any HTML sanitization.
It assumes that all content processed is from a trusted source (i.e., authored by internal staff in the Contentful CMS).
Mitigation Strategy
If there is any possibility that untrusted content could be passed to this tag helper, you must implement a sanitization step.
Use a Sanitization Library: Before passing the content to
Markdown.ToHtml, process it with a dedicated HTML sanitization library likeHtmlSanitizer.csharp// Example using a hypothetical sanitizer using Ganss.XSS; // Example library var sanitizer = new HtmlSanitizer(); var markdown = await GetContent(output); var sanitizedMarkdown = sanitizer.Sanitize(markdown); // Sanitize before processing var html = Markdown.ToHtml(sanitizedMarkdown ?? ""); output.Content.SetHtmlContent(html ?? "");1
2
3
4
5
6
7
8Configure Markdig Pipeline:
Markdigoffers advanced configuration options through itsMarkdownPipelineBuilder. It may be possible to configure a pipeline that disables or sanitizes raw HTML. This would be a more centralized and robust solution. This configuration should be explored in the context of the broader Markdown Rendering feature.
Developers must ensure that any content from external or user-submitted sources is properly sanitized before being rendered by this tag helper.