Author : MD TAREQ HASSAN

What is a Tag Helper

Ways of using TagHelpers

Attribute example

<label asp-for="Email" class="control-label"></label>
<input asp-for="Email" class="form-control" />

Tag/element example

<email>...</email>

Child element example

<tab active-page="Sessions">
    <tab-item title="Home"></tab-item>
    <tab-item title="Speakers"></tab-item>
    <tab-item title="Sessions"></tab-item>
    <tab-item title="Registration"></tab-item>
</tab>

Making Tag Helpers available

Views/_ViewImports.cshtml or Pages/_ViewImports.cshtml

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, FooNamespace

See: Managing Tag Helper scope

Creating a custom Tag Helper

By convension

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Runtime.TagHelpers;
using Microsoft.AspNetCore.Razor.TagHelpers;

namespace WebAppTagHelper.TagHelpers
{

    public class AutoPriceTagHelper : TagHelper
    {

        public string Make { get; set; }


        public string Model { get; set;  }

        public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
        {
			// await here
        }
    }
}

Usage

<auto-price make="chevy" model-name="camero">

</auto-price>

Using attribute

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Runtime.TagHelpers;
using Microsoft.AspNetCore.Razor.TagHelpers;

namespace WebAppTagHelper.TagHelpers
{
    [HtmlTargetElement("auto-price")]
    public class AutoPriceTagHelper : TagHelper
    {
        [HtmlAttributeName("maker")]
        public string Make { get; set; } // to prevent mapping to attribute => [HtmlAttributeNotBound]

        [HtmlAttributeName("model-name")]
        public string Model { get; set;  }

        public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
        {
			// await here
        }
    }
}

Usage

<auto-price maker="chevy" model-name="camero">

</auto-price>

EmailTagHelper example

Using Process instead of ProcessAsync for simplicity

TagHelpers/EmailTagHelper.cs

using Microsoft.AspNetCore.Razor.TagHelpers;
using System.Threading.Tasks;

namespace AuthoringTagHelpers.TagHelpers
{

    [HtmlTargetElement("email")] 
    public class EmailTagHelper : TagHelper
    {
	
		private const string EmailDomain = "hovermind.com";
		
		public string MailTo { get; set; }
		
		public override void Process(TagHelperContext context, TagHelperOutput output)
		{
			output.TagName = "a";    // Replaces <email> with <a> tag

			var address = MailTo + "@" + EmailDomain;
			output.Attributes.SetAttribute("href", "mailto:" + address);
			output.Content.SetContent(address);
		}
    }
}

Usage

<email mail-to="Support">Support</email>

Output

<a href="mailto:Support@hovermind.com">Support@hovermind.com</a>

To use without end tag: [HtmlTargetElement("email", TagStructure = TagStructure.WithoutEndTag)]

[HtmlTargetElement("email", TagStructure = TagStructure.WithoutEndTag)] 
public class EmailVoidTagHelper : TagHelper
{
    // ... ... ...
}

TagHelperContext

Process method parameter - TagHelperContext

See: TagHelperContext Class

TagHelperOutput

Process method parameter - TagHelperOutput

Content

public override void Process(TagHelperContext context, TagHelperOutput output)
{
	output.PreElement.SetHtmlContent("<div>PreElement</div>");
	output.PreContent.SetHtmlContent("<div>PreContent</div>");
	output.Content.SetHtmlContent("<div>Content</div>");
	output.PostContent.SetHtmlContent("<div>PostContent</div>");
	output.PostElement.SetHtmlContent("<div>PostElement</div>");
}

<div>PreElement</div>
	<my-test>
		<div>PreContent</div>
		<div>Content</div>
		<div>PostContent</div>
	</my-test>
<div>PostElement</div>

Attribute

public override void Process(TagHelperContext context, TagHelperOutput output)
{
	output.Attributes.Add("class","highlight");
}

<my-test class="highlight">
	My Test Tag Helper
</my-test>

TagName

public override void Process(TagHelperContext context, TagHelperOutput output)
{
	output.TagName = div";
	output.Attributes.Add("class","highlight");
}

<div class="highlight">
	My Test Tag Helper
</div>

See: TagHelperOutput Class

ModelExpression

using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;

namespace WebAppTagHelper.TagHelpers
{
    [HtmlTargetElement("mod-exp")]
    public class ModExpTagHelper : TagHelper
    {
        public ModelExpression HelperFor { get; set; }

