Tag Helpers and HTML Helpers are server-side features in ASP.NET Core MVC used to generate dynamic HTML.
The main difference is syntax:
- Tag Helpers look like normal HTML.
- HTML Helpers use C# method calls.
Tag Helpers
Tag Helpers add server-side behavior to standard HTML elements using attributes beginning with asp-.
Example
<a asp-controller="Product"
asp-action="Details"
asp-route-id="10">
View Product
</a>
Generated HTML:
<a href="/Product/Details/10">View Product</a>
Form example
@model ProductViewModel
<form asp-controller="Product"
asp-action="Create"
method="post">
<label asp-for="Name"></label>
<input asp-for="Name" />
<span asp-validation-for="Name"></span>
<button type="submit">Save</button>
</form>
Tag Helpers commonly used in MVC include:
asp-controllerasp-actionasp-route-*asp-forasp-validation-forasp-validation-summaryasp-append-version
HTML Helpers
HTML Helpers are C# methods that return HTML markup.
Link example
@Html.ActionLink(
"View Product",
"Details",
"Product",
new { id = 10 },
null)
Generated HTML:
<a href="/Product/Details/10">View Product</a>
Form example
@model ProductViewModel
@using (Html.BeginForm("Create", "Product", FormMethod.Post))
{
@Html.LabelFor(model => model.Name)
@Html.TextBoxFor(model => model.Name)
@Html.ValidationMessageFor(model => model.Name)
<button type="submit">Save</button>
}
Key differences
| Tag Helpers | HTML Helpers |
|---|---|
| HTML-like syntax | C# method-call syntax |
Uses asp-* attributes |
Uses @Html methods |
| Easier for designers to understand | More familiar to C# developers |
| Keeps HTML structure readable | Can become difficult to read when nested |
| Works directly with HTML elements | Generates an entire HTML element |
| Extends or modifies existing elements | Returns an HTML string or IHtmlContent |
| Introduced with ASP.NET Core | Commonly used in older ASP.NET MVC and also supported in ASP.NET Core |
| Supports custom Tag Helpers | Supports custom HTML Helper methods |
Enabling Tag Helpers
Tag Helpers must normally be registered in _ViewImports.cshtml:
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
Without this line, attributes such as asp-controller, asp-action and asp-for will not be processed.
Custom Tag Helpers can be registered using the assembly name:
@addTagHelper *, MyApplication
Model binding example
Given this model:
public class ProductViewModel
{
public int ProductId { get; set; }
public string Name { get; set; } = string.Empty;
}
Tag Helper:
<input asp-for="Name" />
HTML Helper:
@Html.TextBoxFor(model => model.Name)
Both generate an input with the appropriate name, id and value:
<input type="text" id="Name" name="Name" value="..." />
The name attribute is important because model binding uses it when the form is submitted.
When to use each one
Use Tag Helpers when:
- Writing new ASP.NET Core MVC applications
- You want clean, HTML-like markup
- Working with forms, validation and links
- Frontend developers also work on the views
Use HTML Helpers when:
- Maintaining an older MVC project
- A required helper has no suitable Tag Helper
- You already have custom HTML Helpers
- C# method syntax is more suitable for a particular dynamic scenario
Library requirement
No additional NuGet library is normally required. Built-in Tag Helpers and HTML Helpers are included with ASP.NET Core MVC.
Interview answer
Tag Helpers use HTML elements and
asp-*attributes to generate server-side HTML, whereas HTML Helpers use C# methods such asHtml.TextBoxFor()andHtml.ActionLink(). Tag Helpers are generally preferred in modern ASP.NET Core MVC because their markup is cleaner and more natural to HTML developers.
A strongly typed view is a Razor view connected to a specific model class using the @model directive.
This allows the view to access model properties through Model with compile-time checking and IntelliSense.
Simple example
1. Create a ViewModel
public class ProductViewModel
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
}
2. Pass the model from the controller
public class ProductController : Controller
{
public IActionResult Details(int id)
{
var model = new ProductViewModel
{
Id = id,
Name = "Laptop",
Price = 75000
};
return View(model);
}
}
return View(model) passes the ProductViewModel object to the Razor view.
3. Declare the model type in the view
Views/Product/Details.cshtml
@model ProductViewModel
<h2>@Model.Name</h2>
<p>Product ID: @Model.Id</p>
<p>Price: ₹@Model.Price</p>
The @model directive specifies the model type:
@model ProductViewModel
The Model property contains the actual object received from the controller:
@Model.Name
Request flow
Browser Request
↓
Controller Action
↓
Creates ProductViewModel
↓
return View(model)
↓
Razor strongly typed view
↓
HTML response
Collection model
A strongly typed view can receive a collection:
Controller
public IActionResult Index()
{
var products = new List<ProductViewModel>
{
new() { Id = 1, Name = "Laptop", Price = 75000 },
new() { Id = 2, Name = "Mobile", Price = 30000 }
};
return View(products);
}
View
@model IEnumerable<ProductViewModel>
@foreach (var product in Model)
{
<h3>@product.Name</h3>
<p>₹@product.Price</p>
}
Strongly typed forms
Strongly typed views work with Tag Helpers to generate form fields.
@model ProductViewModel
<form asp-action="Create" method="post">
<label asp-for="Name"></label>
<input asp-for="Name" />
<span asp-validation-for="Name"></span>
<label asp-for="Price"></label>
<input asp-for="Price" />
<span asp-validation-for="Price"></span>
<button type="submit">Save</button>
</form>
When submitted, input names match the model properties, allowing MVC model binding to create a ProductViewModel.
[HttpPost]
public IActionResult Create(ProductViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
// Save data
return RedirectToAction(nameof(Index));
}
Model-type mismatch
The object passed by the controller must match the type declared by the view.
View:
@model ProductViewModel
Incorrect controller code:
return View(new CustomerViewModel());
This produces a runtime error because the view expects ProductViewModel, but it receives CustomerViewModel.
Strongly typed view vs ViewBag/ViewData
| Strongly typed model | ViewBag / ViewData |
|---|---|
Uses @model and Model |
Uses dynamic or string-based access |
| Compile-time type checking | Errors may appear only at runtime |
| Provides IntelliSense | Limited IntelliSense |
| Suitable for primary page data | Suitable for small supporting values |
| Easier to refactor | Property/key names can break silently |
Example:
@Model.Name
Compared with:
@ViewBag.ProductName
@ViewData["ProductName"]
Important distinction
@model ProductViewModel
- Lowercase
modelis a Razor directive declaring the expected type.
@Model.Name
- Uppercase
Modelis the object passed from the controller.
Key points
- Define the expected model using
@model. - Pass the object using
return View(model). - Access its properties through
Model. - Prefer a dedicated ViewModel instead of exposing database entities directly.
- Strongly typed views provide IntelliSense and compile-time property checking.
- For lists, use types such as
IEnumerable<ProductViewModel>. - Always return the model again when redisplaying an invalid form.
Library requirement
No additional NuGet library is required. Strongly typed views are built into ASP.NET Core MVC.
Sections are named placeholders in a Razor Layout. They allow individual views to insert page-specific content—such as CSS or JavaScript—into a particular location in the layout.
@RenderBody() renders the main view content, whereas @RenderSection() renders a specific named section.
Simple example
Layout: _Layout.cshtml
<!DOCTYPE html>
<html>
<head>
<title>@ViewData["Title"]</title>
<link rel="stylesheet" href="~/css/site.css" />
@await RenderSectionAsync("Styles", required: false)
</head>
<body>
<header>
<h1>My Application</h1>
</header>
<main>
@RenderBody()
</main>
<script src="~/js/site.js"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
The layout defines two optional sections:
Stylesinside<head>Scriptsbefore</body>
View: Create.cshtml
@model ProductViewModel
<h2>Create Product</h2>
<form asp-action="Create" method="post">
<input asp-for="Name" />
<button type="submit">Save</button>
</form>
@section Styles {
<link rel="stylesheet" href="~/css/product.css" />
}
@section Scripts {
<script src="~/js/product.js"></script>
}
Razor places each section’s content at the corresponding location defined in the layout.
Required and optional sections
Required section
@await RenderSectionAsync("Scripts", required: true)
Every view using this layout must define:
@section Scripts {
<script src="~/js/product.js"></script>
}
If it does not, Razor throws a runtime exception.
Optional section
@await RenderSectionAsync("Scripts", required: false)
The view may define the section, but it is not mandatory.
Optional sections are normally preferred for page-specific CSS and JavaScript.
Check whether a section exists
A layout can check whether a view has supplied a particular section:
@if (IsSectionDefined("Scripts"))
{
@await RenderSectionAsync("Scripts")
}
else
{
<script src="~/js/default.js"></script>
}
This allows the layout to provide default content when the view does not define the section.
RenderBody vs RenderSection
@RenderBody() |
@RenderSection() |
|---|---|
| Renders the view’s main content | Renders content from a named section |
| Required once in a standard layout | Can be required or optional |
| Does not have a section name | Uses a name such as Scripts |
Usually appears inside <main> |
Can appear in <head>, footer or elsewhere |
Important rules
- A section can be defined only in a view that directly uses the layout.
- Partial Views cannot define sections for their parent layout.
- Each section should be rendered only once.
- Section names must match between the view and layout.
- Section names are case-insensitive in ASP.NET Core Razor.
- Sections are not required unless
required: trueis specified.
Common validation-script example
ASP.NET Core MVC forms commonly use:
@section Scripts {
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
}
The layout must include:
@await RenderSectionAsync("Scripts", required: false)
This loads client-side validation scripts only on pages that require them.
Library requirement
No additional library is required. Razor sections are built into ASP.NET Core MVC.
Interview answer
Sections are named placeholders in a Razor Layout that allow individual views to inject page-specific content at defined locations. The layout renders them using
RenderSectionAsync(), and a section can be either required or optional.
In ASP.NET Core MVC, UI can be reused through Layouts, Partial Views, View Components, Tag Helpers, Editor/Display Templates, and reusable CSS/JavaScript.
1. Layouts — shared page structure
Use Layouts for elements common to the entire page:
- Header
- Navigation
- Footer
- CSS and JavaScript references
Views/Shared/_Layout.cshtml
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
<partial name="_Header" />
<main>
@RenderBody()
</main>
<partial name="_Footer" />
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
Configure it globally in _ViewStart.cshtml:
@{
Layout = "_Layout";
}
2. Partial Views — reusable markup
Use a Partial View when the parent already has the required data and the component mainly renders HTML.
Views/Shared/_ProductCard.cshtml
@model ProductViewModel
<div class="product-card">
<h3>@Model.Name</h3>
<p>₹@Model.Price</p>
</div>
Use it in any view:
<partial name="_ProductCard" model="product" />
Common examples include:
- Product cards
- Form fields
- Alerts
- Headers and footers
- Repeated table rows
3. View Components — reusable UI with logic
Use a View Component when the reusable section needs:
- Dependency injection
- Database or service calls
- Independent processing logic
- Its own model
Component class
public class LatestArticlesViewComponent : ViewComponent
{
private readonly IArticleService _articleService;
public LatestArticlesViewComponent(IArticleService articleService)
{
_articleService = articleService;
}
public async Task<IViewComponentResult> InvokeAsync(int count)
{
var articles =
await _articleService.GetLatestArticlesAsync(count);
return View(articles);
}
}
Component view location
Views/Shared/Components/LatestArticles/Default.cshtml
Render it
@await Component.InvokeAsync(
"LatestArticles",
new { count = 5 })
Or:
<vc:latest-articles count="5"></vc:latest-articles>
Common examples include:
- Shopping-cart summary
- Latest articles
- Category menu
- Notification count
- Dynamic sidebar
4. Tag Helpers — reusable HTML-generating behavior
Create a custom Tag Helper when you want reusable server-side behavior with natural HTML syntax.
Tag Helper class
using Microsoft.AspNetCore.Razor.TagHelpers;
public class AlertTagHelper : TagHelper
{
public string Message { get; set; } = string.Empty;
public string Type { get; set; } = "info";
public override void Process(
TagHelperContext context,
TagHelperOutput output)
{
output.TagName = "div";
output.Attributes.SetAttribute(
"class", $"alert alert-{Type}");
output.Content.SetContent(Message);
}
}
Register it in _ViewImports.cshtml:
@addTagHelper *, MyApplication
Use it:
<alert message="Product saved successfully"
type="success">
</alert>
Tag Helpers are useful for reusable elements that require custom rendering rules.
5. Display and Editor Templates
Use templates when the same model type must be displayed or edited consistently across different views.
Display template location
Views/Shared/DisplayTemplates/AddressViewModel.cshtml
@model AddressViewModel
<address>
@Model.Street<br />
@Model.City<br />
@Model.PostalCode
</address>
Use it:
@Html.DisplayFor(model => model.Address)
Editor template location
Views/Shared/EditorTemplates/AddressViewModel.cshtml
@model AddressViewModel
<label asp-for="Street"></label>
<input asp-for="Street" />
<label asp-for="City"></label>
<input asp-for="City" />
Use it:
@Html.EditorFor(model => model.Address)
6. Shared CSS and JavaScript
Place common styles and scripts in reusable files:
wwwroot/
├── css/
│ ├── site.css
│ └── components.css
└── js/
├── site.js
└── validation.js
Reference global assets from the Layout:
<link rel="stylesheet" href="~/css/components.css"
asp-append-version="true" />
<script src="~/js/site.js"
asp-append-version="true"></script>
Which one should you choose?
| Requirement | Recommended approach |
|---|---|
| Common page structure | Layout |
| Reusable HTML with prepared data | Partial View |
| Reusable UI with its own logic/data | View Component |
| Reusable HTML-generating behavior | Custom Tag Helper |
| Consistent rendering for a model type | Display Template |
| Consistent form fields for a model type | Editor Template |
| Common styling or browser behavior | Shared CSS/JavaScript |
Recommended structure
Views/
├── Shared/
│ ├── _Layout.cshtml
│ ├── _Header.cshtml
│ ├── _Footer.cshtml
│ ├── _ProductCard.cshtml
│ ├── Components/
│ │ └── LatestArticles/
│ │ └── Default.cshtml
│ ├── DisplayTemplates/
│ └── EditorTemplates/
├── _ViewImports.cshtml
└── _ViewStart.cshtml
Library requirement
No additional NuGet package is required for these features. They are included with ASP.NET Core MVC.
Interview answer
Use Layouts for the page shell, Partial Views for reusable markup with prepared data, View Components for reusable UI containing independent C# logic, Tag Helpers for reusable HTML-generating behavior, and Display/Editor Templates for consistent model rendering.