Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ASP.NET Core 2.0 MVC Custom Tag Helpers

0.00/5 (No votes)
27 Aug 2017 1  
How to create custom Tag Helpers in ASP.NET Core MVC. Continue reading...

Problem

How to create custom Tag Helpers in ASP.NET Core MVC.

Solution

In an empty project, add NuGet packages update Startup class to add services and middleware for MVC:

public void ConfigureServices(
            IServiceCollection services)
        {
            services.AddMvc();
        }

        public void Configure(
            IApplicationBuilder app, 
            IHostingEnvironment env)
        {
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }

Add a controller with an action method:

public IActionResult Employees()
        {
            var model = new EmployeesViewModel
            {
                Employees = new List<Employee>
                {
                    new Employee {
                        Name = "Tahir Naushad",
                        JobTitle = "Software Developer",
                        Profile = "C#/ASP.NET Developer",
                        Friends = new List<Friend>
                        {
                            new Friend { Name = "Tom" },
                            new Friend { Name = "Dick" },
                            new Friend { Name = "Harry" },
                        }
                    },
                    new Employee {
                        Name = "James Bond",
                        JobTitle = "MI6 Agent",
                        Profile = "Has licence to kill",
                        Friends = new List<Friend>
                        {
                            new Friend { Name = "James Gordon" },
                            new Friend { Name = "Robin Hood" },
                        }
                    },
                }
            };
            return View(model);
        }

An Employees.cshtml page:

@model Fiver.Mvc.TagHelpers.Custom.Models.Home.EmployeesViewModel
@foreach (var employee in Model.Employees)
{
    <employee summary="@employee.Name"

              job-title="@employee.JobTitle"

              profile="@employee.Profile">
        @foreach (var friend in employee.Friends)
        {
            <friend name="@friend.Name" />
        }
    </employee>
}

Add a class EmployeeTagHelper.cs:

[HtmlTargetElement("employee")]
    public class EmployeeTagHelper : TagHelper
    {
        [HtmlAttributeName("summary")]
        public string Summary { get; set; }

        [HtmlAttributeName("job-title")]
        public string JobTitle { get; set; }

        [HtmlAttributeName("profile")]
        public string Profile { get; set; }

        public override void Process(
            TagHelperContext context,
            TagHelperOutput output)
        {
            output.TagName = "details";
            output.TagMode = TagMode.StartTagAndEndTag;

            var sb = new StringBuilder();
            sb.AppendFormat("<summary>{0}</summary>", this.Summary);
            sb.AppendFormat("<em>{0}</em>", this.JobTitle);
            sb.AppendFormat("<p>{0}</p>", this.Profile);
            sb.AppendFormat("<ul>");

            output.PreContent.SetHtmlContent(sb.ToString());

            output.PostContent.SetHtmlContent("</ul>");
        }
    }

Discussion

As discussed in the previous post, Tag Helpers help generate HTML by attaching attributes to existing HTML elements or by creating new elements. In this post, we’ve created a new tag to display employee information.

Tag Helper Class

To create custom Tag Helpers, you create a C# class that inherits from TagHelper abstract base class. There are two attributes that help define the behaviour of our class:

[RestrictChildren] – Restricts the nesting structure of tag helpers. For instance, here the employee tag can only have friend tag nested in them:

[RestrictChildren("friend")]
    [HtmlTargetElement("employee")]
    public class EmployeeTagHelper : TagHelper

Note: This also restricts the use of razor syntax, which I feel is bit too restrictive!

[HtmlTargetElement] – tag helper names by default are kebab casing of C# class name. However, using this attribute, you can explicitly define tag helper name, its parent tag name, tag structure (self-closing, without end tag) and allowed attributes. You can apply this attribute more than once if the C# class is targeting more than one tag helper.

Linking HTML Elements to C# Properties

Properties in your custom C# class maps to HTML element attributes. You could override this behaviour (i.e., stop binding properties to HTML attributes) by using [HtmlAttributeNotBound] attribute on the property. You could also explicitly specify the name of HTML attributes by annotating properties with [HtmlAttributeName].

Process and ProcessAsync (Execution)

Overriding one of these methods enable us to output HTML for our Tag Helpers. There are two parameters to these methods:

  • TagHelperContext – contains a readonly collection of all HTML attributes applied to your tag helper. Also contains Items dictionary that can be used to pass data between nested tag helpers.
  • TagHelperOutput – used to set HTML elements and their attributes for the tag helper. There are five properties to set content (PreElement, PreContenet, Content, PostContent, PostElement) and Attributes property to add attributes to tag helper’s HTML. There are also properties to set HTML element that tag helper outputs (TagName) and its closing tag behaviour (TagMode).

Model Data

We can populate tag helper properties via model binding by creating properties of type ModelExpression:

[HtmlTargetElement("movie",
        TagStructure = TagStructure.WithoutEndTag)]
    public class MovieTagHelper : TagHelper
    {
        [HtmlAttributeName("for-title")]
        public ModelExpression Title { get; set; }

        [HtmlAttributeName("for-year")]
        public ModelExpression ReleaseYear { get; set; }

        [HtmlAttributeName("for-director")]
        public ModelExpression Director { get; set; }

        [HtmlAttributeName("for-summary")]
        public ModelExpression Summary { get; set; }
        
        [HtmlAttributeName("for-stars")]
        public ModelExpression Stars { get; set; }

Then using @model directive to declare and use model binding:

@model Fiver.Mvc.TagHelpers.Custom.Models.Home.MovieViewModel
<movie for-title="Title"
       for-year="ReleaseYear"
       for-director="Director"
       for-summary="Summary"
       for-stars="Stars" />

Value of the property can be retrieved using Model property of ModelExpression:

var director = new TagBuilder("div");
            director.Attributes.Add("class", "movie-director");
            director.InnerHtml.AppendHtml(
                string.Format("<span>Director: {0}</span>", this.Director.Model));

ViewContext

Within the tag helper class, we can use ViewContext type to access view’s contextual information, e.g., HttpContext, ModelState, etc. This is achieved by declaring a property of type ViewContext and annotating with a [ViewContext] and [HttpAttributeNotBound] attributes:

[HtmlAttributeNotBound]
        [ViewContext]
        public ViewContext ViewContext { get; set; }

Dependency Injection

Tag Helpers can benefit from dependency injection like other parts of ASP.NET Core. Simply add your service in ConfigureServices method of Startup and inject in tag helper using constructor. The sample code with this post demonstrates this.

Tag Builder

Instead of concatenating strings to create HTML output, you could use TagBuilder class. It helps in building HTML element and their attributes. You could create nested tags easily using this mechanism:

var year = new TagBuilder("span");
            year.Attributes.Add("class", "movie-year");
            year.InnerHtml.AppendHtml(
                string.Format("({0})", this.ReleaseYear.Model));

            var title = new TagBuilder("div");
            title.Attributes.Add("class", "movie-title");
            title.InnerHtml.AppendHtml(
                string.Format("{0}", this.Title.Model));
            title.InnerHtml.AppendHtml(year);

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here