# ASP.NET Core: Tag Helper - Example

### 1\. How Built-in Tag Helpers Are Built (Inside the "Black Box")

It might feel like `asp-for` or `asp-controller` are hard-coded magic inside the Razor engine, but they are not. They are just standard C# classes written by Microsoft, exactly like the ones you can write.

They exist in the assembly `Microsoft.AspNetCore.Mvc.TagHelpers`.

#### Case Study: The `asp-controller` / `asp-action` (Anchor Tag Helper)

This helper targets the standard `<a>` tag. When you use `asp-controller`, the helper calculates the correct route and injects a standard `href` attribute.

Here is a simplified, conceptual view of how Microsoft wrote the `AnchorTagHelper`:

```csharp
// 1. Target standard <a> tags that have an "asp-action" attribute
[HtmlTargetElement("a", Attributes = "asp-action")]
[HtmlTargetElement("a", Attributes = "asp-controller")]
public class AnchorTagHelper : TagHelper
{
    private readonly IHtmlGenerator _generator;

    // Dependency Injection typically used for URL generation logic
    public AnchorTagHelper(IHtmlGenerator generator) 
    {
        _generator = generator;
    }

    // 2. Map the "asp-action" attribute to this C# property
    [HtmlAttributeName("asp-action")]
    public string Action { get; set; }

    [HtmlAttributeName("asp-controller")]
    public string Controller { get; set; }

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        // 3. Check if the user already manually added an href. If so, don't overwrite it.
        if (output.Attributes.ContainsName("href")) return;

        // 4. Use the generator to calculate the URL (e.g., "/Home/Index")
        var href = _generator.GenerateActionLink(Controller, Action);

        // 5. Modify the output to include the standard HTML href
        output.Attributes.SetAttribute("href", href);
    }
}
```

**Takeaway:** There is no magic. `asp-for` works the same way: it takes a `ModelExpression` property, looks at your model's metadata (Is it required? What is the name?), and generates `id`, `name`, and `value` attributes for you.

---

### 2\. Why Use Custom Tag Helpers? (The Necessity)

You use custom Tag Helpers to **hide complexity**.

If you find yourself writing the same complex HTML structure or the same `if/else` logic in multiple views, you should move that logic into a Tag Helper.

**The "Necessity" Checklist:**

1. **DRY (Don't Repeat Yourself):** You have a specific UI widget (like a user card or pagination) used in 10 different views.
    
2. **Readability:** Your view is full of ugly C# `switch` statements or string concatenation.
    
3. **Consistency:** If you change the design of your "Warning Button" in the Tag Helper, it updates everywhere instantly.
    

---

### 3\. Real-Life Example: The "Status Badge"

**The Scenario:** You are building an Order Management System. An order can be **"Pending"** (Yellow), **"Approved"** (Green), or **"Rejected"** (Red).

#### The "Bad" Way (Without Custom Tag Helper)

You have to write this logic in every single view (Index, Details, Delete, Dashboard). It is messy and error-prone.

```xml
<td>
    @if (Model.Status == "Approved")
    {
        <span class="badge bg-success">Approved <i class="fa fa-check"></i></span>
    }
    else if (Model.Status == "Rejected")
    {
        <span class="badge bg-danger">Rejected <i class="fa fa-times"></i></span>
    }
    else
    {
        <span class="badge bg-warning">Pending <i class="fa fa-clock"></i></span>
    }
</td>
```

#### The "Good" Way (With Custom Tag Helper)

You create a clean, readable tag.

```xml
<td>
    <status-badge value="@Model.Status"></status-badge>
</td>
```

#### The Implementation

Here is how you would build that `StatusBadgeTagHelper`.

```csharp
[HtmlTargetElement("status-badge")]
public class StatusBadgeTagHelper : TagHelper
{
    public string Value { get; set; } // Passed from @Model.Status

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        output.TagName = "span"; // Render as a <span>
        output.Attributes.SetAttribute("class", "badge"); // Base class

        var sb = new StringBuilder();

        // Encapsulate the logic here
        switch (Value)
        {
            case "Approved":
                output.Attributes.AppendCssClass("bg-success"); // Helper extension
                sb.Append($"{Value} <i class='fa fa-check'></i>");
                break;
            case "Rejected":
                output.Attributes.AppendCssClass("bg-danger");
                sb.Append($"{Value} <i class='fa fa-times'></i>");
                break;
            default:
                output.Attributes.AppendCssClass("bg-warning");
                sb.Append($"{Value} <i class='fa fa-clock'></i>");
                break;
        }

        output.Content.SetHtmlContent(sb.ToString());
    }
}
```

**The Benefit:** If your designer says, *"We need to change 'Approved' to Blue instead of Green,"* you change it in **one** C# file, and all 50 views in your application update instantly. That is the necessity.
