ASP.NET Core MVC Mastery

Custom HTML Helpers

1 Views Updated 5/4/2026

Custom HTML Helpers — Extending Razor with Reusable UI Logic

What if you need a button, badge, or alert component that appears across dozens of views with consistent styling? Instead of copy-pasting HTML, you create a Custom HTML Helper — a reusable C# method that generates HTML markup and is callable from any Razor view using @Html.YourMethod().

1. WHAT Are Custom HTML Helpers?

Custom HTML Helpers are static extension methods on the IHtmlHelper interface. They encapsulate repetitive HTML generation logic into reusable, testable C# methods. When called in a Razor view, they return IHtmlContent — properly encoded HTML that the Razor engine safely renders.

The Building Blocks
  • IHtmlHelper — The interface your extension method extends
  • TagBuilder — The class that generates well-formed, XSS-safe HTML tags
  • IHtmlContent — The return type that Razor renders without double-encoding
  • _ViewImports.cshtml — Where you register your helper's namespace

2. HOW to Create a Custom HTML Helper

Step-by-Step: Building a Reusable Status Badge Helper

// Helpers/HtmlHelperExtensions.cs
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;

namespace MyApp.Helpers
{
    public static class HtmlHelperExtensions
    {
        /// <summary>
        /// Renders a Bootstrap-styled status badge.
        /// Usage: @Html.StatusBadge("Active", "success")
        /// </summary>
        public static IHtmlContent StatusBadge(this IHtmlHelper helper,
            string text, string variant = "primary")
        {
            // TagBuilder ensures proper HTML encoding (XSS prevention)
            var span = new TagBuilder("span");
            span.AddCssClass($"badge bg-{variant} rounded-pill px-3 py-2");
            span.InnerHtml.Append(text);

            return span;
        }

        /// <summary>
        /// Renders a delete confirmation button with data attributes.
        /// Usage: @Html.DeleteButton("/api/products/5", "Delete Product")
        /// </summary>
        public static IHtmlContent DeleteButton(this IHtmlHelper helper,
            string deleteUrl, string confirmMessage = "Are you sure?")
        {
            var button = new TagBuilder("button");
            button.AddCssClass("btn btn-danger btn-sm");
            button.Attributes.Add("data-delete-url", deleteUrl);
            button.Attributes.Add("data-confirm", confirmMessage);
            button.Attributes.Add("onclick", "confirmDelete(this)");
            button.InnerHtml.AppendHtml("Delete");

            return button;
        }

        /// <summary>
        /// Renders a formatted price with currency symbol.
        /// Usage: @Html.FormatPrice(49.99m, "USD")
        /// </summary>
        public static IHtmlContent FormatPrice(this IHtmlHelper helper,
            decimal price, string currency = "USD")
        {
            var symbol = currency switch
            {
                "USD" => "$", "EUR" => "€", "GBP" => "£",
                "INR" => "₹", "JPY" => "¥", _ => currency
            };

            var span = new TagBuilder("span");
            span.AddCssClass("fw-bold text-success");
            span.InnerHtml.AppendHtml($"{symbol}{price:N2}");

            return span;
        }
    }
}

Register the Namespace

<!-- Views/_ViewImports.cshtml -->
@using MyApp.Helpers     <!-- Makes @Html.StatusBadge(), etc. available in all views -->

Use in Razor Views

<!-- Clean, reusable usage across any view -->
<h3>Order Status: @Html.StatusBadge(order.Status, order.IsActive ? "success" : "danger")</h3>

<td>@Html.FormatPrice(product.Price, "USD")</td>
<td>@Html.DeleteButton("/api/products/" + product.Id, "Delete " + product.Name + "?")</td>

3. Advanced: Helper Returning Complex HTML

