Custom Tag Helper in ASP.NET Core MVC

Custom Tag Helper in ASP.NET Core MVC

In this article, I will discuss Creating a Custom Tag Helper in ASP.NET Core MVC Applications with Examples. Please read our previous article discussing Partial Tag Helper in ASP.NET Core MVC Application.

Why Custom Tag Helper in ASP.NET Core MVC?

Custom Tag Helpers in ASP.NET Core MVC extend the functionality of standard HTML elements or create entirely new custom elements by adding server-side behaviors. They provide a way to create reusable components with a clean, HTML-like syntax, encapsulating complex logic that can be reused across different views. Custom Tag Helpers are beneficial because they:

  • Reusability: Once defined, custom tag helpers can be reused across multiple views, reducing code duplication and promoting consistent behavior and appearance. 
  • Improved Readability: Tag Helpers allow developers to use HTML-like syntax to invoke server-side logic. This improves the readability of views by keeping the markup clean and developers who may not be familiar with C# or ASP.NET Core but understand HTML.
  • Enhanced Functionality: They can interact with model data and ViewData, execute complex logic, and render the output based on the conditions or data passed to them, which is much more difficult with standard HTML helpers.
  • Rich HTML Generation: Custom Tag Helpers can generate dynamic HTML content, helping to build rich UIs with minimal effort.
  • Cleaner Razor Views: By moving complex HTML and C# code out of the view and into a tag helper, the Razor view becomes cleaner and easier to maintain.
What is Custom Tag Helper in ASP.NET Core MVC?

A Custom Tag Helper in ASP.NET Core MVC is a class that extends HTML tags with server-side logic, allowing for dynamic content generation in Razor views. Custom Tag Helpers are classes inherited from TagHelper and are responsible for manipulating or generating HTML based on certain conditions, attributes, or logic. They can target standard HTML tags, such as <div>, <input>, etc. or create completely custom tags.

Tag Helpers are processed at runtime by ASP.NET Core and convert custom or standard tags with specific functionality, such as modifying attributes, setting content, or adding styles dynamically. 

How Do We Create and Use a Custom Tag Helper in ASP.NET Core MVC?

We need to follow the below steps to create and use a Custom Tag Helper in the ASP.NET Core MVC Application:

Create a Tag Helper Class:

First, we need to create a class that extends TagHelper. Tag helpers follow a convention where the class name must end with TagHelper. This class will contain the logic executed when the tag helper is invoked in a Razor view. If we want to target a specific HTML element, we need to decorate the class with the [HtmlTargetElement] attribute, or you can omit it to use the class name as the tag. If this is not clear, don’t worry; we will look at both options with examples.

Add Logic to Process the Tag:

Override the Process or ProcessAsync method of the TagHelper class to add the rendering logic that modifies the tag helper’s output. These methods provide access to the TagHelperContext and TagHelperOutput objects, allowing the tags and their content to be manipulated.

Register the Tag Helper:

Next, we need to add the Custom Tag Helper to the Razor view or globally to all views by adding @addTagHelper *, YourAssemblyName in the _ViewImports.cshtml file.

Use the Tag Helper in a Razor View:

Use the tag helper in a Razor view like any HTML element. Pass any necessary parameters via attributes. 

Example of Understanding Custom Tag Helpers in ASP.NET Core MVC:

Let us proceed and see a few examples to understand this concept better. First, create a new ASP.NET Core Application named CustomTagHelpersDemo using the Model View Controller Project template. In this example, we will create a Custom Button Tag Helper that generates a Bootstrap-styled button.

Create a New Custom Tag Helper Class

First, create a class file named MyCustomButtonTagHelper.cs within the Models folder, and then copy and paste the following code. Tag helpers follow a convention where the class name must end with TagHelper. So, the tag helper’s name is MyCustomButton, and it suffixes TagHelper to behave like a Tag helper similar to the controller.

