Appearance
Are you an LLM? You can read better optimized documentation at /components/tag_helpers.md for this page in Markdown format
Tag helpers
This document provides a technical overview of custom Tag Helpers used within the TheExampleApp project. Tag Helpers are server-side C# components that participate in the rendering of Razor views, enabling the extension of standard HTML elements or the creation of custom tags.
Tag helpers overview
In ASP.NET Core, Tag Helpers provide a powerful way to write cleaner, more expressive, and more maintainable server-side code within Razor views. They abstract complex server-side logic into simple, HTML-like tags.
The application registers three sets of Tag Helpers in the _ViewImports.cshtml file, making them globally available across all views:
cshtml
// File: TheExampleApp/Views/_ViewImports.cshtml
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Contentful.AspNetCore
@addTagHelper *, TheExampleApp1
2
3
4
5
2
3
4
5
Microsoft.AspNetCore.Mvc.TagHelpers: These are the standard, built-in helpers provided by ASP.NET Core (e.g.,asp-action,asp-for,asp-validation-for).Contentful.AspNetCore: These are helpers provided by the Contentful SDK, likely for rendering Contentful-specific content types or links.TheExampleApp: This is the most important declaration for custom development. The wildcard*instructs the Razor engine to discover and enable all public classes that inherit fromTagHelperwithin theTheExampleAppassembly. This means any new custom tag helper created in the project is automatically available for use in views without further configuration.
For more information on the view architecture, see Razor Pages Architecture and Shared Views.
MarkdownTagHelper
The primary custom tag helper in this application is the MarkdownTagHelper. Its purpose is to convert Markdown-formatted text into HTML at render time. This is crucial for displaying rich text content managed in the Contentful CMS.
The helper is defined in TheExampleApp/TagHelpers/MarkdownTagHelper.cs and leverages the Markdig library for the conversion process.
Implementation Details
The MarkdownTagHelper is designed to be flexible and can be invoked in two ways, defined by its HtmlTargetElement attributes:
- As a custom element:
<markdown>...</markdown> - As an attribute on any standard HTML element:
<div markdown>...</div>
csharp
// File: TheExampleApp/TagHelpers/MarkdownTagHelper.cs
using Markdig;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;
using System.Threading.Tasks;
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; }
/// <summary>
/// Asynchronously executes the taghelper with the given context and output.
/// </summary>
public async override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
// If the tag is <markdown>, remove the tag itself from the output,
// leaving only the rendered HTML content.
if (output.TagName == "markdown")
{
output.TagName = null;
}
// Remove the 'markdown' attribute to keep the final HTML clean.
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)
{
// Prioritize content from the model-bound 'Content' property.
if (Content == null)
// If not bound, fall back to the inner content of the tag.
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
48
49
50
51
52
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
48
49
50
51
52
Key Logic in ProcessAsync:
- Tag Suppression: If the helper is used as a
<markdown>element,output.TagNameis set tonull. This prevents the<markdown>tag from being rendered in the final HTML, outputting only its processed content. - Attribute Removal: The
markdownattribute is removed from the output element to avoid non-standard attributes in the client-side HTML. - Content Retrieval: The
GetContenthelper method determines the source of the Markdown text—either from a bound model property or the element's inner content. - HTML Conversion:
Markdown.ToHtml()from the Markdig library performs the conversion. A null-coalescing operator (?? "") ensures that null input doesn't cause an exception. - Output Rendering:
output.Content.SetHtmlContent()replaces the element's original content with the newly generated HTML.
For more context on how Markdown is used, refer to the Markdown Rendering feature documentation.
Using tag helpers
The MarkdownTagHelper can be used in any Razor view (.cshtml) in two distinct ways.
1. As a Custom Element with Model Binding
This is the most common usage pattern in the application. It's ideal when the Markdown content comes from a view model property. The content attribute is used to bind the model property to the tag helper.
Example from LessonCopy.cshtml:
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
2
3
4
5
6
7
In this example:
- The
<markdown>tag invokes theMarkdownTagHelper. - The
contentattribute binds to theCopyproperty of the@Modelobject (which is of typeLessonCopy). - The tag helper will render the HTML equivalent of the Markdown string stored in
@Model.Copy.
2. As an Attribute with Inline Content
This approach is useful for static Markdown content written directly in a Razor view. By adding the markdown attribute to a standard HTML element (like a div), you instruct the tag helper to process its inner content.
Example:
cshtml
<div markdown class="alert alert-info">
# Important Note
This content will be processed from Markdown into HTML.
- List item 1
- List item 2
</div>1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Resulting HTML:
html
<div class="alert alert-info">
<h1>Important Note</h1>
<p>This content will be processed from Markdown into HTML.</p>
<ul>
<li>List item 1</li>
<li>List item 2</li>
</ul>
</div>1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Tag helper attributes
The MarkdownTagHelper exposes a single custom attribute for configuration.
| Attribute | Type | Required | Description |
|---|---|---|---|
content | ModelExpression | No | Binds a model property containing the Markdown string to be rendered. If this attribute is omitted, the tag helper processes the inner content of the element instead. |
Model binding with tag helpers
Model binding is a key feature of the MarkdownTagHelper, enabling it to work seamlessly with data from the application's models, such as those populated by Contentful.
The magic happens through the Content property on the tag helper:
csharp
public ModelExpression Content { get; set; }1
The type ModelExpression is a special ASP.NET Core type that holds both the metadata and the value of the expression passed to the attribute (e.g., @Model.Copy).
The GetContent private method demonstrates the logic for prioritizing model-bound data:
csharp
private async Task<string> GetContent(TagHelperOutput output)
{
// 1. Check if the 'Content' property was bound from the view.
if (Content == null)
// 2. If not, get the content from between the opening and closing tags.
return (await output.GetChildContentAsync()).GetContent();
// 3. If it was bound, return the value from the model.
return Content.Model?.ToString();
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
This design pattern makes the component highly reusable: it can operate on dynamic data from a model (see Lesson Modules Models) or on static content written directly in the view.
Custom tag helper development
Developers can easily create new tag helpers to encapsulate other reusable UI logic. The global registration in _ViewImports.cshtml ensures they are immediately available.
Follow these steps to create a new tag helper:
Step 1: Create the Class
Create a new C# class in the TheExampleApp/TagHelpers directory. The class must be public and inherit from Microsoft.AspNetCore.Razor.TagHelpers.TagHelper.
Step 2: Define the Target Element/Attribute
Use the [HtmlTargetElement] attribute on your class to specify what it should target.
[HtmlTargetElement("my-custom-tag")]: Targets a<my-custom-tag>element.[HtmlTargetElement(Attributes = "my-attribute")]: Targets any element with amy-attributeattribute.[HtmlTargetElement("p", Attributes = "highlight")]: Targets only<p>elements that have ahighlightattribute.
Step 3: Define Public Properties for Attributes
For each HTML attribute you want your tag helper to accept, create a corresponding public property. The property name (converted to kebab-case) becomes the attribute name in HTML.
Step 4: Implement the Processing Logic
Override the ProcessAsync (or synchronous Process) method to implement your logic. You will use the TagHelperContext and TagHelperOutput parameters to read information and modify the output.
Example: A Simple "Environment" Tag Helper
Imagine you need a tag helper that only renders its content in the Development environment.
1. Create the file TheExampleApp/TagHelpers/EnvironmentTagHelper.cs:
csharp
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace TheExampleApp.TagHelpers
{
// This helper will render its content only if the 'name' matches the current environment.
[HtmlTargetElement("environment")]
public class EnvironmentTagHelper : TagHelper
{
private readonly IHostingEnvironment _hostingEnv;
// The name of the environment to target (e.g., "Development", "Staging")
public string Name { get; set; }
// Use dependency injection to get the current hosting environment.
public EnvironmentTagHelper(IHostingEnvironment hostingEnv)
{
_hostingEnv = hostingEnv;
}
public override void Process(TagHelperContext context, TagHelperOutput output)
{
// If the environment name does not match the specified name, suppress the output.
if (!_hostingEnv.IsEnvironment(Name))
{
output.SuppressOutput();
}
// Make the <environment> tag itself disappear from the final HTML.
output.TagName = null;
}
}
}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
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
2. Use it in a view:
cshtml
<environment name="Development">
<p class="text-danger">
This message is only visible to developers running the application locally.
</p>
</environment>1
2
3
4
5
2
3
4
5
Because of the setup in _ViewImports.cshtml, this new tag helper is ready to use as soon as the class is created. Note that this is a simplified version of the built-in Environment Tag Helper and is provided here as a clear development example.