ASP.NET Core 中的区域

作者:Dhananjay KumarRick Anderson

区域是一项 ASP.NET 功能,用于将相关功能整理到一个组中,单独作为:

  • 用于路由的命名空间。
  • 用于视图和 Razor Pages 的文件夹结构。

使用区域会通过为 controlleraction 或 Razor 页面 page 添加另一个路由参数 area,创建用于路由目的的层次结构。

区域提供了一种将 ASP.NET Core Web 应用划分为更小的功能组的方法,每个功能组都有自己的一组 Razor Pages、控制器、视图和模型。 区域实际上是应用内的结构。 在 ASP.NET Core Web 项目中,Pages、模型、控制器和视图等逻辑组件保存在不同的文件夹中。 ASP.NET Core 运行时使用命名约定来创建这些组件之间的关系。 对于大型应用,将应用分区为独立的高级功能区域可能更有利。 例如,具有多个业务单位(如结账、计费、搜索等)的电子商务应用。 每个单位都有自己的区域,用于包含视图、控制器、Razor Pages 和模型。

如果发生以下情况,请考虑在项目中使用区域:

  • 应用由可以进行逻辑分隔的多个高级功能组件组成。
  • 想对应用进行分区,以便可以独立处理每个功能区域。

如果使用 Razor Pages,请参阅本文档中的使用 Razor Pages 的区域

带视图的控制器区域

使用区域、控制器和视图的典型 ASP.NET Core Web 应用包含以下内容:

  • 区域文件夹结构

  • 使用 [Area] 属性将自己与区域关联的控制器:

    [Area("Products")]
    public class ManageController : Controller
    {
    
  • 添加到 Program.cs 的区域路由

    var builder = WebApplication.CreateBuilder(args);
    
    builder.Services.AddControllersWithViews();
    
    var app = builder.Build();
    
    if (!app.Environment.IsDevelopment())
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }
    
    app.UseHttpsRedirection();
    app.UseStaticFiles();
    
    app.UseRouting();
    
    app.UseAuthorization();
    
    app.MapControllerRoute(
        name: "MyArea",
        pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
    
    app.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
    
    app.Run();
    

区域文件夹结构

请考虑具有两个逻辑组(产品和服务)的应用。 使用区域,文件夹结构类似于以下内容:

  • 项目名称
    • Areas
      • Products
        • Controllers
          • HomeController.cs
          • ManageController.cs
        • 视图
          • Home
            • Index.cshtml
          • Manage
            • Index.cshtml
            • About.cshtml
      • Services
        • Controllers
          • HomeController.cs
        • 视图
          • Home
            • Index.cshtml

虽然前面的布局是使用区域时的典型布局,但只需要视图文件即可使用此文件夹结构。 视图发现按以下顺序搜索匹配的区域视图文件:

/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

将控制器与区域关联

区域控制器是使用 [Area] 特性指定的:

using Microsoft.AspNetCore.Mvc;
using Microsoft.Docs.Samples;

namespace MVCareas.Areas.Products.Controllers;

[Area("Products")]
public class ManageController : Controller
{
    public IActionResult Index()
    {
        ViewData["routeInfo"] = ControllerContext.MyDisplayRouteInfo();
        return View();
    }

    public IActionResult About()
    {
        ViewData["routeInfo"] = ControllerContext.MyDisplayRouteInfo();
        return View();
    }
}

添加区域路由

区域路由通常使用传统路由,而不使用特性路由。 传统路由依赖于顺序。 一般情况下,具有区域的路由应放在路由表中靠前的位置,因为它们比没有区域的路由更特定。

如果所有区域的 url 空间一致,则 {area:...} 可用作路由模板中的令牌:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

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

app.UseRouting();

app.UseAuthorization();

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

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

app.Run();

在前面的代码中,exists 应用了路由必须与区域匹配的约束。 将 {area:...}MapControllerRoute 结合使用:

  • 是将路由添加到区域的最简单的机制。
  • 将所有控制器与 [Area("Area name")] 特性相匹配。

以下代码使用 MapAreaControllerRoute 创建两个命名区域路由:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

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

app.UseRouting();

app.UseAuthorization();

app.MapAreaControllerRoute(
    name: "MyAreaProducts",
    areaName: "Products",
    pattern: "Products/{controller=Home}/{action=Index}/{id?}");

app.MapAreaControllerRoute(
    name: "MyAreaServices",
    areaName: "Services",
    pattern: "Services/{controller=Home}/{action=Index}/{id?}");

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

app.Run();

有关详细信息,请参阅区域路由

示例下载中的以下代码显示指定区域的链接生成:

<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>

示例下载包括一个分部视图,其中包含:

  • 上面代码中所示的链接。
  • 与上述链接类似的链接,只是未指定 area

布局文件中引用部分视图,因此应用中的每个页面都显示生成的链接。 在未指定区域的情况下生成的链接仅在从同一区域和控制器中的页面引用时才有效。

如果未指定区域或控制器,路由取决于环境值。 当前请求的当前路由值被视为链接生成的环境值。 在许多情况下,对于示例应用,使用环境值将生成带有未指定区域的标记的错误链接。

有关详细信息,请参阅路由到控制器操作

使用 _ViewStart.cshtml 文件的共享区域布局

若要共享整个应用的常用布局,请将 _ViewStart.cshtml 保留在应用程序根文件夹中。 有关详细信息,请参阅 ASP.NET Core 中的布局

应用程序根文件夹

应用程序根文件夹是一个文件夹,其中包含了通过 ASP.NET Core 模板创建的 Web 应用中的 Program.cs 文件。

_ViewImports.cshtml

适用于 MVC 的 /Views/_ViewImports.cshtml 和适用于 Razor Pages 的 /Pages/_ViewImports.cshtml 不会导入到区域中的视图。 使用以下任一方法将视图导入到所有视图中:

  • 将 _ViewImports.cshtml 添加到应用程序根文件夹。 应用程序根文件夹中的 _ViewImports.cshtml 将应用到应用中的所有视图。
  • 将 _ViewImports.cshtml 文件复制到区域下相应的视图文件夹中。 例如,使用单个用户帐户创建的 Razor Pages 应用在以下文件夹中有一个 _ViewImports.cshtml 文件:
    • /Areas/Identity/Pages/_ViewImports.cshtml
    • /Pages/_ViewImports.cshtml

_ViewImports.cshtml 文件通常包含标记帮助程序导入 @using@inject 语句。 有关详细信息,请参阅导入共享指令

更改存储视图的默认区域文件夹

以下代码将默认的区域文件夹从 "Areas" 改为"MyAreas"

using Microsoft.AspNetCore.Mvc.Razor;

var builder = WebApplication.CreateBuilder(args);

builder.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");
});

builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

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

app.UseRouting();

app.UseAuthorization();

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

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

app.Run();

包含 Razor Pages 的区域

包含 Razor Pages 的区域要求在应用根目录中有一个 Areas/<area name>/Pages 文件夹。 以下文件夹结构用于示例应用

  • 项目名称
    • Areas
      • Products
        • Pages
          • _ViewImports
          • 关于
          • 索引
      • Services
        • Pages
          • Manage
            • 关于
            • 索引

示例下载中的以下代码显示指定区域(例如 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>

示例下载包含部分视图,该视图包含以前的链接和相同的链接(未指定区域)。 在布局文件中引用部分视图,因此应用中的每个页面都显示生成的链接。 在未指定区域的情况下生成的链接仅在从同一区域中的页引用时才有效。

如果未指定区域,路由取决于环境值。 当前请求的当前路由值被视为链接生成的环境值。 在许多情况下,对于示例应用,使用环境值会生成错误的链接。 例如,考虑从下面的代码生成的链接:

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

对于上述代码:

  • 只有当最后一个请求是针对 Services 区域的页时,从 <a asp-page="/Manage/About"> 生成的链接才是正确的。 例如,/Services/Manage//Services/Manage/Index/Services/Manage/About
  • 只有当最后一个请求是针对 /Home 中的页时,从 <a asp-page="/About"> 生成的链接才是正确的。
  • 代码摘自示例下载

使用 _ViewImports 文件导入命名空间和标记帮助程序

可以向每个区域 Pages 文件夹添加一个 _ViewImports.cshtml 文件,以便将命名空间和标记帮助程序导入到该文件夹中的每个 Razor 页面中。

请考虑使用示例代码的“服务”区域,它不包含 _ViewImports.cshtml 文件。 以下标记显示 /Services/Manage/About Razor 页面:

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

<div>
  ViewData["routeInfo"]:  @ViewData["routeInfo"]
</div>

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

在前面的标记中:

  • 必须使用完全限定的类名来指定模型 (@model RPareas.Areas.Services.Pages.Manage.AboutModel)。
  • 标记帮助程序@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 启动

在示例下载中,“产品”区域包含下列 _ViewImports.cshtml 文件

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

以下标记显示 /Products/About Razor 页面:

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

在前面的文件中,命名空间和 @addTagHelper 指令通过 Areas/Products/Pages/_ViewImports.cshtml 文件导入到文件中。

有关详细信息,请参阅管理标记帮助程序范围导入共享指令

Razor Pages 区域的共享布局

要共享整个应用的常用布局,请将 _ViewStart.cshtml 移动到应用程序根文件夹

发布区域

当 *.csproj 文件中包含 <Project Sdk="Microsoft.NET.Sdk.Web"> 时,所有 *.cshtml 文件以及 wwwroot 目录中的文件都将发布到输出中

通过 Visual Studio 添加 MVC 区域

在“解决方案资源管理器”中,右键单击项目,选择“添加”>“新建基架项”,然后选择“MVC 区域”。

其他资源

区域是一项 ASP.NET 功能,用于将相关功能整理到一个组中,单独作为:

  • 用于路由的命名空间。
  • 用于视图和 Razor Pages 的文件夹结构。

使用区域会通过为 controlleraction 或 Razor 页面 page 添加另一个路由参数 area,创建用于路由目的的层次结构。

区域提供了一种将 ASP.NET Core Web 应用划分为更小的功能组的方法,每个功能组都有自己的一组 Razor Pages、控制器、视图和模型。 区域实际上是应用内的结构。 在 ASP.NET Core Web 项目中,Pages、模型、控制器和视图等逻辑组件保存在不同的文件夹中。 ASP.NET Core 运行时使用命名约定来创建这些组件之间的关系。 对于大型应用,将应用分区为独立的高级功能区域可能更有利。 例如,具有多个业务单位(如结账、计费、搜索等)的电子商务应用。 每个单位都有自己的区域,用于包含视图、控制器、Razor Pages 和模型。

如果发生以下情况,请考虑在项目中使用区域:

  • 应用由可以进行逻辑分隔的多个高级功能组件组成。
  • 想对应用进行分区,以便可以独立处理每个功能区域。

查看或下载示例代码如何下载)。 下载示例提供了用于测试区域的基本应用。

