All articles

Partial Views in ASP.NET Core MVC with Examples

Turn repeated Razor into reusable partial views: the partial tag helper, name resolution, passing models, and when a view component is the better tool.

0 · log in to like, save & follow Share on LinkedIn Share on X
Partial Views in ASP.NET Core MVC with Examples

Any time you catch yourself copying the same chunk of Razor between views — a product card, an address block, a pagination bar — that chunk wants to be a partial view: a .cshtml file without a layout that renders inside other views. This guide covers creating partials, the <partial> tag helper, naming and folder resolution, and when to reach for a view component instead, on .NET 10 with verified code from the companion repository.

Partial Views in ASP.NET Core MVC with Examples

Creating a partial

A partial is just a Razor file, conventionally named with a leading underscore, placed where the views that use it can find it. Ours renders one product card:

@* Views/Shared/_ProductCard.cshtml *@
@model AspNetCoreMvcExamples.Models.Product

<div class="card mb-3">
    <div class="card-body">
        <h5 class="card-title">@Model.Name</h5>
        <p class="card-text">₹@Model.Price.ToString("N2")</p>
        <p class="card-text">
            @if (Model.Stock > 0) { <span class="badge bg-success">@Model.Stock in stock</span> }
            else { <span class="badge bg-danger">Out of stock</span> }
        </p>
        <a class="btn btn-sm btn-outline-primary" href="/products/@Model.Id">Details</a>
    </div>
</div>

There is no Layout, no @section — a partial renders exactly its own markup into the parent's output stream.

Rendering with the partial tag helper

The modern way is the <partial> tag helper:

@* Views/Products/Index.cshtml *@
@model IReadOnlyList<AspNetCoreMvcExamples.Models.Product>

<div class="row">
    @foreach (var product in Model)
    {
        <div class="col-md-4">
            <partial name="_ProductCard" model="product" />
        </div>
    }
</div>

Each iteration passes a different Product to the same partial — one definition, many renderings. The detail page reuses it with its own model:

@* Views/Products/Detail.cshtml *@
@model AspNetCoreMvcExamples.Models.Product

<partial name="_ProductCard" model="Model" />

Change the card once, and the listing, the detail page, and anywhere else it appears update together — that is the entire point.

How partial names resolve

name="_ProductCard" (no path, no extension) makes Razor search, in order:

  1. The current controller's view folder — Views/Products/
  2. Views/Shared/

Put page-specific partials next to their page, and genuinely shared ones in Shared. You can also give an explicit application-relative path (name="~/Views/Shared/_ProductCard.cshtml"), which skips searching — useful when two folders contain a partial with the same name and you must disambiguate.

The underscore prefix is convention only; Razor does not treat underscored files specially in MVC. It survives as a signal to humans: "this file is not a full page."

Passing data: model and view-data

The model attribute is the primary channel, and the partial's @model type makes it compile-time safe. For small extras (a flag, a heading) there is view-data:

<partial name="_ProductCard" model="product" view-data='new ViewDataDictionary(ViewData) { ["Highlight"] = true }' />

Use it sparingly — once a partial needs three ViewData keys, wrap them in a tiny view model instead. A partial with a precise model type documents itself.

If you omit model entirely, the partial receives the parent's model — convenient for splitting a large page into sections that all share one model.

Html.PartialAsync and RenderPartialAsync

The tag helper covers nearly everything, but two method-style equivalents exist:

@await Html.PartialAsync("_ProductCard", product)

@{ await Html.RenderPartialAsync("_ProductCard", product); }

PartialAsync returns IHtmlContent; RenderPartialAsync writes directly to the output stream (marginally faster for very large fragments, awkward syntax). Prefer the tag helper for readability; never use the obsolete synchronous Html.Partial, which can deadlock — the analyzer MVC1000 flags it.

Partials in forms

A partial rendered inside a <form> participates in model binding as long as the input names line up with the parent model. The stock pattern is the validation scripts partial every MVC template ships with:

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
}

For editing collections (order lines, phone numbers), partials pair with for-loop indexing (Items[0].Name) so the binder reconstructs the list — a topic that deserves its own post.

Partial view vs. view component

A partial is markup reuse; it renders whatever model the caller already has. The moment the fragment needs to fetch its own data — a cart summary that queries the database, a tag cloud, a "related articles" box — you want a view component: a small class with an InvokeAsync method that can use dependency injection, plus its own view. Rule of thumb:

  • Caller already has the data → partial view.
  • Fragment must get its own data or contains real logic → view component.
  • Fragment must update without a page reload → neither; that's an API endpoint plus fetch, or Blazor.

Summary

Partial views turn repeated Razor into a single, named, strongly-typed file: underscore-prefixed by convention, resolved from the controller's folder then Shared, rendered with <partial name="..." model="..." />, and combined with view components when the fragment needs its own data. The product-card example — used by both the listing grid and the detail page — runs as-is in the companion repository.

Enjoyed this article? Get the best GeeksArray articles in your inbox — once a week, no spam, unsubscribe anytime.

Comments (0)

Log in to join the conversation.

No comments yet — be the first to share your thoughts.