Skip to content

Commit

Permalink
Add Tag Helper Components doc (#7922)
Browse files Browse the repository at this point in the history
  • Loading branch information
Rick-Anderson authored and scottaddie committed Sep 18, 2018
1 parent 298133d commit 65eaf9d
Show file tree
Hide file tree
Showing 48 changed files with 20,650 additions and 0 deletions.
156 changes: 156 additions & 0 deletions aspnetcore/mvc/views/tag-helpers/th-components.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
---
title: Tag Helper Components in ASP.NET Core
author: scottaddie
description: Learn what Tag Helper Components are and how to use them in ASP.NET Core.
monikerRange: '>= aspnetcore-2.0'
ms.author: scaddie
ms.date: 09/18/2018
uid: mvc/views/tag-helpers/th-components
---
# Tag Helper Components in ASP.NET Core

By [Scott Addie](https://twitter.com/Scott_Addie) and [Fiyaz Bin Hasan](https://github.com/fiyazbinhasan)

A Tag Helper Component is a Tag Helper that allows you to conditionally modify or add HTML elements from server-side code. This feature is available in ASP.NET Core 2.0 or later.

ASP.NET Core includes two built-in Tag Helper Components: `head` and `body`. They're located in the `Microsoft.AspNetCore.Mvc.Razor.TagHelpers` namespace and can be used in both MVC and Razor Pages. Tag Helper Components don't require registration with the app in *_ViewImports.cshtml*.

[View or download sample code](https://github.com/aspnet/Docs/tree/master/aspnetcore/mvc/views/tag-helpers/th-components/samples) ([how to download](xref:tutorials/index#how-to-download-a-sample))

## Use cases

Two common use cases of Tag Helper Components include:

1. [Injecting a `<link>` into the `<head>`.](#inject-into-html-head-element)
1. [Injecting a `<script>` into the `<body>`.](#inject-into-html-body-element)

The following sections describe these use cases.

### Inject into HTML head element

Inside the HTML `<head>` element, CSS files are commonly imported with the HTML `<link>` element. The following code injects a `<link>` element into the `<head>` element using the `head` Tag Helper Component:

[!code-csharp[](th-components/samples/RazorPagesSample/TagHelpers/AddressStyleTagHelperComponent.cs)]

In the preceding code:

* `AddressStyleTagHelperComponent` implements `TagHelperComponent`. The abstraction:
* Allows initialization of the class with a `TagHelperContext`.
* Enables the use of Tag Helper Components to add or modify HTML elements.
* The `Order` property defines the order in which the Components are rendered. `Order` is necessary when there are multiple usages of Tag Helper Components in an app.
* `ProcessAsync` compares the execution context's `TagName` property value to `head`. If the comparison evaluates to true, the content of the `_style` field is injected into the HTML `<head>` element.

### Inject into HTML body element

The `body` Tag Helper Component can inject a `<script>` element into the `<body>` element. The following code demonstrates this technique:

[!code-csharp[](th-components/samples/RazorPagesSample/TagHelpers/AddressScriptTagHelperComponent.cs)]

A separate HTML file is used to store the `<script>` element. The HTML file makes the code cleaner and more maintainable. The preceding code reads the contents of *TagHelpers/Templates/AddressToolTipScript.html* and appends it with the Tag Helper output. The *AddressToolTipScript.html* file includes the following markup:

[!code-html[](th-components/samples/RazorPagesSample/TagHelpers/Templates/AddressToolTipScript.html)]

The preceding code binds a [Bootstrap tooltip widget](https://getbootstrap.com/docs/3.3/javascript/#tooltips) to any `<address>` element that includes a `printable` attribute. The effect is visible when a mouse pointer hovers over the element.

## Register a Component

A Tag Helper Component must be added to the app's Tag Helper Components collection. There are three ways to add to the collection:

1. [Registration via services container](#registration-via-services-container)
1. [Registration via Razor file](#registration-via-razor-file)
1. [Registration via Page Model or controller](#registration-via-page-model-or-controller)

### Registration via services container

If the Tag Helper Component class isn't managed with `ITagHelperComponentManager`, it must be registered with the [dependency injection (DI)](xref:fundamentals/dependency-injection) system. The following `Startup.ConfigureServices` code registers the `AddressStyleTagHelperComponent` and `AddressScriptTagHelperComponent` classes with a [transient lifetime](xref:fundamentals/dependency-injection#lifetime-and-registration-options):

[!code-csharp[](th-components/samples/RazorPagesSample/Startup.cs?name=snippet_ConfigureServices&highlight=12-15)]

### Registration via Razor file

If the Tag Helper Component isn't registered with DI, it can be registered from a Razor Pages page or an MVC view. This technique is used for controlling the injected markup and the component execution order from a Razor file.

`ITagHelperComponentManager` is used to add Tag Helper Components or remove them from the app. The following code demonstrates this technique with `AddressTagHelperComponent`:

[!code-cshtml[](th-components/samples/RazorPagesSample/Pages/Contact.cshtml?name=snippet_ITagHelperComponentManager)]

In the preceding code:

* The `@inject` directive provides an instance of `ITagHelperComponentManager`. The instance is assigned to a variable named `manager` for access downstream in the Razor file.
* An instance of `AddressTagHelperComponent` is added to the app's Tag Helper Components collection.

`AddressTagHelperComponent` is modified to accommodate a constructor that accepts the `markup` and `order` parameters:

[!code-csharp[](th-components/samples/RazorPagesSample/TagHelpers/AddressTagHelperComponent.cs?name=snippet_Constructor)]

The provided `markup` parameter is used in `ProcessAsync` as follows:

[!code-csharp[](th-components/samples/RazorPagesSample/TagHelpers/AddressTagHelperComponent.cs?name=snippet_ProcessAsync&highlight=10-11)]

### Registration via Page Model or controller

If the Tag Helper Component isn't registered with DI, it can be registered from a Razor Pages page model or an MVC controller. This technique is useful for separating C# logic from Razor files.

Constructor injection is used to access an instance of `ITagHelperComponentManager`. The Tag Helper Component is added to the instance's Tag Helper Components collection. The following Razor Pages page model demonstrates this technique with `AddressTagHelperComponent`:

[!code-csharp[](th-components/samples/RazorPagesSample/Pages/Index.cshtml.cs?name=snippet_IndexModelClass)]

In the preceding code:

* Constructor injection is used to access an instance of `ITagHelperComponentManager`.
* An instance of `AddressTagHelperComponent` is added to the app's Tag Helper Components collection.

## Create a Component

To create a custom Tag Helper Component:

* 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.

The following code creates a custom Tag Helper Component that targets the `<address>` HTML element:

[!code-csharp[](th-components/samples/RazorPagesSample/TagHelpers/AddressTagHelperComponentTagHelper.cs)]

Use the custom `address` Tag Helper Component to inject HTML markup as follows:

```csharp
public class AddressTagHelperComponent : TagHelperComponent
{
private readonly string _printableButton =
"<button type='button' class='btn btn-info' onclick=\"window.open("
"'https://binged.it/2AXRRYw')\">" +
"<span class='glyphicon glyphicon-road' aria-hidden='true'></span>" +
"</button>";

public override int Order => 3;

public override async Task ProcessAsync(TagHelperContext context,
TagHelperOutput output)
{
if (string.Equals(context.TagName, "address",
StringComparison.OrdinalIgnoreCase) &&
output.Attributes.ContainsName("printable"))
{
var content = await output.GetChildContentAsync();
output.Content.SetHtmlContent(
$"<div>{content.GetContent()}</div>{_printableButton}");
}
}
}
```

The preceding `ProcessAsync` method injects the HTML provided to `SetHtmlContent` into the matching `<address>` element. The injection occurs when:

* The execution context's `TagName` property value equals `address`.
* The corresponding `<address>` element has a `printable` attribute.

For example, the `if` statement evaluates to true when processing the following `<address>` element:

[!code-cshtml[](th-components/samples/RazorPagesSample/Pages/Contact.cshtml?name=snippet_AddressPrintable)]

## Additional resources

* <xref:fundamentals/dependency-injection>
* <xref:mvc/views/dependency-injection>
* <xref:mvc/views/tag-helpers/builtin-th/Index>
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
@page
@model ContactModel
<!-- <snippet_ITagHelperComponentManager> -->
@using RazorPagesSample.TagHelpers;
@using Microsoft.AspNetCore.Mvc.Razor.TagHelpers;
@inject ITagHelperComponentManager manager;

@{
string markup;

if (Model.IsWeekend)
{
markup = "<em class='text-warning'>Office closed today!</em>";
}
else
{
markup = "<em class='text-info'>Office open today!</em>";
}

manager.Components.Add(new AddressTagHelperComponent(markup, 1));
}
<!-- </snippet_ITagHelperComponentManager> -->

<h2>Tag Helper Component registration via Razor</h2>

<!-- <snippet_AddressPrintable> -->
<address printable>
One Microsoft Way<br />
Redmond, WA 98052-6399<br />
<abbr title="Phone">P:</abbr>
425.555.0100
</address>
<!-- </snippet_AddressPrintable> -->

<address>
<strong>Support:</strong> <a href="mailto:[email protected]">Support@example.com</a><br />
<strong>Marketing:</strong> <a href="mailto:[email protected]">Marketing@example.com</a>
</address>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace RazorPagesSample.Pages
{
public class ContactModel : PageModel
{
public string Message { get; set; }

public bool IsWeekend
{
get
{
var dayOfWeek = DateTime.Now.DayOfWeek;

return dayOfWeek == DayOfWeek.Saturday ||
dayOfWeek == DayOfWeek.Sunday;
}
}

public void OnGet()
{
Message = "Your contact page.";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
@page
@model ErrorModel
@{
ViewData["Title"] = "Error";
}

<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>

@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}

<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>Development environment should not be enabled in deployed applications</strong>, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>, and restarting the application.
</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace RazorPagesSample.Pages
{
public class ErrorModel : PageModel
{
public string RequestId { get; set; }

public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@page
@model IndexModel

<h2>Tag Helper Component registration via Razor Pages Page Model</h2>

<address printable>
One Microsoft Way<br />
Redmond, WA 98052-6399<br />
<abbr title="Phone">P:</abbr>
425.555.0100
</address>

<address>
<strong>Support:</strong> <a href="mailto:[email protected]">Support@example.com</a><br />
<strong>Marketing:</strong> <a href="mailto:[email protected]">Marketing@example.com</a>
</address>
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
namespace RazorPagesSample.Pages
{
#region snippet_IndexModelClass
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 bool IsWeekend
{
get
{
var dayOfWeek = DateTime.Now.DayOfWeek;

return dayOfWeek == DayOfWeek.Saturday ||
dayOfWeek == DayOfWeek.Sunday;
}
}

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

public void OnGet()
{
string markup;

if (IsWeekend)
{
markup = "<em class='text-warning'>Office closed today!</em>";
}
else
{
markup = "<em class='text-info'>Office open today!</em>";
}

_tagHelperComponentManager.Components.Add(
new AddressTagHelperComponent(markup, 1));
}
}
#endregion
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@page
@model PrivacyModel
@{
ViewData["Title"] = "Privacy Policy";
}
<h2>@ViewData["Title"]</h2>

<p>Use this page to detail your site's privacy policy.</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace RazorPagesSample.Pages
{
public class PrivacyModel : PageModel
{
public void OnGet()
{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
@using Microsoft.AspNetCore.Http.Features

@{
var consentFeature = Context.Features.Get<ITrackingConsentFeature>();
var showBanner = !consentFeature?.CanTrack ?? false;
var cookieString = consentFeature?.CreateConsentCookie();
}

@if (showBanner)
{
<nav id="cookieConsent" class="navbar navbar-default navbar-fixed-top" role="alert">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#cookieConsent .navbar-collapse">
<span class="sr-only">Toggle cookie consent banner</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<span class="navbar-brand"><span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span></span>
</div>
<div class="collapse navbar-collapse">
<p class="navbar-text">
Use this space to summarize your privacy and cookie use policy.
</p>
<div class="navbar-right">
<a asp-page="/Privacy" class="btn btn-info navbar-btn">Learn More</a>
<button type="button" class="btn btn-default navbar-btn" data-cookie-string="@cookieString">Accept</button>
</div>
</div>
</div>
</nav>
<script>
(function () {
document.querySelector("#cookieConsent button[data-cookie-string]").addEventListener("click", function (el) {
document.cookie = el.target.dataset.cookieString;
document.querySelector("#cookieConsent").classList.add("hidden");
}, false);
})();
</script>
}
Loading

0 comments on commit 65eaf9d

Please sign in to comment.