/// Renders a Bootstrap card with header, body, and footer
public static IHtmlContent InfoCard(this IHtmlHelper helper,
    string title, string body, string footer = null, string icon = "fas fa-info-circle")
{
    var card = new TagBuilder("div");
    card.AddCssClass("card shadow-sm mb-4");

    // Card Header
    var header = new TagBuilder("div");
    header.AddCssClass("card-header bg-primary text-white");
    header.InnerHtml.AppendHtml($"{title}");
    card.InnerHtml.AppendHtml(header);

    // Card Body
    var cardBody = new TagBuilder("div");
    cardBody.AddCssClass("card-body");
    cardBody.InnerHtml.AppendHtml($"

{body}

"); card.InnerHtml.AppendHtml(cardBody); // Optional Footer if (!string.IsNullOrEmpty(footer)) { var cardFooter = new TagBuilder("div"); cardFooter.AddCssClass("card-footer text-muted small"); cardFooter.InnerHtml.Append(footer); card.InnerHtml.AppendHtml(cardFooter); } return card; }

4. When to Use HTML Helpers vs Tag Helpers vs View Components

FeatureCustom HTML HelpersTag HelpersView Components
Syntax@Html.Method()<tag asp-attr="">@await Component.InvokeAsync()
ReturnsIHtmlContentModifies TagHelperOutputRazor View (.cshtml)
DI Support❌ Static methods✅ Constructor injection✅ Full DI
Best ForSmall, stateless UI snippetsEnhancing HTML elementsComplex components with data access
ComplexityLowMediumHigh

5. Best Practices

  • Always use TagBuilder — never concatenate raw HTML strings (XSS risk)
  • Return IHtmlContent, not string — prevents double-encoding
  • Keep helpers stateless — no database calls, no service injection
  • Use View Components when you need async data access or complex business logic
  • Register namespace in _ViewImports.cshtml for global availability

6. Interview Mastery

Q: "How would you decide between creating a Custom HTML Helper vs a Tag Helper vs a View Component?"

Answer: "The decision comes down to complexity and data needs. For simple, stateless UI elements like badges, icons, or formatted prices — I use HTML Helpers. They're just static methods, fast to write, and have zero overhead. When I need to enhance existing HTML elements with server-side behavior — like adding asp-for binding — I use Tag Helpers because they feel natural in HTML. When the component needs its own data (e.g., a 'Recent Posts' sidebar that queries the database) — I use View Components, since they support full DI and have their own controller-like InvokeAsync method with a dedicated Razor view."

ASP.NET Core MVC Mastery
1. Core Framework
Introduction to ASP.NET Core MVC
MODULE 1: INTRODUCTION & ENVIRONMENT SETUP
Microsoft Web Stack Overview Evolution of ASP.NET Environment Setup
2. View Engine
Layouts & Partial Views in Razor
MODULE 2: .NET CORE FUNDAMENTALS
Core Concepts Project Structure Startup Flow Middleware Pipeline
MODULE 3: ASP.NET CORE BASICS
Creating Project CLI Commands wwwroot & Static Files
MODULE 4: MVC FUNDAMENTALS
MVC Architecture Dependency Injection (DI) Service Lifetimes
MODULE 5: DATA PASSING TECHNIQUES
ViewData vs ViewBag TempData ViewModel Pattern
MODULE 6: ROUTING
Conventional vs Attribute Routing Custom Constraints
MODULE 7: VIEWS & UI
Razor View Engine Layouts & Sections View Components
MODULE 8: ACTION RESULTS
ViewResult JsonResult RedirectResult
MODULE 9: HTML HELPERS
Form Helpers Custom HTML Helpers
MODULE 10: TAG HELPERS
Built-in Tag Helpers Custom Tag Helpers
MODULE 11: MODEL BINDING
FromQuery vs FromRoute Complex Binding
MODULE 12: VALIDATION
Data Annotations Remote Validation Fluent Validation
MODULE 13: STATE MANAGEMENT
Cookies & Sessions TempData
MODULE 14: FILTERS & SECURITY
Action Filters Authorize Filters Anti-forgery
MODULE 15: ENTITY FRAMEWORK CORE (DEEP DIVE)
DbContext Migrations LINQ Relationships
MODULE 16: DESIGN PATTERNS
Repository Pattern Unit of Work Clean Architecture
MODULE 17: FILE HANDLING
File Upload/Download PDF/Excel Generation
MODULE 18: ADVANCED ASP.NET CORE
Request Lifecycle Bundling & Minification Deployment
MODULE 19: PERFORMANCE & BEST PRACTICES
Caching Strategies Async Programming Secure Coding
MODULE 20: RAZOR PAGES (BONUS)
Razor Pages vs MVC
MODULE 21: REAL-WORLD PROJECTS (🔥 MUST DO)
E-Commerce Web Application Employee Management System