如果使用 Razor Pages,请参阅本文档中的使用 Razor Pages 的区域

带视图的控制器区域

使用区域、控制器和视图的典型 ASP.NET Core Web 应用包含以下内容:

  • 区域文件夹结构

  • 使用 [Area] 属性将自己与区域关联的控制器:

    [Area("Products")]
    public class ManageController : Controller
    {
    
  • 添加到启动的区域路由

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "MyArea",
            pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
    
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
    

区域文件夹结构

请考虑具有两个逻辑组(产品和服务)的应用。 使用区域,文件夹结构类似于以下内容:

  • 项目名称
    • Areas
      • Products
        • Controllers
          • HomeController.cs
          • ManageController.cs
        • 视图
          • Home
            • Index.cshtml
          • Manage
            • Index.cshtml
            • About.cshtml
      • Services
        • Controllers
          • HomeController.cs
        • 视图
          • Home
            • Index.cshtml

虽然前面的布局是使用区域时的典型布局,但只需要视图文件即可使用此文件夹结构。 视图发现按以下顺序搜索匹配的区域视图文件:

/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

将控制器与区域关联

区域控制器是使用 [Area] 特性指定的:

using Microsoft.AspNetCore.Mvc;
using Microsoft.Docs.Samples;

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

        public IActionResult About()
        {
            ViewData["routeInfo"] = ControllerContext.MyDisplayRouteInfo();
            return View();
        }
    }
}

添加区域路由

区域路由通常使用传统路由,而不使用特性路由。 传统路由依赖于顺序。 一般情况下,具有区域的路由应放在路由表中靠前的位置,因为它们比没有区域的路由更特定。

如果所有区域的 url 空间一致,则 {area:...} 可用作路由模板中的令牌:

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

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "MyArea",
            pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");

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

