All articles

How to Use Areas in ASP.NET Core MVC

Structure a growing MVC app with areas: folder conventions, the {area:exists} route, view resolution, cross-area links, and securing an area with a policy.

0 · log in to like, save & follow Share on LinkedIn Share on X
How to Use Areas in ASP.NET Core MVC

As an MVC application grows, a flat Controllers folder with thirty files stops scaling — the admin screens, the customer store, and the API endpoints all blur together. Areas are ASP.NET Core MVC's built-in answer: self-contained sections of the app, each with its own controllers, views, and URL prefix, without spinning up separate projects.

How to Use Areas in ASP.NET Core MVC

This guide sets up an Admin area on .NET 10 — folder structure, routing, link generation, and the sharp edges — verified against the companion repository.

What an area gives you

  • A URL namespace: everything under /Admin/... routes to the area's controllers.
  • A folder namespace: Areas/Admin/Controllers and Areas/Admin/Views keep the section's code together.
  • Independent view resolution: the area's views can differ completely from the main app's, while still sharing layouts when you want.

Typical splits: Admin, Account, Api, or per-business-domain sections (Billing, Catalog) in a modular monolith.

Folder structure

AspNetCoreMvcExamples/
├── Areas/
│   └── Admin/
│       ├── Controllers/
│       │   └── DashboardController.cs
│       └── Views/
│           └── Dashboard/
│               └── Index.cshtml
├── Controllers/
├── Views/
└── Program.cs

Only the Areas/{name}/Controllers and Areas/{name}/Views conventions matter; Models can live wherever your architecture puts them.

The controller: [Area] is mandatory

[Area("Admin")]
public class DashboardController : Controller
{
    public IActionResult Index()
    {
        ViewBag.Stats = new Dictionary<string, int>
        {
            ["Products"] = Catalog.Products.Count,
            ["Out of stock"] = Catalog.Products.Count(p => p.Stock == 0),
        };
        return View();
    }
}

The [Area("Admin")] attribute is what binds the controller to the area — the folder location alone does nothing. Forgetting the attribute is the classic areas bug: the controller still exists, but it matches the default route, and its views resolve from the wrong folder.

Routing

Register an area route before the default route, using the {area:exists} constraint:

app.MapControllerRoute(
    name: "areas",
    pattern: "{area:exists}/{controller=Dashboard}/{action=Index}/{id?}");

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

{area:exists} only matches URL prefixes that correspond to a registered area, so /Products still falls through to the default route while /Admin/Dashboard hits the area. Verified:

GET /Admin/Dashboard   → 200 (Areas/Admin/Views/Dashboard/Index.cshtml)
GET /Products          → 200 (Views/Products/Index.cshtml)

Attribute routing works too, and larger apps often prefer it: [Route("admin/[controller]/[action]")] on a base AdminController gives every admin controller the prefix without pattern juggling.

View resolution and layouts

For an area request, Razor searches Areas/Admin/Views/{controller}/, then Areas/Admin/Views/Shared/, then the app-level Views/Shared/. That fallback is exactly what makes layout sharing easy — the area view can opt into the main layout explicitly:

@{
    Layout = "~/Views/Shared/_Layout.cshtml";
    ViewData["Title"] = "Admin dashboard";
}

If the area needs its own chrome (a sidebar-heavy admin shell), drop a _ViewStart.cshtml and _Layout.cshtml into Areas/Admin/Views/ and the whole area switches without touching the public site. Remember to add a _ViewImports.cshtml in the area (or rely on the root one) so tag helpers work.

Generating links across areas

Tag helpers need to be told about area boundaries — this is the second classic bug. From an area view, a link back to the main app must set asp-area="":

<a asp-area="" asp-controller="Products" asp-action="Index">Back to the store</a>

And from the main app into the area:

<a asp-area="Admin" asp-controller="Dashboard" asp-action="Index">Admin</a>

Omit asp-area and the current request's area is assumed — links inside the admin area silently point at /Admin/Products instead of /Products, which 404s. The same applies to RedirectToAction: pass new { area = "" } when crossing out of an area.

Securing an area

Areas are an organizational feature, not a security boundary — /Admin/... URLs are as public as any other until you say otherwise. The clean approach is an authorization policy applied to the whole area:

builder.Services.AddAuthorizationBuilder()
    .AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));

app.MapControllerRoute(
        name: "areas",
        pattern: "{area:exists}/{controller=Dashboard}/{action=Index}/{id?}")
    .RequireAuthorization("AdminOnly");

or [Authorize(Policy = "AdminOnly")] on a shared base controller for the area. Either way, the rule lives in one place instead of on thirty actions.

Areas vs. alternatives

  • Feature folders (organizing by feature instead of by controller/view kind) solve code organization without URL prefixes — some teams combine both.
  • Separate projects / Razor class libraries are the step up when a section needs independent deployment or reuse across apps.
  • Minimal API groups (MapGroup("/admin")) are the equivalent concept for endpoint-style APIs.

Areas hit the sweet spot when one deployable app has a few clearly distinct sections with their own screens.

Summary

Create Areas/{Name}/Controllers and Views, mark controllers with [Area], register the {area:exists} route before the default, and be explicit with asp-area on every cross-boundary link. Add an authorization policy at the area edge, and you have a tidy admin section inside one deployable app. The complete working area 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.