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:
// 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:
DRY (Don't Repeat Yourself): You have a specific UI widget (like a user card or pagination) used in 10 different views.
Readability: Your view is full of ugly C#
switchstatements or string concatenation.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.
<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.
<td>
<status-badge value="@Model.Status"></status-badge>
</td>
The Implementation
Here is how you would build that StatusBadgeTagHelper.
[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.
Comments
Join the discussionNo comments yet. Be the first to comment.