Creating Custom Tag Helper in ASP.NET Core

Sheonarayan
Posted by in ASP.NET Core category on for Advance level | Points: 250 | Views : 12458 red flag
Rating: 4.5 out of 5  
 2 vote(s)

In this article, we shall learn what is TagHelper in ASP.NET Core and how to create custom tag helper.


 Download source code for Creating Custom Tag Helper in ASP.NET Core

Recommendation
Read 6 Steps to host ASP.NET MVC/Core application on server before this article.

Introduction

Tag Helpers enable use to run server-side code to render HTML, CSS and JavaScript codes in the Razor view.

Tag Helpers helps us in
  1. Writing HTML friendly code - Most of the Razor view contains HTML and Tag Helper looks like standard HTML code so it is easy for front end developers who does not feel comfortable with server side C# code in the view.
  2. IntelliSense support - Even if Tag Helpers looks like HTML, a developer can get like server side IntelliSense support that makes them more productive.
Tag Helpers attach to HTML elements while HTML Helpers (methods) are invoked as server side methods in Razor views.

Tag Helper
<a asp-action="Index">Back to List</a>
Here asp-action attributes tells that when this link is clicked send the user to Index action method of the current controller.

Html Helper
@Html.ActionLink("Back to List", "Index")
Here the first method parameter is the link text and second is the action method of the current controller.

OUTPUT: Both above codes generates the same HTML output
<a href="/Categories/Create">Create New</a>

Background

Apart from using ASP.NET Core in-built Tag Helpers, we can also create our own. In this article, we shall learn how to create a custom tag helpers in ASP.NET Core.

This is generally beneficial when you want to write certain set of HTML codes on multiple Razor view. In this case, we shall learn creating a custom Tag Helper that writes Categories from the database.

Creating Custom Tag Helper

A tag helper is a class that implements ITagHelper interface or derive from TagHelper class. When we are creating a custom Tag Helper, we shall derive our class from TagHelper class.

To segregate this class from remaining classes of the project, create a TagHelpers folder in the root of your application as shown in the picture below.



Now create a class file named CategoryTagHelper.cs in this folder. Note that it is not mandatory to name the class file suffixed with "TagHelper" however it is a best practice and naming convention that is followed so that it these classes are easily identified. When you use this TagHelper, the suffixed word "TagHelper" is ignored.

Custom Tag Helper class

Let's learn how to create Custom Tag Helper class in ASP.NET Core.
Step - 1

Below is the CategoryTagHelper.cs file created under TagHelpers folder. Notice that this class is inheriting from TagHelper class (in-built). TagHelper class has few virtual methods that we need to override to write our content for this tag. 

Primarily TagHelper (in-built) has two method
  • Process - to process the data for this tag
  • ProcessAsync - to process the data for this tag asynchronously
If we create any property in this class, that becomes an attribute for the tag. In this case we have created CategoryId property  so this becomes attribute (we will see these things later in this article).

Normally, we do not need a constructor for the custom Tag Helper class but as we are going to use the database (Entity Framework) so we are injecting TrainingDatabaseContext that has been added as service in the ConfigureServices method of Startup.cs file.

using ASPNETCoreTutorials.Data;
using ASPNETCoreTutorials.Models;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ASPNETCoreTutorials.TagHelpers
{
    public class CategoryTagHelper : TagHelper
    {
        public int CategoryId { get; set; }

        private readonly TrainingDatabaseContext _context;
        public CategoryTagHelper(TrainingDatabaseContext context)
        {
            _context = context;
        }

        public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
        {
            int? myCategory = null;
            if (CategoryId > 0)
            {
                myCategory = CategoryId;
            }

            var childContent = await output.GetChildContentAsync();
            output.TagName = "div";

            output.TagMode = TagMode.StartTagAndEndTag;
            string innerContent = childContent.GetContent();
            string categories = innerContent + "<hr />" + GetCategories(myCategory);

            output.Content.SetHtmlContent(categories);
        }

        private string GetCategories(int? id)
        {
            List<Categories> list = new List<Categories>();

            if (id.HasValue)
            {
                list = _context.Categories.Where(d => d.CategoryId.Equals(id.Value)).ToList();
            }
            else
            {
                list = _context.Categories.ToList();
            }

            StringBuilder strB = new StringBuilder("<ul class=\"list-group\">");
            foreach(var item in list)
            {
                strB.Append("<li class=\"list-group-item\">" + item.CategoryName + "</li>");
            }
            strB.Append("</ul>");
            return strB.ToString();
        }

        ///The same can be achieved using this method as well. If both ProcessAsync() and Process() method is 
        /// written then ProcessAsync() method is called.
        //public override void Process(TagHelperContext context, TagHelperOutput output)
        //{

        //    int? myCategory = null;
        //    if (CategoryId > 0)
        //    {
        //        myCategory = CategoryId;
        //    }

        //    output.TagName = "div";
        //    output.TagMode = TagMode.StartTagAndEndTag;
        //    string categories = output.Content + "<hr /> Synchronus" + GetCategories(myCategory);

        //    output.Content.SetHtmlContent(categories);
        //}
    }
}
In the ProcessAsync method, we are checking for CategoryId, if it is provided (greater than 0) then setting myCategory value. 

