Layouts and Partials
A shared layout with RenderBody and sections, partials for repeated markup, and view components for a piece with its own logic.
@RenderBody() @await RenderSectionAsync("Scripts", required: false)Every page on a site repeats the same shell: the <head>, the navigation, the footer. A layout holds that shell once. By convention it is Views/Shared/_Layout.cshtml for MVC, or Pages/Shared/_Layout.cshtml for Razor Pages.
A view chooses it with Layout = "_Layout"; in a code block, or inherits the choice from _ViewStart.cshtml, a file that runs before every view. The layout calls @RenderBody() where the view's own markup belongs. Every layout must call it, and exactly once.
Sections
A section is a second, named hole in the layout. The layout calls @await RenderSectionAsync("Scripts", required: false) and a view fills it with @section Scripts { ... }. With required: true, a view that leaves the section out throws. A section is visible only to its own layout, never to a partial.
Partials and view components
A partial view is a .cshtml file with no @page directive, rendered inside another file: <partial name="_ProductCard" model="item" />. That tag is shorthand for @await Html.PartialAsync("_ProductCard", item), the same call written the long way. A partial only formats data handed to it.
When the piece has to fetch its own data, use a view component: a class deriving from ViewComponent with an Invoke or InvokeAsync method. Render it with <vc:cart-summary item-count="3"></vc:cart-summary>, or the longhand @await Component.InvokeAsync("CartSummary", new { itemCount = 3 }).
What trips people up
Partials do not run _ViewStart.cshtml, and a partial gets a copy of ViewData, so what it writes there is lost when it returns.
Example
* Views/Shared/_Layout.cshtml *
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>["Title"] - Shop</title>
<link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
<header>
<a href="/">Shop</a>
* A view component. It runs its own code to get its data.
The vc: form needs *, Shop in
Views/_ViewImports.cshtml, where Shop is the assembly name. *
<vc:cart-summary item-count="3"></vc:cart-summary>
</header>
<main>
* The current view's markup is written at this point. *@
()
</main>
* A partial. Markup only, no logic of its own. *
<partial name="_Footer" />
<script src="~/js/site.js"></script>
* An optional hole. Views that need extra scripts fill it. *
RenderSectionAsync("Scripts", required: false)
</body>
</html>When to use it
- Put the site header, footer and stylesheet links in one file instead of every page.
- Let one page add a script tag to the bottom of the shared layout.
- Render a cart badge in the header that counts the signed-in visitor's items.
More examples
Choosing the layout once, and filling a section
_ViewStart keeps the layout name out of every single view, which matters when you later rename it. Sections are how a page adds something to a part of the shell it does not own, most often one extra script or stylesheet near the end of the body.
@* Views/_ViewStart.cshtml. Runs before every view in the folder tree,
but not before layouts and not before partials. *@
@{
Layout = "_Layout";
}
@* ----- Views/Home/Index.cshtml ----- *@
@{
ViewData["Title"] = "Home";
}
<h1>Home</h1>
<p>Welcome back.</p>
@* Fills the layout's Scripts section. Without this block the layout
renders nothing there, because the section is not required. *@
@section Scripts {
<script src="~/js/home.js"></script>
}A view component class
Use this shape whenever a shared fragment needs data that the surrounding page knows nothing about, such as a cart badge or an unread-messages count. Note that View("text") would be read as a view name rather than a model, so pass a model object or name the view explicitly with View("Default", model).
// ViewComponents/CartSummaryViewComponent.cs
using Microsoft.AspNetCore.Mvc;
namespace Shop.ViewComponents;
// The component's name is the class name with the ViewComponent
// suffix removed, so this one is called CartSummary.
public class CartSummaryViewComponent : ViewComponent
{
// Invoke may take any parameters you like. InvokeAsync, returning
// Task<IViewComponentResult>, is the form to use when the component
// queries a database or calls another service.
public IViewComponentResult Invoke(int itemCount)
{
// Renders Views/Shared/Components/CartSummary/Default.cshtml
// with itemCount as its model.
return View(itemCount);
}
}The component's view, and both ways to call it
The two call forms are interchangeable, so you need to recognise both in other people's code. The tag form reads like markup and gets editor support; the InvokeAsync form is the one to use when the component name is held in a variable.
@* Views/Shared/Components/CartSummary/Default.cshtml *@
@model int
<span>Cart: @Model item(s)</span>
@* ----- Calling it from any view or layout ----- *@
@* Shorthand. Attribute and element names are kebab-case. *@
<vc:cart-summary item-count="3"></vc:cart-summary>
@* Longhand. The same call, with the arguments as an object. *@
@await Component.InvokeAsync("CartSummary", new { itemCount = 3 })
@* Both render: <span>Cart: 3 item(s)</span> *@
Discussion