在前面的代码中,exists 应用了路由必须与区域匹配的约束。 将 {area:...}MapControllerRoute 结合使用:

  • 是将路由添加到区域的最简单的机制。
  • 将所有控制器与 [Area("Area name")] 特性相匹配。

以下代码使用 MapAreaControllerRoute 创建两个命名区域路由:

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

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapAreaControllerRoute(
            name: "MyAreaProducts",
            areaName: "Products",
            pattern: "Products/{controller=Home}/{action=Index}/{id?}");

        endpoints.MapAreaControllerRoute(
            name: "MyAreaServices",
            areaName: "Services",
            pattern: "Services/{controller=Home}/{action=Index}/{id?}");

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

有关详细信息,请参阅区域路由

示例下载中的以下代码显示指定区域的链接生成:

<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>

示例下载包括一个分部视图,其中包含:

  • 上面代码中所示的链接。
  • 与上述链接类似的链接,只是未指定 area

布局文件中引用部分视图,因此应用中的每个页面都显示生成的链接。 在未指定区域的情况下生成的链接仅在从同一区域和控制器中的页面引用时才有效。

如果未指定区域或控制器,路由取决于环境值。 当前请求的当前路由值被视为链接生成的环境值。 在许多情况下,对于示例应用,使用环境值将生成带有未指定区域的标记的错误链接。

有关详细信息,请参阅路由到控制器操作

使用 _ViewStart.cshtml 文件的共享区域布局

若要共享整个应用的常用布局,请将 _ViewStart.cshtml 保留在应用程序根文件夹中。 有关详细信息,请参阅 ASP.NET Core 中的布局

应用程序根文件夹

应用程序根文件夹是一个文件夹,其中包含了通过 ASP.NET Core 模板创建的 Web 应用中的 Startup.cs

_ViewImports.cshtml

MVC 的 /Views/_ViewImports.cshtml 以及 Razor Pages 的 /Pages/_ViewImports.cshtml,不导入到到区域中的视图。 使用以下任一方法将视图导入到所有视图中:

  • _ViewImports.cshtml 添加到应用程序根文件夹。 应用程序根文件夹中的 _ViewImports.cshtml 将应用到应用中的所有视图。
  • _ViewImports.cshtml 文件复制到区域下相应的视图文件夹中。

_ViewImports.cshtml 文件通常包含标记帮助程序导入、@using@inject 语句。 有关详细信息,请参阅导入共享指令

更改存储视图的默认区域文件夹

以下代码将默认的区域文件夹从 "Areas" 改为"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.AddControllersWithViews();
}

包含 Razor Pages 的区域

包含 Razor Pages 的区域要求在应用根目录中有一个 Areas/<area name>/Pages 文件夹。 以下文件夹结构用于示例应用

  • 项目名称
    • Areas
      • Products
        • Pages
          • _ViewImports
          • 关于
          • 索引
      • Services
        • Pages
          • Manage
            • 关于
            • 索引

示例下载中的以下代码显示指定区域(例如 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>

示例下载包含部分视图,该视图包含以前的链接和相同的链接(未指定区域)。 在布局文件中引用部分视图,因此应用中的每个页面都显示生成的链接。 在未指定区域的情况下生成的链接仅在从同一区域中的页引用时才有效。

如果未指定区域,路由取决于环境值。 当前请求的当前路由值被视为链接生成的环境值。 在许多情况下,对于示例应用,使用环境值会生成错误的链接。 例如,考虑从下面的代码生成的链接:

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

对于上述代码:

  • 只有当最后一个请求是针对 Services 区域的页时,从 <a asp-page="/Manage/About"> 生成的链接才是正确的。 例如,/Services/Manage//Services/Manage/Index/Services/Manage/About
  • 只有当最后一个请求是针对 /Home 中的页时,从 <a asp-page="/About"> 生成的链接才是正确的。
  • 代码摘自示例下载

使用 _ViewImports 文件导入命名空间和标记帮助程序

可以向每个区域 Pages 文件夹添加一个 _ViewImports.cshtml 文件,以便将命名空间和标记帮助程序导入到该文件夹中的每个 Razor Page。

请考虑使用示例代码的“服务”区域,它不包含 _ViewImports.cshtml 文件_ViewImports.cshtml。 以下标记显示 /Services/Manage/About Razor 页面:

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

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

在前面的标记中:

  • 必须使用完全限定的类名来指定模型 (@model RPareas.Areas.Services.Pages.Manage.AboutModel)。
  • 标记帮助程序@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 启动

在示例下载中,“产品”区域包含下列 _ViewImports.cshtml 文件_ViewImports.cshtml

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

以下标记显示 /Products/About Razor 页面:

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

在前面的文件中,命名空间和 @addTagHelper 指令通过 Areas/Products/Pages/_ViewImports.cshtml 文件导入到文件中。

有关详细信息,请参阅管理标记帮助程序范围导入共享指令

Razor Pages 区域的共享布局

要共享整个应用的常用布局,请将 _ViewStart.cshtml 移动到应用程序根文件夹_ViewStart.cshtml

发布区域

当 *.csproj 文件中包含 <Project Sdk="Microsoft.NET.Sdk.Web"> 时,所有 *.cshtml 文件以及 wwwroot 目录中的文件都将发布到输出中

通过 Visual Studio 添加 MVC 区域

在“解决方案资源管理器”中,右键单击项目,选择“添加”>“新建基架项”,然后选择“MVC 区域”。

区域是 ASP.NET 功能,用于将相关功能以单独的名称空间(用于路由)和文件夹结构(用于视图)的形式组织到一个组中。 使用区域会通过为 controlleraction 或 Razor 页面 page 添加另一个路由参数 area,创建用于路由目的的层次结构。

区域提供了一种将 ASP.NET Core Web 应用划分为更小的功能组的方法,每个功能组都有自己的一组 Razor Pages、控制器、视图和模型。 区域实际上是应用内的结构。 在 ASP.NET Core Web 项目中,Pages、模型、控制器和视图等逻辑组件保存在不同的文件夹中。 ASP.NET Core 运行时使用命名约定来创建这些组件之间的关系。 对于大型应用,将应用分区为独立的高级功能区域可能更有利。 例如,具有多个业务单位(如结账、计费、搜索等)的电子商务应用。 每个单位都有自己的区域,用于包含视图、控制器、Razor Pages 和模型。

如果发生以下情况,请考虑在项目中使用区域:

  • 应用由可以进行逻辑分隔的多个高级功能组件组成。
  • 想对应用进行分区,以便可以独立处理每个功能区域。

查看或下载示例代码如何下载)。 下载示例提供了用于测试区域的基本应用。

如果使用 Razor Pages,请参阅本文档中的使用 Razor Pages 的区域

带视图的控制器区域

使用区域、控制器和视图的典型 ASP.NET Core Web 应用包含以下内容:

  • 区域文件夹结构

  • 使用 [Area] 属性将自己与区域关联的控制器:

    [Area("Products")]
    public class ManageController : Controller
    {
    
  • 添加到启动的区域路由

    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?}");
    });
    

区域文件夹结构

请考虑具有两个逻辑组(产品和服务)的应用。 使用区域,文件夹结构类似于以下内容:

  • 项目名称
    • Areas
      • Products
        • Controllers
          • HomeController.cs
          • ManageController.cs
        • 视图
          • Home
            • Index.cshtml
          • Manage
            • Index.cshtml
            • About.cshtml
      • Services
        • Controllers
          • HomeController.cs
        • 视图
          • Home
            • Index.cshtml

虽然前面的布局是使用区域时的典型布局,但只需要视图文件即可使用此文件夹结构。 视图发现按以下顺序搜索匹配的区域视图文件:

/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

将控制器与区域关联

区域控制器是使用 [Area] 特性指定的:

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();
        }
    }
}

添加区域路由

区域路由通常使用传统路由,而不使用属性路由。 传统路由依赖于顺序。 一般情况下,具有区域的路由应放在路由表中靠前的位置,因为它们比没有区域的路由更特定。

如果所有区域的 url 空间一致,则 {area:...} 可用作路由模板中的令牌:

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?}");
    });
}

在前面的代码中,exists 应用了路由必须与区域匹配的约束。 使用 {area:...} 是将路由添加到区域的最简单的机制。

以下代码使用 MapAreaRoute 创建两个命名区域路由:

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?}");
    });
}

MapAreaRoute 与 ASP.NET Core 2.2 配合使用时,请参阅此 GitHub 问题