If we are using this Tag class with child content like below

<category>
    <strong>Only category</strong> <br />
    <input type="text" value="<category>with child content</category>" size="60" />
</category>
then we can retrieve the content within <category></category> tag with the help of output.GetChildContentAsync() method and then calling GetContent() method.

The GetCategories(int? id) method is being used to retrieve the categories from the database based on whether the id (CategoryId) has been passed or not.

Finally, we are using output.Content.SetHtmlContent() method to set the complete content from GetCategories method and inner content of this tag, if any.

Now build the project and ensure that there is no error.

Step - 2
Once the Tag Helper class is created, we need to intimate the ASP.NET Core to make this available to all the Razor views, to do this we need to register our add a @addTagHelper directive into ~/Views/_ViewImports.cshtml page (This is the page where namespaces are used so that their classes id available to all the views. If a namespace is used here you do not need to use the same namespaces into any other specific views).

Look at the last directive in below code. This says that use all tag helper class from ASPNETCoreTutorials (my project name) assembly.
@using ASPNETCoreTutorials
@using ASPNETCoreTutorials.Models
@using ASPNETCoreTutorials.Models.AccountViewModels
@using ASPNETCoreTutorials.Models.ManageViewModels
@using Microsoft.AspNetCore.Identity
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, ASPNETCoreTutorials

Step - 3
Again build the project and then go to the view page where you would like to use the Category Tag Helper we just created.

Now type <cate (Despite the Tag Helper name is CategoryTagHelper, the actual tag name will be category as the 'TagHelper' suffix is ignored by the ASP.NET Framework and upper case is converted to lower case unless explicitly specified as an attribute - not explained here) and you will see the IntelliSense as shown in the picture below (If you are not getting, try to close all the document in the Visual Studio and open the view again, if not try to close the Visual Studio and open again).


As we have created a property (CategoryId) in the Category Tag Helper, so we are getting an attribute category-id (PascalCase gets translated into lower-kebab-case).


In my View, I have written following code

<input type="text" value="<category></category>" />
<category></category>

<category>
    <strong>Only category</strong> <br />
    <input type="text" value="<category>with child content</category>" size="60" />
</category>

<category category-id="1">
    <strong>Category with category-id</strong> <br />
    <input type="text" value="<category category-id='1'> with child content</category>" style="width:390px;max-width:1000px;" />
</category>

The first tag is just <category></category>, this gives all the categories from the database.

The second tag is <category></category> with child content that will give child content as heading and then all the categories from the database.


The third tag is <category category-id="1"></category> with child content that will give child content as heading and category where Category = 1.


Conclusion

Hope this article helped in creating a custom Tag Helper in ASP.NET Core project. In case the need is not to handle the database, simply remove the database part and write your own HTML code.

Reference

https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/authoring

Recommendation
Read Star rating system in ASP.NET MVC after this article.
Page copy protected against web site content infringement by Copyscape

About the Author

Sheonarayan
Full Name: Sheo Narayan
Member Level: HonoraryPlatinum
Member Status: Administrator
Member Since: 7/8/2008 6:32:14 PM
Country: India
Regards, Sheo Narayan http://www.dotnetfunda.com

Ex-Microsoft MVP, Author, Writer, Mentor & architecting applications since year 2001. Connect me on http://www.facebook.com/sheo.narayan | https://twitter.com/sheonarayan | http://www.linkedin.com/in/sheonarayan

Login to vote for this post.

Comments or Responses

Login to post response

Comment using Facebook(Author doesn't get notification)