using Microsoft.AspNetCore.Razor.TagHelpers;
namespace CustomTagHelpersDemo.Models
{
    // Specify that this TagHelper will target HTML elements with the name "custom-button"
       [HtmlTargetElement("custom-button")]
    // Inherits from TagHelper to create custom tag helper functionality.
    public class MyCustomButtonTagHelper : TagHelper
    {
        // Define a public property to store the text inside the button, with a default value "Click Me"
        public string Text { get; set; } = "Click Me";

        // Define a public property to store the button type (e.g., "submit", "button"), default value is "button"
        public string ButtonType { get; set; } = "button";

        // Define a public property to store the CSS class for the button, with a default value "btn-primary"
        public string ButtonClass { get; set; } = "btn-primary";

        // Override the Process method to define the custom processing logic of the tag helper, which gets called when the TagHelper runs
        // TagHelperContext context: Provides information about the tag being processed, such as the tag's attributes and ID.
        // TagHelperOutput output: Controls the final output that is rendered, including modifying the tag name, attributes, and inner content of the tag being processed.
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            // Set the output tag to be a "button" element, effectively rendering <button> in the final HTML
            output.TagName = "button";

            // Add the "type" attribute to the button element, using the value specified in the ButtonType property
            output.Attributes.SetAttribute("type", ButtonType);

            // Add the "class" attribute to the button, combining "btn" with the value in the ButtonClass property
            output.Attributes.SetAttribute("class", $"btn {ButtonClass}");

            // Set the content inside the button, using the value from the Text property
            output.Content.SetContent(Text);
        }
    }
}
Register Custom Tag Helper

In the _ViewImports.cshtml file, add the following line to register the custom Tag Helper. Here, CustomTagHelpersDemo is the name of our assembly where we generate the Custom Tag Helper, and @addTagHelper is the directive used to register the Tag helpers. You need to replace CustomTagHelpersDemo with your assembly name.
@addTagHelper *, CustomTagHelpersDemo

With the above line in place, the _ViewImports.cshtml file should look as follows:

@using CustomTagHelpersDemo
@using CustomTagHelpersDemo.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, CustomTagHelpersDemo
Use the Custom Tag Helper in a Razor View

Now, you can use the custom-button tag in your Razor views. So, modify the Index view of the Home Controller as follows:

@{
    ViewData["Title"] = "Home Page";
}

@*  Uses the custom tag helper "custom-button" to create a button.
    This custom tag helper must be defined in your project as per the previous code example. *@
<custom-button text="Submit" button-type="submit" button-class="btn-success"></custom-button>
@* This line creates a button with the text "Submit".
   It sets the button type to "submit", which means it's intended to submit a form.
   It applies a Bootstrap class "btn-success" to style the button with a green color. *@

@*  Another usage of the "custom-button" tag helper to create a different button. *@
<custom-button text="Cancel" button-class="btn-danger"></custom-button>
@*  This line creates a button with the text "Cancel".
    Since no "button-type" attribute is specified, it defaults to "button" as defined in the Tag Helper.
    The button is styled with the Bootstrap class "btn-danger", giving it a red color. *@

Now, run the application, and you should see the button elements as expected in the UI. If you verify the page source code, you will see the following HTML generated behind the scenes.

<button type="submit" class="btn btn-success">Submit</button>
<button type="button" class="btn btn-danger">Cancel</button>
What happens if we remove the HtmlTargetElement Attribute from the Custom Tag Helper class?

If we remove the HtmlTargetElement attribute from the Custom Tag Helper class, we can no longer access the tag helper using the custom-button tag helper. Let us understand this with an example. First, modify the MyCustomButtonTagHelper class as follows. Here, you can observe we have removed the HtmlTargetElement Attribute.

using Microsoft.AspNetCore.Razor.TagHelpers;
namespace CustomTagHelpersDemo.Models
{
    // Inherits from TagHelper to create custom tag helper functionality.
    public class MyCustomButtonTagHelper : TagHelper
    {
        // Define a public property to store the text inside the button, with a default value "Click Me"
        public string Text { get; set; } = "Click Me";

        // Define a public property to store the button type (e.g., "submit", "button"), default value is "button"
        public string ButtonType { get; set; } = "button";

