Author : MD TAREQ HASSAN
What is a Tag Helper
- Tag helpers are a new feature similar to HTML helpers which help to render HTML (available in ASP.NET Core 2.0 or later)
- Tag Helpers enable server-side code to participate in creating and rendering HTML elements in Razor files (Razor views and Razor pages)
- Tag Helpers are classes written in C# but are attached to HTML elements in order to run server-side code from Razor files
- Tag Helpers provide
- an HTML-friendly development experience
- a rich IntelliSense environment for creating HTML and Razor markup
- a way to make you more productive and able to produce more robust, reliable, and maintainable code using information only available on the server
- Tag Helpers reduce the explicit transitions between HTML and C# in Razor views
- Tag Helpers don’t replace HTML Helpers and there’s not a Tag Helper for each HTML Helper
- In order to use Tag Helpers, we need to install a NuGet library
Microsoft.AspNet.Mvc.TagHelpers
and also add an addTagHelper directive to the view or views that use these tag helpers - Links:
Ways of using TagHelpers
- HTML attribute
- HTML tag/element
- as a child element inside HTML element
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
@addTagHelper
directive makes Tag Helpers available to the Razor files@addTagHelper FQN, Namespace
(FQN = full qualified name i.e. FooNamespace.TagHelpers.BarTagHelper)- use
*
for all tag helpers
Views/_ViewImports.cshtml
or Pages/_ViewImports.cshtml
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, FooNamespace
See: Managing Tag Helper scope
Creating a custom Tag Helper
- tag helper naming convention
- tag name: kebab-case (to explicitly mention tag name, use
[HtmlTargetElement("tag-name")]
) - tag helper class name: CamelCase (TagName)
- tag helper class name suffix: ‘
TagHelper
’ i.e.FooBarTagHelper
(suffix is not required, but it’s considered a best practice convention)
- tag name: kebab-case (to explicitly mention tag name, use
- tag helper attribute naming convention
- tag attribute: kebab-case (baz-bax)
- property inside tag helper calss: CamelCase (BazBax)
- all properties in tag helper class are mapped to attribute of tag helper (to exclude, use:
[HtmlAttributeNotBound]
)
- A tag helper is any class that implements the
ITagHelper
interface - Create a folder to hold the Tag Helpers called TagHelpers. The TagHelpers folder is not required, but it’s a reasonable convention
- Links:
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
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>
ModelExpression
- Model is exposed to view by
@model
directive - tag helper can use
Model.Prop
uisng razor syntax@Model.Prop
i.e.<foo modle-prop="@Model.Prop"> </foo>
- ModelExpression => use just Prop inside tag helper i.e.
<foo modle-prop="Prop"> </foo>
- ModelExpression would be type of property in tag helpers (ModelExpression type property will be mapped to attribute and that attribute can use model property name only)
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>
ViewContext
- ViewContext => access more info about the request
- Get additional info from
ViewContext
if needed
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
- parent:
<tab> </tab>
- child:
<tab-item> </tab-item>
- data will be passed from tab to tab-item
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
- Constructor injection
- To register to DI container, use:
services.AddTransient<IFoo, Foo>();
(inStartup.cs
)
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
- HtmlTargetElementAttribute Class
- Multiple
[HtmlTargetElement]
attribute can be applied to same tag helper class [HtmlTargetElement(Attributes = "")]
- Attributes = comma separetd list of attributes i.e.
[HtmlTargetElement(Attributes = "foo, bar, baz")]
- bot “…” and “[…]” are valid i.e
[HtmlTargetElement(Attributes = "foo")]
&[HtmlTargetElement(Attributes = "[foo]")]
- pattern can be used for Attributes and only
=
,^
(start) &$
(end) are valid in pattern - pattern in Attributes example:
[HtmlTargetElement(Attributes = "[foo ^= 'a'], [bar $= 'z']")]
- Attributes = comma separetd list of attributes i.e.
Restricting tag
[RestrictChildren("tab-item")]
[HtmlTargetElement("tab")]
public class TabTagHelper : TagHelper
{
// ... ... ...
}
[HtmlTargetElement("tab-item", ParentTag = "tab")]
public class TabItemTagHelper : TagHelper
{
// ... ... ...
}
[HtmlTargetElement("tab", TagStructure = TagStructure.NormalOrSelfClosing)]
public class TabTagHelper : TagHelper
{
// ... ... ...
}
[HtmlTargetElement("tab", TagMode = TagMode.SelfClosing)]
public class TabTagHelper : TagHelper
{
// ... ... ...
}
Tag Helper Components
- A Tag Helper Component is a Tag Helper that allows you to conditionally modify or add HTML elements from server-side code (aspnet 2.0+)
- ASP.NET Core includes two built-in Tag Helper Components:
head
andbody
- Two common use cases of Tag Helper Components include:
- Injecting a
<link>
into the<head>
- Injecting a
<script>
into the<body>
- Injecting a
- Links:
Register TagHelperComponent
- A Tag Helper Component must be added to the app’s Tag Helper Components collection
- Unless registered,
<link>
or<script>
can not be injected
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
- All
TagHelperComponent
you create will be executed forTagHelper
that derives fromTagHelperComponentTagHelper
and imported by_ViewImports.cshtml
- There are two built-in TagHelpers (
HeadTagHelper
&BodyTagHelper
) that derives fromTagHelperComponentTagHelper
in...Mvc.Razor.TagHelpers
namespace- Any
TagHelperComponent
you create will be executed forhead
andbody
since...Mvc.Razor.TagHelpers
is imported by_ViewImports.cshtml
and built-inHeadTagHelper
&BodyTagHelper
are being available - Use if to filter for a specific tag
- Any
To create a custom TagHelperComponent
- Create a public class deriving from TagHelperComponentTagHelper.
- Apply an
[HtmlTargetElement]
attribute to the class. Specify the name of the target HTML element. - Optional: Apply an
[EditorBrowsable(EditorBrowsableState.Never)]
attribute to the class to suppress the type’s display in IntelliSense.
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
))
HeadTagHelper.cs
BodyTagHelper.cs
- HeadTagHelper Class
- BodyTagHelper Class
- Make sure that your
TagHelperComponentTagHelper
is imported by_ViewImports.cshtml
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/tag-helpers/built-in
- Tag helpers github repo: https://github.com/dotnet/aspnetcore/tree/master/src/Mvc/Mvc.TagHelpers/src
- https://www.dotnettricks.com/learn/aspnetcore/aspnet-core-tag-helpers
Form related Tag Helpers
https://docs.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms