Areas in ASP.NET Core

By Dhananjay Kumar and Rick Anderson

Areas are an ASP.NET feature used to organize related functionality into a group as a separate namespace (for routing) and folder structure (for views). Using areas creates a hierarchy for the purpose of routing by adding another route parameter, area, to controller and action or a Razor Page page.

Areas provide a way to partition an ASP.NET Core Web app into smaller functional groups, each with its own set of Razor Pages, controllers, views, and models. An area is effectively a structure inside an app. In an ASP.NET Core web project, logical components like Pages, Model, Controller, and View are kept in different folders. The ASP.NET Core runtime uses naming conventions to create the relationship between these components. For a large app, it may be advantageous to partition the app into separate high level areas of functionality. For instance, an e-commerce app with multiple business units, such as checkout, billing, and search. Each of these units have their own area to contain views, controllers, Razor Pages, and models.

Consider using Areas in a project when:

  • The app is made of multiple high-level functional components that can be logically separated.
  • You want to partition the app so that each functional area can be worked on independently.

View or download sample code (how to download). The download sample provides a basic app for testing areas.

If you're using Razor Pages, see Areas with Razor Pages in this document.

Areas for controllers with views

A typical ASP.NET Core web app using areas, controllers, and views contains the following:

  • An Area folder structure.

  • Controllers decorated with the [Area] attribute to associate the controller with the area:

    [Area("Products")]
    public class ManageController : Controller
    {
    
  • The area route added to startup:

    app.UseMvc(routes =>
    {
        routes.MapRoute(
          name: "MyArea",
          template: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
    
        routes.MapRoute(
           name: "default",
           template: "{controller=Home}/{action=Index}/{id?}");
    });
    

Area folder structure

Consider an app that has two logical groups, Products and Services. Using areas, the folder structure would be similar to the following:

  • Project name
    • Areas
      • Products
        • Controllers
          • HomeController.cs
          • ManageController.cs
        • Views
          • Home
            • Index.cshtml
          • Manage
            • Index.cshtml
            • About.cshtml
      • Services
        • Controllers
          • HomeController.cs
        • Views
          • Home
            • Index.cshtml

While the preceding layout is typical when using Areas, only the view files are required to use this folder structure. View discovery searches for a matching area view file in the following order:

/Areas/<Area-Name>/Views/<Controller-Name>/<Action-Name>.cshtml
/Areas/<Area-Name>/Views/Shared/<Action-Name>.cshtml
/Views/Shared/<Action-Name>.cshtml
/Pages/Shared/<Action-Name>.cshtml

Associate the controller with an Area

Area controllers are designated with the [Area] attribute:

using Microsoft.AspNetCore.Mvc;

namespace MVCareas.Areas.Products.Controllers
{
    [Area("Products")]
    public class ManageController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        public IActionResult About()
        {
            return View();
        }
    }
}

Add Area route

Area routes typically use conventional routing rather than attribute routing. Conventional routing is order-dependent. In general, routes with areas should be placed earlier in the route table as they're more specific than routes without an area.

{area:...} can be used as a token in route templates if url space is uniform across all areas:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseMvc(routes =>
    {
        routes.MapRoute(
          name: "MyArea",
          template: "{area:exists}/{controller=Home}/{action=Index}/{id?}");

        routes.MapRoute(
           name: "default",
           template: "{controller=Home}/{action=Index}/{id?}");
    });
}

In the preceding code, exists applies a constraint that the route must match an area. Using {area:...} is the least complicated mechanism to adding routing to areas.

The following code uses MapAreaRoute to create two named area routes:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseMvc(routes =>
    {
        routes.MapAreaRoute(
            name: "MyAreaProducts",
            areaName:"Products",
            template: "Products/{controller=Home}/{action=Index}/{id?}");

        routes.MapAreaRoute(
            name: "MyAreaServices",
            areaName: "Services",
            template: "Services/{controller=Home}/{action=Index}/{id?}");

        routes.MapRoute(
           name: "default",
           template: "{controller=Home}/{action=Index}/{id?}");
    });
}

When using MapAreaRoute with ASP.NET Core 2.2, see this GitHub issue.

For more information, see Area routing.

The following code from the sample download shows link generation with the area specified:

<li>Anchor Tag Helper links</li>
<ul>
    <li>
        <a asp-area="Products" asp-controller="Home" asp-action="About">
            Products/Home/About
        </a>
    </li>
    <li>
        <a asp-area="Services" asp-controller="Home" asp-action="About">
            Services About
        </a>
    </li>
    <li>
        <a asp-area="" asp-controller="Home" asp-action="About">
            /Home/About
        </a>
    </li>
</ul>
<li>Html.ActionLink generated links</li>
<ul>
    <li>
        @Html.ActionLink("Product/Manage/About", "About", "Manage", 
                                                new { area = "Products" })
    </li>
</ul>
<li>Url.Action generated links</li>
<ul>
    <li>
        <a href='@Url.Action("About", "Manage", new { area = "Products" })'>
            Products/Manage/About
        </a>
    </li>
</ul>

The links generated with the preceding code are valid anywhere in the app.

The sample download includes a partial view that contains the preceding links and the same links without specifying the area. The partial view is referenced in the layout file, so every page in the app displays the generated links. The links generated without specifying the area are only valid when referenced from a page in the same area and controller.

When the area or controller is not specified, routing depends on the ambient values. The current route values of the current request are considered ambient values for link generation. In many cases for the sample app, using the ambient values generates incorrect links.

For more information, see Routing to controller actions.

Shared layout for Areas using the _ViewStart.cshtml file

To share a common layout for the entire app, move the _ViewStart.cshtml to the application root folder.

_ViewImports.cshtml

In its standard location, /Views/_ViewImports.cshtml doesn't apply to areas. To use common Tag Helpers, @using, or @inject in your area, ensure a proper _ViewImports.cshtml file applies to your area views. If you want the same behavior in all your views, move /Views/_ViewImports.cshtml to the application root.

Change default area folder where views are stored

The following code changes the default area folder from "Areas" to "MyAreas":

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<RazorViewEngineOptions>(options =>
    {
        options.AreaViewLocationFormats.Clear();
        options.AreaViewLocationFormats.Add("/MyAreas/{2}/Views/{1}/{0}.cshtml");
        options.AreaViewLocationFormats.Add("/MyAreas/{2}/Views/Shared/{0}.cshtml");
        options.AreaViewLocationFormats.Add("/Views/Shared/{0}.cshtml");
    });

    services.AddMvc();
}

Areas with Razor Pages

Areas with Razor Pages require an Areas//Pages folder in the root of the app. The following folder structure is used with the sample app:

  • Project name
    • Areas
      • Products
        • Pages
          • _ViewImports
          • About
          • Index
      • Services
        • Pages
          • Manage
            • About
            • Index

The following code from the sample download shows link generation with the area specified (for example, asp-area="Products"):

<li>Anchor Tag Helper links</li>
<ul>
    <li>
        <a asp-area="Products" asp-page="/About">
            Products/About
        </a>
    </li>
    <li>
        <a asp-area="Services" asp-page="/Manage/About">
            Services/Manage/About
        </a>
    </li>
    <li>
        <a asp-area="" asp-page="/About">
            /About
        </a>
    </li>
</ul>
<li>Url.Page generated links</li>
<ul>
    <li>
        <a href='@Url.Page("/Manage/About", new { area = "Services" })'>
            Services/Manage/About
        </a>
    </li>
    <li>
        <a href='@Url.Page("/About", new { area = "Products" })'>
            Products/About
        </a>
    </li>
</ul>

The links generated with the preceding code are valid anywhere in the app.

The sample download includes a partial view that contains the preceding links and the same links without specifying the area. The partial view is referenced in the layout file, so every page in the app displays the generated links. The links generated without specifying the area are only valid when referenced from a page in the same area.

When the area is not specified, routing depends on the ambient values. The current route values of the current request are considered ambient values for link generation. In many cases for the sample app, using the ambient values generates incorrect links. For example, consider the links generated from the following code:

<li>
    <a asp-page="/Manage/About">
        Services/Manage/About
    </a>
</li>
<li>
    <a asp-page="/About">
        /About
    </a>
</li>

For the preceding code:

  • The link generated from <a asp-page="/Manage/About"> is correct only when the last request was for a page in Services area. For example, /Services/Manage/, /Services/Manage/Index, or /Services/Manage/About.
  • The link generated from <a asp-page="/About"> is correct only when the last request was for a page in /Home.
  • The code is from the sample download.

Import namespace and Tag Helpers with _ViewImports file

A _ViewImports.cshtml file can be added to each area Pages folder to import the namespace and Tag Helpers to each Razor Page in the folder.

Consider the Services area of the sample code, which doesn't contain a _ViewImports.cshtml file. The following markup shows the /Services/Manage/About Razor Page:

@page
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@model RPareas.Areas.Services.Pages.Manage.AboutModel
@{
    ViewData["Title"] = "Srv Mng About";
}

<h2>/Services/Manage/About</h2>

<a asp-area="Products" asp-page="/Index">
    Products/Index
</a>

In the preceding markup:

  • The fully qualified domain name must be used to specify the model (@model RPareas.Areas.Services.Pages.Manage.AboutModel).
  • Tag Helpers are enabled by @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

In the sample download, the Products area contains the following _ViewImports.cshtml file:

@namespace RPareas.Areas.Products.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

The following markup shows the /Products/About Razor Page:

@page
@model AboutModel
@{
    ViewData["Title"] = "Prod About";
}

<h2>Products/About</h2>

<a asp-area="Services" asp-page="/Manage/About">
    Services/Manage/About
</a>

In the preceding file, the namespace and @addTagHelper directive is imported to the file by the Areas/Products/Pages/_ViewImports.cshtml file.

For more information, see Managing Tag Helper scope and Importing Shared Directives.

Shared layout for Razor Pages Areas

To share a common layout for the entire app, move the _ViewStart.cshtml to the application root folder.

Publishing Areas

All *.cshtml files and files within the wwwroot directory are published to output when <Project Sdk="Microsoft.NET.Sdk.Web"> is included in the *.csproj file.