        // Define a public property to store the CSS class for the button, with a default value "btn-primary"
        public string ButtonClass { get; set; } = "btn-primary";

        // Override the Process method to define the custom processing logic of the tag helper, which gets called when the TagHelper runs
        // TagHelperContext context: Provides information about the tag being processed, such as the tag's attributes and ID.
        // TagHelperOutput output: Controls the final output that is rendered, including modifying the tag name, attributes, and inner content of the tag being processed.
        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            // Set the output tag to be a "button" element, effectively rendering <button> in the final HTML
            output.TagName = "button";

            // Add the "type" attribute to the button element, using the value specified in the ButtonType property
            output.Attributes.SetAttribute("type", ButtonType);

            // Add the "class" attribute to the button, combining "btn" with the value in the ButtonClass property
            output.Attributes.SetAttribute("class", $"btn {ButtonClass}");

            // Set the content inside the button, using the value from the Text property
            output.Content.SetContent(Text);
        }
    }
}

With these changes, we cannot access the custom tag helper using the custom-button tag helper. If you run the application, you will not get any data for the button elements. 

How Do We Access the Custom Tag Helper without HtmlTargetElement Attribute in ASP.NET Core MVC?

Custom Tag Helpers in ASP.NET Core MVC follow a convention where the class name must end with TagHelper. When we create a class named MyCustomButtonTagHelper, the Razor engine automatically assumes the tag name as my-custom-button. To invoke the tag helper in your Razor view, we need to use <my-custom-button> as the tag name. This is because the TagHelper suffix is removed, and the remainder of the class name (MyCustomButton) is converted to kebab-case (my-custom-button). For a better understanding, please modify the Index view as follows:

@{
    ViewData["Title"] = "Home Page";
}

<my-custom-button text="Submit" button-type="submit" button-class="btn-success"></my-custom-button>

<my-custom-button text="Cancel" button-class="btn-danger"></my-custom-button>

With the above changes in place, run the application, and it should work as expected.

Real-time Example to Understand Custom Tag Helper in ASP.NET Core MVC:

Let’s create a real-time example where the custom Tag Helper generates a list of recent blog posts with a title and summary fetched asynchronously from a service. The number of posts to be displayed will be taken as an input parameter. This Tag Helper will be reusable across different views where you want to display a list of recent blog posts. Here, we will build a Tag Helper that fetches recent blog posts from a service and displays them in a structured format.

Step 1: Define a Model

First, we define a simple BlogPost model. So, create a class file named BlogPost.cs with the Models folder and then copy and paste the following code:

namespace CustomTagHelpersDemo.Models
{
    public class BlogPost
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Summary { get; set; }
        public DateTime PublishedOn { get; set; }
    }
}
Step 2: Create a Dummy Data Service

Next, we will create a service that asynchronously returns blog posts. In a real-time application, this service would fetch data from a database or an external API. Here, we have hardcoded the data. So, create a class file named BlogPostService.cs within the Models folder and copy and paste the following code:

namespace CustomTagHelpersDemo.Models
{
    public class BlogPostService
    {
        private static readonly List<BlogPost> _blogPosts = new()
        {
            new BlogPost { Id = 1, Title = "First Post", Summary = "This is the first post summary.", PublishedOn = DateTime.Now.AddDays(-10) },
            new BlogPost { Id = 2, Title = "Second Post", Summary = "This is the second post summary.", PublishedOn = DateTime.Now.AddDays(-9) },
            new BlogPost { Id = 3, Title = "Third Post", Summary = "This is the third post summary.", PublishedOn = DateTime.Now.AddDays(-6) },
            new BlogPost { Id = 4, Title = "Fourth Post", Summary = "This is the fourth post summary.", PublishedOn = DateTime.Now.AddDays(-3) },
            new BlogPost { Id = 5, Title = "Fifth Post", Summary = "This is the fifth post summary.", PublishedOn = DateTime.Now.AddDays(-2) }
        };

