Partial Views — Reusable HTML Fragments

Partial views are reusable Razor fragments — smaller views rendered inside other views. They reduce duplication: a post card component used on the home page, the search results page, and the author page is defined once as a partial view and included in all three. Partial views follow the same naming convention as full views (underscore prefix by convention: _PostCard.cshtml) and can receive their own model. They run within the parent view’s context by default but can render completely independently with their own model.

Creating and Rendering Partial Views

@* ── _PostCard.cshtml — the partial view ──────────────────────────────── *@
@model PostSummaryViewModel

<article class="card mb-3">
    <div class="card-body">
        <h5 class="card-title">
            <a asp-controller="Posts" asp-action="Details" asp-route-id="@Model.Id">
                @Model.Title
            </a>
        </h5>
        <p class="text-muted">@Model.PublishedAt.ToString("MMMM d, yyyy")</p>
        <p class="card-text">@Model.Excerpt</p>
        <div>
            @foreach (var tag in Model.Tags)
            {
                <span class="badge bg-secondary">@tag</span>
            }
        </div>
    </div>
</article>

@* ── Including the partial from a parent view ─────────────────────────── *@
@* Views/Posts/Index.cshtml *@
@model PostIndexViewModel

<h1>Posts</h1>

@* Method 1: Partial Tag Helper (preferred, async) *@
@foreach (var post in Model.Posts)
{
    <partial name="_PostCard" model="post" />
}

@* Method 2: Html.PartialAsync (also async) *@
@foreach (var post in Model.Posts)
{
    @await Html.PartialAsync("_PostCard", post)
}

@* Method 3: Without model — partial uses parent's ViewData *@
<partial name="_Pagination" />
Note: Always use <partial name="..." model="..." /> (Tag Helper) or @await Html.PartialAsync() — never the synchronous @Html.Partial(). The synchronous version blocks a thread while rendering, which can cause deadlocks in async contexts. The async versions are drop-in replacements with no downside. ASP.NET Core’s Razor engine is fully async; using synchronous HTML Helpers in an async action pipeline is an anti-pattern that degrades throughput.
Tip: By convention, partial view files start with an underscore (_PostCard.cshtml, _Pagination.cshtml, _NavigationMenu.cshtml). This convention signals that the file is not a standalone page (not accessible directly by URL through conventional routing) and should be rendered inside another view. While nothing prevents naming partial views without an underscore, following the convention makes the project structure self-documenting — any .cshtml starting with _ is a component, not a page.
Warning: Partial views inherit the parent view’s ViewData and ViewBag by default — changes made to ViewData inside a partial propagate back to the parent. This can cause subtle bugs when multiple partial views modify the same ViewData key. If you need isolated data passing to a partial, always pass an explicit model: <partial name="_PostCard" model="post" />. Avoid relying on shared ViewData across partial boundaries.

Partial View vs View Component — When to Use Each

Concern Partial View View Component
Data source Model passed from parent view Fetches own data via DI services
Logic Pure rendering, no business logic Has InvokeAsync method with logic
DI support No (uses parent’s ViewData) Yes (constructor injection)
Use for Reusable HTML fragments with passed model Self-contained UI widgets (nav, cart count)
Example _PostCard, _Pagination Navigation menu, recent posts sidebar

Common Mistakes

Mistake 1 — Using synchronous @Html.Partial() in async views

❌ Wrong — blocks threads, potential deadlock in async contexts:

@Html.Partial("_PostCard", post)   @* synchronous — avoid! *@

✅ Correct — use the Partial Tag Helper or @await Html.PartialAsync().

Mistake 2 — Database calls inside partial views (N+1 query problem)

❌ Wrong — each rendered _PostCard partial triggers its own database query for tags.

✅ Correct — load all data needed for all partials in the parent controller action; pass it via the model.

🧠 Test Yourself

A navigation menu partial needs to query the database for the list of categories to display. Should it use a Partial View or a View Component?