有关详细信息,请参阅区域路由

示例下载中的以下代码显示指定区域的链接生成:

<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>

使用上述代码生成的链接在应用的任何位置都有效。

示例下载包含部分视图,该视图包含以前的链接和相同的链接(未指定区域)。 在布局文件中引用部分视图,因此应用中的每个页面都显示生成的链接。 在未指定区域的情况下生成的链接仅在从同一区域和控制器中的页面引用时才有效。

如果未指定区域或控制器,路由取决于环境值。 当前请求的当前路由值被视为链接生成的环境值。 在许多情况下,对于示例应用,使用环境值会生成错误的链接。

有关详细信息,请参阅路由到控制器操作

使用 _ViewStart.cshtml 文件的共享区域布局

要共享整个应用的常用布局,请将 _ViewStart.cshtml 移动到应用程序根文件夹_ViewStart.cshtml

_ViewImports.cshtml

在其标准位置,/Views/_ViewImports.cshtml 不适用于区域/Views/_ViewImports.cshtml。 若要在区域中使用常用的标记帮助程序@using@inject,确保将正确的 _ViewImports.cshtml 文件应用于区域视图。 如果希望所有视图都具有相同的行为,请将 /Views/_ViewImports.cshtml 迁移到应用程序根/Views/_ViewImports.cshtml

更改存储视图的默认区域文件夹

以下代码将默认的区域文件夹从 "Areas" 改为"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();
}

包含 Razor Pages 的区域

包含 Razor Pages 的区域要求在应用根目录中有一个 Areas/<area name>/Pages 文件夹。 以下文件夹结构用于示例应用

  • 项目名称
    • Areas
      • Products
        • Pages
          • _ViewImports
          • 关于
          • 索引
      • Services
        • Pages
          • Manage
            • 关于
            • 索引

示例下载中的以下代码显示指定区域(例如 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>

使用上述代码生成的链接在应用的任何位置都有效。

示例下载包含部分视图,该视图包含以前的链接和相同的链接(未指定区域)。 在布局文件中引用部分视图,因此应用中的每个页面都显示生成的链接。 在未指定区域的情况下生成的链接仅在从同一区域中的页引用时才有效。

如果未指定区域,路由取决于环境值。 当前请求的当前路由值被视为链接生成的环境值。 在许多情况下,对于示例应用,使用环境值会生成错误的链接。 例如,考虑从下面的代码生成的链接:

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

对于上述代码:

  • 只有当最后一个请求是针对 Services 区域的页时,从 <a asp-page="/Manage/About"> 生成的链接才是正确的。 例如,/Services/Manage//Services/Manage/Index/Services/Manage/About
  • 只有当最后一个请求是针对 /Home 中的页时,从 <a asp-page="/About"> 生成的链接才是正确的。
  • 代码摘自示例下载

使用 _ViewImports 文件导入命名空间和标记帮助程序

可以向每个区域 Pages 文件夹添加一个 _ViewImports.cshtml 文件,以便将命名空间和标记帮助程序导入到该文件夹中的每个 Razor Page。

请考虑使用示例代码的“服务”区域,它不包含 _ViewImports.cshtml 文件_ViewImports.cshtml。 以下标记显示 /Services/Manage/About Razor 页面:

@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>

在前面的标记中:

  • 必须使用完全限定的域名来指定模型 (@model RPareas.Areas.Services.Pages.Manage.AboutModel)。
  • 标记帮助程序@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 启动

在示例下载中,“产品”区域包含下列 _ViewImports.cshtml 文件_ViewImports.cshtml

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

以下标记显示 /Products/About Razor 页面:

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

<h2>Products/About</h2>

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

在前面的文件中,命名空间和 @addTagHelper 指令通过 Areas/Products/Pages/_ViewImports.cshtml 文件导入到文件中。

有关详细信息,请参阅管理标记帮助程序范围导入共享指令

Razor Pages 区域的共享布局

要共享整个应用的常用布局,请将 _ViewStart.cshtml 移动到应用程序根文件夹_ViewStart.cshtml

发布区域

当 *.csproj 文件中包含 <Project Sdk="Microsoft.NET.Sdk.Web"> 时,所有 *.cshtml 文件以及 wwwroot 目录中的文件都将发布到输出中