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 reference
This document provides a technical reference for the custom Tag Helpers used within the TheExampleApp application. It is intended for developers responsible for maintaining and extending the application's view layer. A foundational understanding of ASP.NET Core Tag Helpers is assumed. For a general overview, please refer to the Core Features: Tag Helpers documentation.
Tag helpers API
Tag Helpers are server-side C# components that participate in the rendering of Razor views. They allow for the creation of reusable, robust, and testable UI components by enabling server-side code to target and augment HTML elements.
In TheExampleApp, Tag Helpers are registered globally for all Razor views in the _ViewImports.cshtml file. This approach simplifies view development by making all registered helpers available without explicit @addTagHelper directives in each file.
cshtml
// File: TheExampleApp/Pages/_ViewImports.cshtml
@namespace TheExampleApp.Pages
@using Microsoft.AspNetCore.Http;
@using Microsoft.AspNetCore.Mvc.Localization
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Contentful.AspNetCore
@addTagHelper *, TheExampleApp
@inject IViewLocalizer Localizer
@inject TheExampleApp.Configuration.IContentfulOptionsManager Manager1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
The key registrations are:
Microsoft.AspNetCore.Mvc.TagHelpers: The standard set of helpers provided by ASP.NET Core (e.g.,asp-action,asp-for).Contentful.AspNetCore: Helpers provided by the Contentful SDK, used for generating links and handling Contentful-specific data.TheExampleApp: All custom Tag Helpers defined within this project's assembly, such as theMarkdownTagHelper.
MarkdownTagHelper
The MarkdownTagHelper is a custom helper designed to convert Markdown-formatted strings into HTML at render time. This is a critical component for displaying rich text content sourced from the Contentful headless CMS, as content editors often write in Markdown. The helper leverages the Markdig library for high-performance Markdown processing.
Implementation Details
The helper is defined in TheExampleApp/TagHelpers/MarkdownTagHelper.cs. It is designed to be flexible, supporting activation as both a custom element and an attribute on standard HTML elements.
csharp
// File: TheExampleApp/TagHelpers/MarkdownTagHelper.cs
using Markdig;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
// ...
namespace TheExampleApp.TagHelpers
{
/// <summary>
/// Taghelper for turning markdown into html.
/// </summary>
[HtmlTargetElement("markdown")]
[HtmlTargetElement(Attributes = "markdown")]
public class MarkdownTagHelper : TagHelper
{
/// <summary>
/// The model expression for which property to convert.
/// </summary>
public ModelExpression Content { get; set; }
public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
// If used as <markdown>, remove the tag itself from the output.
if (output.TagName == "markdown")
{
output.TagName = null;
}
// If used as an attribute, remove the 'markdown' attribute from the output tag.
output.Attributes.RemoveAll("markdown");
var content = await GetContent(output);
var markdown = content;
var html = Markdown.ToHtml(markdown ?? "");
output.Content.SetHtmlContent(html ?? "");
}
private async Task<string> GetContent(TagHelperOutput output)
{
// Prefer the 'content' attribute if it's bound.
if (Content == null)
// Fall back to the tag's inner content.
return (await output.GetChildContentAsync()).GetContent();
return Content.Model?.ToString();
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
Key Behaviors:
- Targeting: The
[HtmlTargetElement]attributes configure the helper to run for:- Any element named
<markdown>. - Any element with a
markdownattribute (e.g.,<div markdown>).
- Any element named
- Content Source: The helper determines the Markdown source with the following priority:
contentattribute: If aModelExpressionis bound to theContentproperty (via acontent="..."attribute), its value is used. This is the primary method for rendering model data.- Inner Content: If the
contentattribute is not present, the helper processes the content nested between the element's start and end tags.
- Output Rendering:
- When used as
<markdown>, the tag itself is suppressed (output.TagName = null), and only the resulting HTML is rendered. - When used as an attribute (e.g.,
<div markdown>), the host tag (div) is preserved, but themarkdownattribute is removed to produce clean HTML. - The final rendered HTML is injected into the output using
output.Content.SetHtmlContent().
- When used as
Usage Examples
1. As a Custom Element with Model Binding
This is the most common use case in the application, used for rendering rich text fields from a Contentful model. See the Content Display feature documentation for more context.
cshtml
// File: TheExampleApp/Views/Shared/LessonCopy.cshtml
@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
8
2
3
4
5
6
7
8
2. As a Custom Element with Inner Content
This is useful for static Markdown content written directly in a Razor view.
cshtml
<markdown>
### A Sub-Heading
* List item 1
* List item 2
This is a paragraph with `inline code`.
</markdown>
<!-- Renders as: -->
<h3>A Sub-Heading</h3>
<ul>
<li>List item 1</li>
<li>List item 2</li>
</ul>
<p>This is a paragraph with <code>inline code</code>.</p>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
3. As an Attribute on a Standard Element
This pattern is useful when you need to wrap the rendered Markdown in a specific container element with its own classes or attributes.
cshtml
<div class="alert alert-info" markdown>
# Important Note
Please review the guidelines before proceeding.
</div>
<!-- Renders as: -->
<div class="alert alert-info">
<h1>Important Note</h1>
<p>Please review the guidelines before proceeding.</p>
</div>1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
This behavior is verified by unit tests, as seen in TheExampleApp.Tests/TagHelpers/MarkdownTagHelperTests.cs.
Creating custom tag helpers
Developers can extend the application with new Tag Helpers by following the pattern established by MarkdownTagHelper.
1. Inherit from TagHelper
All Tag Helpers must inherit from Microsoft.AspNetCore.Razor.TagHelpers.TagHelper.
csharp
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace TheExampleApp.TagHelpers
{
public class MyCustomTagHelper : TagHelper
{
// ...
}
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
2. Define Target Elements and Attributes
Use the [HtmlTargetElement] attribute to specify when the helper should be activated. You can define public properties on the class to accept attributes from the Razor markup. Property names are automatically converted from PascalCase to kebab-case in HTML. For example, a C# property MyAttribute becomes a my-attribute attribute in Razor.
csharp
[HtmlTargetElement("my-custom-tag", Attributes = "my-attribute")]
public class MyCustomTagHelper : TagHelper
{
// This property will be populated from the 'my-attribute' attribute.
public string MyAttribute { get; set; }
// For strongly-typed model binding, use ModelExpression.
public ModelExpression For { get; set; }
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
3. Implement Processing Logic
Override the ProcessAsync method to implement the helper's logic. The TagHelperContext provides information about the source element, while the TagHelperOutput is used to modify the final rendered HTML.
csharp
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
// Example: Wrap the original content in a <strong> tag.
var childContent = await output.GetChildContentAsync();
var content = childContent.GetContent();
output.Content.SetHtmlContent($"<strong>{content}</strong>");
// Example: Add a CSS class to the output tag.
output.Attributes.Add("class", "my-custom-class");
// Example: Change the tag name.
output.TagName = "div";
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
4. Registration
No action is typically needed for registration. As long as the new Tag Helper is created within TheExampleApp namespace (or any namespace within the project assembly), the existing @addTagHelper *, TheExampleApp directive in _ViewImports.cshtml will automatically discover and enable it across all views.