        public async Task<List<BlogPost>> GetRecentPostsAsync(int count)
        {
            await Task.Delay(TimeSpan.FromSeconds(1));
            var recentPosts = _blogPosts
                .OrderByDescending(post => post.PublishedOn)
                .Take(count).ToList();
            return recentPosts;
        }
    }
}
Step 3: Create the Custom Tag Helper

Now, we will create the Custom RecentPosts tag helper, which uses the BlogPostService to fetch and display blog post data. This class should and must end with TagHelper. So, create a class file named RecentPostsTagHelper.cs within the Models folder and copy and paste the following code.

namespace CustomTagHelpersDemo.Models
{
    using Microsoft.AspNetCore.Razor.TagHelpers;
    using System.Threading.Tasks;

    [HtmlTargetElement("recent-posts")]
    public class RecentPostsTagHelper : TagHelper
    {
        // Attribute to specify the number of recent posts to display
        [HtmlAttributeName("count")]
        public int Count { get; set; } = 5; // Default to 5 if not specified

        public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
        {
            // Simulate a service that fetches the recent blog posts
            BlogPostService blogPostService = new BlogPostService();
            var posts = await blogPostService.GetRecentPostsAsync(Count);

            // Set the output tag to render as a div with Bootstrap classes for better styling
            output.TagName = "div"; // Render the tag as a div
            output.Attributes.SetAttribute("class", "container mt-4"); // Container class to wrap the content

            // Start building the content using Bootstrap components
            var content = "<div class='row'>"; // Start a new Bootstrap row for vertical alignment

            foreach (var post in posts)
            {
                content += $@"
                    <div class='col-12 mb-4'>
                        <div class='card border-primary shadow'>
                            <div class='card-header bg-primary text-white'>
                                <h5 class='card-title mb-0'>{post.Title}</h5>
                            </div>
                            <div class='card-body'>
                                <p class='card-text'>{post.Summary}</p>
                                <p class='card-text'><small class='text-muted'>Published on: {post.PublishedOn.ToShortDateString()}</small></p>
                            </div>
                            <div class='card-footer text-right'>
                                <a href='#' class='btn btn-outline-primary btn-sm'>Read More</a>
                            </div>
                        </div>
                    </div>";
            }

            content += "</div>"; // Close the Bootstrap row

            // Add the constructed content to the TagHelper output
            output.Content.SetHtmlContent(content);
        }
    }
}
Step 4: Register the Tag Helper

Next, we need to register the tag helper in _ViewImports.cshtml. So, modify the _ViewImports.cshtml file as follows. In our previous example, we registered our assembly using the @addTagHelper directive.

@using CustomTagHelpersDemo
@using CustomTagHelpersDemo.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, CustomTagHelpersDemo
Step 5: Use the Custom Tag Helper in Razor Views

Use the recent-posts Tag Helper in Razor views to display the most recent posts. So, modify the Index view of the Home Controller as follows:

@{
    ViewData["Title"] = "Home Page";
}

<!-- Main container with Bootstrap classes for padding and centering -->
<div class="container mt-5">

    <!-- Header section with a Bootstrap card for better visual appearance -->
    <div class="card bg-light mb-4 shadow-sm">
        <div class="card-body text-center">
            <h1 class="card-title display-4">Welcome to the Home Page</h1>
            <p class="lead">Stay updated with our latest blog posts and news.</p>
        </div>
    </div>

    <!-- Section for recent posts with some padding and styling -->
    <div class="recent-posts-section">
        <h3 class="mb-4">Recent Blog Posts</h3>
        <recent-posts count="3"></recent-posts>
    </div>

</div>

Now, run the application, and you should get the output as expected, as shown in the image below:

Creating Custom Tag Helper in ASP.NET Core MVC

In the next article, I will discuss View Component Tag Helper in ASP.NET Core MVC Applications. In this article, I explain how to create a custom tag helper in an ASP.NET Core MVC application with examples. I hope you enjoy this article on creating a custom tag helper in the ASP.NET Core MVC.

Leave a Reply

Your email address will not be published. Required fields are marked *