        public override void Process(TagHelperContext context, 
            TagHelperOutput output)
        {
            var str = HelperFor == null ? "" :" Name: " + HelperFor.Name + " Model: " + HelperFor.Model;
            output.Content.SetContent(str);
        }
    }
}

Usage

<mod-exp helper-for="FirstName"></mod-exp>

See: ModelExpression Class

ViewContext

using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Razor.TagHelpers;

namespace WebAppTagHelper.TagHelpers
{
    [HtmlTargetElement("view-ctx")]
    public class ViewCtxTagHelper : TagHelper
    {
        [HtmlAttributeNotBound]
        [ViewContext]
        public ViewContext ViewContext { get; set; }

        public string MiscString { get; set; }

        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            var isHttps = ViewContext.HttpContext.Request.IsHttps;
            var isFormValid = ViewContext.ModelState.IsValid;
            MiscString = ViewContext.ExecutingFilePath;
			
            output.Content.SetContent("use above info here if needed");
        }
    }
}

Usage

<view-ctx misc-string="@ViewContext.ExecutingFilePath"></view-ctx>

See:

Passing data berween tag helpers

TagHelpers/TabTagHelper.cs

using Microsoft.AspNetCore.Razor.TagHelpers;

namespace WebAppTagHelper.TagHelpers
{  
    [RestrictChildren("tab-item")]
    [HtmlTargetElement("tab")]
    public class TabTagHelper : TagHelper
    {
        [HtmlAttributeName("active-page")]
        public string ActivePage { get; set; }

        public override void Process(TagHelperContext context, 
            TagHelperOutput output)
        {
            context.Items["ActivePage"] = ActivePage;

            output.TagName = "div";
            output.PreContent.SetHtmlContent
                (@"<h3>Silicon Valley Code Camp</h3>
                   <nav class='navbar navbar-expand-sm bg-light navbar-light'>
                     <ul class='nav nav-pills'>");
            output.PostContent.SetHtmlContent("</ul></nav>");
            output.Attributes.Add("class", "container");
        }
    }
}

TagHelpers/TabItemTagHelper.cs


using Microsoft.AspNetCore.Razor.TagHelpers;

namespace WebAppTagHelper.TagHelpers
{
    [HtmlTargetElement("tab-item")]
    public class TabItemTagHelper : TagHelper
    {
        public string Title { get; set; }

        public override void Process(TagHelperContext context, 
            TagHelperOutput output)
        {
            string activePage = context.Items["ActivePage"] as string;
              
            output.TagName = "li";

            var activeLabel = activePage == Title ? "active" : "";
            var str = string.Format(@"
                  <a class='nav-link {0}' data-toggle='pill' href='#'>{1}</a>
            ", activeLabel, Title);
            output.Content.SetHtmlContent(str);
        }
    }
}

Usage

<tab active-page="Sessions">
    <tab-item title="Home"></tab-item>
    <tab-item title="Speakers"></tab-item>
    <tab-item title="Sessions"></tab-item>
    <tab-item title="Registration"></tab-item>
</tab>

Dependency injection

using System;
using Microsoft.AspNetCore.Razor.TagHelpers;

namespace WebAppTagHelper.TagHelpers
{
    [HtmlTargetElement("foo-bar")]
    public class FooBarTagHelper : TagHelper
    {
        public string Baz { get; set; }

        private IFoo _foo;

        public TimeSinceTagHelper(IFoo foo)
        {
            _foo = foo;
        }

        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            output.Content.SetContent(_foo.GetBax());
        }
    }
}

HtmlTargetElement

Restricting tag

[RestrictChildren("tab-item")]
[HtmlTargetElement("tab")]
public class TabTagHelper : TagHelper
{
	// ... ... ...
}

[HtmlTargetElement("tab-item", ParentTag = "tab")]
public class TabItemTagHelper : TagHelper
{
	// ... ... ...
}

TagStructure Enum

[HtmlTargetElement("tab", TagStructure = TagStructure.NormalOrSelfClosing)]
public class TabTagHelper : TagHelper
{
	// ... ... ...
}

TagMode Enum

[HtmlTargetElement("tab", TagMode = TagMode.SelfClosing)]
public class TabTagHelper : TagHelper
{
	// ... ... ...
}

Tag Helper Components

Register TagHelperComponent

Dependency injection in ConfigureServices(...)

public void ConfigureServices(IServiceCollection services)
{
    // ... ... ...
	
    services.AddTransient<ITagHelperComponent, AddressStyleTagHelperComponent>();
    services.AddTransient<ITagHelperComponent, AddressScriptTagHelperComponent>();
	
    // ... ... ...
}

Registration via Razor file

@using RazorPagesSample.TagHelpers;
@using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
@inject ITagHelperComponentManager manager;

@{
	// ... ... ...
	
    manager.Components.Add(new AddressTagHelperComponent(...));
}

Registration via controller or Page Model (constructor injection)

using System;
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesSample.TagHelpers;

public class IndexModel : PageModel
{
    private readonly ITagHelperComponentManager _tagHelperComponentManager;

