# ASP.NET Core: Tag Helpers

Tag Helpers are a feature in [ASP.NET](http://ASP.NET) Core that enable server-side code to participate in creating and rendering HTML elements in Razor files. They transform HTML-like elements into robust, dynamic content, bridging the gap between HTML designers and C# developers.

## 1\. Architecture & Lifecycle

At its core, a Tag Helper is a class that implements the `ITagHelper` interface. However, most developers inherit from the `TagHelper` base class to simplify the implementation.

### The Processing Pipeline

When Razor parses a view, it looks for elements that match registered Tag Helpers. If a match is found, the Razor engine invokes the helper's `Process` or `ProcessAsync` method.

### Key Components

#### A. `TagHelperContext`

This object contains information about the current execution environment.

* `AllAttributes`: A read-only collection of all attributes on the HTML tag.
    
* `Items`: A dictionary (`IDictionary<object, object>`) used to coordinate between parent and child Tag Helpers.
    
* `UniqueId`: A unique identifier for the element being processed.
    

#### B. `TagHelperOutput`

This object represents the output being generated. You manipulate this object to change the tag, attributes, or content.

* `TagName`: The HTML tag name (e.g., "div", "a"). Set this to `null` to strip the tag but keep the content.
    
* `Attributes`: The collection of attributes to be rendered. You can add, remove, or modify attributes here.
    
* `Content`: The content inside the tag.
    
* `PreElement` / `PostElement`: Content to be injected *outside* the tag (before/after).
    

---

## 2\. Authoring Custom Tag Helpers

### Phase 1: Targeting Elements

Use the `[HtmlTargetElement]` attribute to define when your Tag Helper should run.

| **Target Strategy** | **Syntax** | **Description** |
| --- | --- | --- |
| **By Tag Name** | `[HtmlTargetElement("bold-text")]` | Runs on `<bold-text>` elements. |
| **By Attribute** | `[HtmlTargetElement(Attributes = "make-bold")]` | Runs on any tag with the `make-bold` attribute. |
| **By Parent** | `[HtmlTargetElement("li", ParentTag = "ul")]` | Runs on `<li>` only if inside a `<ul>`. |

### Phase 2: Binding Properties

Public properties on your Tag Helper class are automatically bound to matching attributes on the HTML element.

```csharp
public class EmailTagHelper : TagHelper
{
    // Matches <email address="..."></email>
    // The "Address" suffix is optional in the HTML attribute name
    public string Address { get; set; } 

    // Explicitly mapping a C# property to a different HTML attribute name
    [HtmlAttributeName("display-name")]
    public string DisplayName { get; set; }
}
```

### Phase 3: The Process Method

This is where the logic lives. You typically override `Process` (synchronous) or `ProcessAsync` (asynchronous).

#### Example: A Simple Alert Tag Helper

**Scenario:** Create a helper that transforms `<alert type="danger">Error!</alert>` into a Bootstrap alert div.

```csharp
[HtmlTargetElement("alert")]
public class AlertTagHelper : TagHelper
{
    public string Type { get; set; } = "info"; // Default to info

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        // 1. Change the tag name from <alert> to <div>
        output.TagName = "div";

        // 2. Add Bootstrap classes
        output.Attributes.SetAttribute("class", $"alert alert-{Type}");

        // 3. Accessibility best practice
        output.Attributes.SetAttribute("role", "alert");
    }
}
```

---

## 3\. Advanced Scenarios

### A. Asynchronous Content & Child Content

If your Tag Helper needs to read the content inside it (the child content) or perform async DB calls, use `ProcessAsync`.

```csharp
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
    // Retrieve the content inside the tag
    var childContent = await output.GetChildContentAsync();
    var contentText = childContent.GetContent();

    // Example: Append a timestamp to the content
    output.Content.SetHtmlContent(contentText + $" <small>{DateTime.Now}</small>");
}
```

### B. Dependency Injection in Tag Helpers

Tag Helpers support constructor injection. This is powerful for accessing services like `IHtmlGenerator`, `IUrlHelperFactory`, or your own domain services.

```csharp
public class UserCardTagHelper : TagHelper
{
    private readonly IUserRepository _userRepo;

    public UserCardTagHelper(IUserRepository userRepo)
    {
        _userRepo = userRepo;
    }

    public int UserId { get; set; }

    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        var user = await _userRepo.GetByIdAsync(UserId);
        output.TagName = "div";
        output.Content.SetContent($"User: {user.Name}");
    }
}
```

### C. Coordination (Parent-Child Communication)

Tag Helpers can communicate using the `context.Items` dictionary. This is useful for components like Tab Groups or Lists.

**Parent (TabGroup):**

```csharp
public class TabGroupTagHelper : TagHelper
{
    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        context.Items["TabContext"] = new List<string>(); // Initialize shared state
        
        await output.GetChildContentAsync(); // Execute children to populate state
        
        // Render the final structure based on data collected from children
    }
}
```

**Child (TabItem):**

```csharp
public class TabItemTagHelper : TagHelper
{
    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        if (context.Items.ContainsKey("TabContext"))
        {
            var tabs = (List<string>)context.Items["TabContext"];
            tabs.Add("New Tab"); // Add data to parent
        }
    }
}
```

### D. Suppressing Output

Sometimes you want a Tag Helper to prevent the element from rendering entirely based on a condition (e.g., permissions).

```csharp
public override void Process(TagHelperContext context, TagHelperOutput output)
{
    if (!UserHasPermission)
    {
        output.SuppressOutput(); // Nothing will be rendered to the browser
    }
}
```

---

## 4\. Built-in Tag Helper Mastery

[ASP.NET](http://ASP.NET) 9 includes several powerful built-in helpers that you should prefer over manual HTML.

### The Cache Tag Helper (`<cache>`)

Allows you to cache the output of a section of a view to memory.

* **Vary-by-query**: `<cache vary-by-query="id">` (Different cache per query string ID)
    
* **Vary-by-user**: `<cache vary-by-user="true">` (Unique cache per logged-in user)
    
* **Expires-after**: `<cache expires-after="@TimeSpan.FromMinutes(10)">`
    

### The Environment Tag Helper (`<environment>`)

Conditionally renders content based on the hosting environment.

```xml
<environment include="Development">
    <script src="~/js/debug-helper.js"></script>
</environment>
<environment exclude="Development">
    <script src="https://cdn.example.com/app.min.js"></script>
</environment>
```

### The Script/Link Tag Helpers

* `asp-append-version="true"`: Appends a SHA256 hash of the file content as a query string (cache busting).
    
* `asp-fallback-src`: Provides a local fallback if the CDN link fails.
    

---

## 5\. View Imports (`_ViewImports.cshtml`)

To use your custom Tag Helpers, you must register them in `_ViewImports.cshtml`.

```csharp
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, MyProjectAssemblyName
```

* The first line enables built-in [ASP.NET](http://ASP.NET) Core helpers.
    
* The second line enables all helpers in your project's assembly.
