Areas — Organising Large MVC Applications

Areas partition a large ASP.NET Core application into distinct sections, each with its own controllers, views, and optionally models. A common use case: an application with a public-facing blog (/blog/*), an admin panel (/admin/*), and an API (/api/*). Without areas, all controllers and views are in a flat structure and naming conflicts become a problem as the application grows. Areas give each section its own namespace, folder structure, and route prefix, keeping sections cleanly separated.

Creating and Configuring an Area

// ── Project structure with an Admin area ─────────────────────────────────
// BlogApp.Web/
// ├── Areas/
// │   └── Admin/
// │       ├── Controllers/
// │       │   ├── DashboardController.cs
// │       │   └── PostsController.cs    ← different from root PostsController!
// │       └── Views/
// │           ├── Dashboard/
// │           │   └── Index.cshtml
// │           ├── Posts/
// │           │   ├── Index.cshtml
// │           │   └── Create.cshtml
// │           └── Shared/
// │               └── _AdminLayout.cshtml
// ├── Controllers/
// │   └── PostsController.cs           ← root PostsController (different class)
// └── Views/
//     └── Posts/
//         └── Index.cshtml

// ── Area controller — must have [Area] attribute ──────────────────────────
namespace BlogApp.Web.Areas.Admin.Controllers
{
    [Area("Admin")]
    [Authorize(Roles = "Admin")]
    [Route("admin/[controller]/[action]")]
    public class PostsController : Controller
    {
        // GET /admin/posts/index
        public async Task<IActionResult> Index() => View();

        // GET /admin/posts/create
        [HttpGet]
        public IActionResult Create() => View(new CreatePostViewModel());

        [HttpPost, ValidateAntiForgeryToken]
        public async Task<IActionResult> Create(CreatePostViewModel model)
        {
            if (!ModelState.IsValid) return View(model);
            await _service.CreateAsync(model.ToRequest(), User.GetUserId());
            TempData["Success"] = "Post created.";
            return RedirectToAction(nameof(Index));
        }
    }
}

// ── Register area routing in Program.cs ───────────────────────────────────
app.MapAreaControllerRoute(
    name:     "admin",
    areaName: "Admin",
    pattern:  "admin/{controller=Dashboard}/{action=Index}/{id?}");

app.MapControllerRoute(
    name:    "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");
Note: The [Area("Admin")] attribute on the controller is mandatory — without it, the controller is not associated with any area even if it is in the Areas/Admin/Controllers/ folder. The folder location is conventional and helps organisation, but the routing system uses the [Area] attribute for the actual area association. Missing the [Area] attribute is the most common area configuration mistake.
Tip: Generating links to area controllers requires specifying the area in the route values: <a asp-area="Admin" asp-controller="Posts" asp-action="Index">Admin Posts</a>. Without asp-area, the Tag Helper generates a link to the root PostsController, not the Admin area one. Ambient route values include the current area — so from within the Admin area, links to Admin controllers do not need asp-area, but links from the root to the Admin area always do.
Warning: Area view discovery follows the order: Areas/{AreaName}/Views/{Controller}/{Action}.cshtml, then Areas/{AreaName}/Views/Shared/{Action}.cshtml, then the root Views/Shared/{Action}.cshtml. This means area views can fall back to root shared views (like Error.cshtml), which is usually correct. However, if you have a root shared layout and an area layout with the same name, the area layout shadows the root layout only within the area — as expected. Test view resolution after adding a new area to verify views are found correctly.
// ── In a root controller — link to area ───────────────────────────────────
string adminUrl = Url.Action("Index", "Dashboard", new { area = "Admin" });
// → /admin/dashboard/index

// ── In a view — Tag Helper with asp-area ──────────────────────────────────
<a asp-area="Admin" asp-controller="Posts" asp-action="Create">
    Admin: New Post
</a>
@* → /admin/posts/create *@

<a asp-area="" asp-controller="Posts" asp-action="Index">
    Public Blog
</a>
@* asp-area="" explicitly escapes any ambient area to link to root controller *@

Common Mistakes

Mistake 1 — Missing [Area] attribute on the controller (area routing ignored)

❌ Wrong — controller in Areas/Admin/ but missing [Area(“Admin”)]; treated as a root controller.

✅ Correct — always add [Area(“Admin”)] to every controller inside the Admin area folder.

Mistake 2 — Not specifying asp-area when linking to area controllers

❌ Wrong — link goes to root PostsController instead of Admin area PostsController:

<a asp-controller="Posts" asp-action="Index">Admin</a>  @* missing asp-area="Admin" *@

✅ Correct — always include asp-area="Admin" when generating links to area controllers from outside the area.

🧠 Test Yourself

You have both a root PostsController and an Admin area PostsController. A view in the root generates <a asp-controller="Posts" asp-action="Index">. Which controller’s action does it link to?