    public IndexModel(ITagHelperComponentManager tagHelperComponentManager)
    {
        _tagHelperComponentManager = tagHelperComponentManager;
    }

    public void OnGet()
    {
        // ... ... ...

        _tagHelperComponentManager.Components.Add(new AddressTagHelperComponent(...));
    }
}

See:

Create a custom TagHelper Component

To create a custom TagHelperComponent

See: Create a custom Tag Helper Component

Injecting Google analytics in the body using custom TagHelperComponent

TagHelperComponents/GoogleAnalyticsTagHelperComponent.cs

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace WebAppTHC.TagHelperComponents
{
    public class GoogleAnalyticsTagHelperComponent : TagHelperComponent
    {
        public GoogleAnalyticsTagHelperComponent(string trackingCode)
        {
            TrackingCode = trackingCode;
        }
		
        [HtmlAttributeName("tracking-code")]
        public string TrackingCode { get; set; } = "UA-XXXXXX-XX";

        public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
        {
            if (string.Equals(context.TagName, "body", StringComparison.OrdinalIgnoreCase))
            {
                var gaString = "<script>window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;ga('create', '" 
				+ TrackingCode 
				+ "', 'auto');ga('send', 'pageview');</script><script async src='https://www.google-analytics.com/analytics.js'></script>"; output.PostContent.AppendHtml(gaString);
            }
            

            return Task.CompletedTask;
        }
    }
}

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
	// ... ... ...

	services.AddSingleton<ITagHelperComponent>(new GoogleAnalyticsTagHelperComponent("UA-654321-99"));
}

Now Google analytics will be injected to every page body by GoogleAnalyticsTagHelperComponent

TagHelper component for a tag other than head and body

To create TagHelperComponent for a tag other than head and body, create a TagHelper that derives from TagHelperComponentTagHelper (just like aspnet core team did for (HeadTagHelper & BodyTagHelper))

Example: targeting footer
TagHelperComponentTagHelpers/FooterTagHelper.cs

using System.ComponentModel;
using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Logging;

namespace WebAppTHC.TagHelperComponentTagHelpers
{
    [HtmlTargetElement("footer")]
    [EditorBrowsable(EditorBrowsableState.Never)]
    public class FooterTagHelper : TagHelperComponentTagHelper
    {
        public FooterTagHelper(ITagHelperComponentManager manager, ILoggerFactory loggerFactory): base(manager, loggerFactory)
        {
        }
    }
}

TagHelperComponents/FooterTagHelperComponent.cs

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.TagHelpers;

namespace WebAppTHC.TagHelperComponents
{
    public class FooterTagHelperComponent : TagHelperComponent
    {
        public string CopyrightNotice { get; set; }

        public FooterTagHelperComponent(string copyrightNotice)
        {
            CopyrightNotice = copyrightNotice;
        }

        public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
        {
            if (string.Equals(context.TagName, "footer", StringComparison.OrdinalIgnoreCase))
            {
                output.PostContent.AppendHtml("<br/>" + CopyrightNotice);
            }
			
            return Task.CompletedTask;
        }
    }
}

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
	// ... ... ...

	services.AddSingleton<ITagHelperComponent>(new FooterTagHelperComponent("<i>Copyright Foo 2019</i>"));

	// ... ... ...
}

_ViewImports.cshtml

@using WebAppTHC
@using WebAppTHC.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper WebAppTHC.TagHelperComponentTagHelpers.*, WebAppTHC  

Built-in Tag Helpers

https://docs.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms

Community developed Tag Helpers