ASP.NET Core - ASP.NET Core MVC 的功能划分

概述

大型 Web 应用比小型 Web 应用须要更好的组织。在大型应用中,ASP.NET MVC(和 Core MVC)所用的默认组织结构开始成为你的负累。你可使用两种简单的技术来更新组织方法并及时跟进不断增加的应用程序。 html

Model-View-Controller (MVC) 模式至关成熟,即便在 Microsoft ASP.NET 空间中亦是如此。初版 ASP.NET MVC 在 2009 年推出,而且在今年夏初全面启动了 ASP.NET Core MVC 平台。至今,随着 ASP.NET MVC 的改进,默认项目结构已保持不变:“控制器”和“视图”的文件夹,一般还有“模型”(或多是 ViewModels)的文件夹。实际上,若是你如今新建 ASP.NET Core 应用,你会看到这些文件夹是由默认模板建立的,如图 1 中所示。 git

图 1 默认 ASP.NET Core Web 应用模板结构 github

 

该组织结构具备不少优势。它很熟悉;若是你在过去的几年中一直使用 ASP.NET MVC 项目,你会很快识别它。它井井有理;若是你正在寻找某个控制器或视图,你会很清楚从何处着手。当你开始进行一个新项目时,该组织结构运行良好,由于尚未不少文件。可是,随着项目的增长,有关定位所需控制器或在这些层中的不断增加的文件和文件夹数目中查看文件的摩擦也随之增长。 web

若要明白个人意思,请设想你在这种相同的结构中组织你的计算机文件。你并无不一样项目或工做类型的单独文件夹,而是只有彻底由文件类型所组织的目录。可能有针对文本文档、PDF、图像和电子表格的文件夹。当执行包含多种文档类型的特殊任务时,你将须要在不一样的文件夹之间来回跳动并在每一个文件夹中的不少文件(这些文件与当前任务并不相关)中来回滚动和搜索。这正是以默认方式组织的 MVC 应用中功能的使用方式。 json

该方法的问题在于,经过类型(而非经过目的)组织的文件组每每缺乏聚合。聚合是指一个模块的元素共同所属的程度。在典型的 ASP.NET MVC 项目中,给定的控制器将参考一个或多个相关视图(在与控制器的名称相对应的文件夹中)。控制器和视图都将参考与控制器的责任相关的一个或多个 ViewModel。可是,一般状况下,ViewModel 类型或视图不多被多种控制器类型使用(且一般状况下,域模型或持久性模型被移至其本身的单独项目)。 服务器

 

示例项目

请考虑一个简单的项目(管理 4 个松散并相关的应用程序概念的任务): Ninjas、Plants、Pirates 和 Zombies。实际示例仅容许你列出、查看并添加这些概念。可是,请设想在这里有涉及更多视图的其余复杂性。该项目的默认组织结构看上去应相似于图 2app

 

图 2 使用默认组织的示例项目框架

若要使用包含 Pirates 的一些新功能,你须要导航到“控制器”并查找 PiratesController,而后从“视图”依次导航到 Pirates 和相应的视图文件。尽管只有 5 个控制器,但能够看到有不少上下移动的文件夹导航。当项目的根包含更多文件夹时,此问题更加严重,由于“控制器”和“视图”并不是按字母顺序临近彼此排列(所以其余文件夹每每会在文件夹列表中的这两个文件夹间放置)。 优化

经过文件类型组织文件的替代方法是按应用程序执行来对其进行组织。你的项目将围绕功能或组织的区域来组织文件夹,以替代按控制器、模型和视图组织的文件夹。当对应用的某个特定功能相关的 bug 或功能进行操做时,你须要将不多的文件夹处于打开状态,由于相关文件可能被一块儿存储。有多种方式能够实现此操做,包括对功能文件夹使用内置 Areas 功能和使用你本身的约定。 spa

 

ASP.NET Core MVC 如何查看文件

咱们有必要抽出一些时间来谈谈 ASP.NET Core MVC 如何与其使用的应用程序内置的标准类型文件一块儿工做。应用程序的服务器端中所涉及的大部分文件都将在某些 .NET 语言中分类编写。只要它们能够经过应用程序编译和引用,这些代码文件便可存在于磁盘上的任何位置。具体来讲,控制器类文件无需存储在任何特定文件夹中。各类模型类(域模型、视图模型、持久性模型等)都是相同的,它们均可以轻松地存在于 ASP.NET MVC Core 项目的单独的项目中。你能够对应用程序中的大部分代码文件按你想要的任何方式进行排列和从新排列。

可是,“视图”文件是不一样的。“视图”文件是内容文件。它们相对于应用程序的控制器类所存储的位置是不相关的,可是 MVC 须要知道在哪里能够找到它们,这一点很重要。与默认的“视图”文件夹相比,Areas 提供对不一样区域中定位视图的内置支持。你也能够对 MVC 肯定视图位置的方式进行自定义。

 

使用 Areas 组织 MVC 项目

Areas 提供在 ASP.NET MVC 应用程序内组织独立模块的方式。每一个 Area 都具备一个模拟项目根约定的文件夹结构。所以,你的 MVC 应用程序应具备相同的根文件夹约定和称为 Areas 的额外文件夹,其中包含一个应用的每一个部分的文件夹,它包括“控制器”和“视图”的文件夹(根据须要,可能还包括“模型”或“ViewModels”文件夹)。

Areas 具备强大的功能:容许你将某一大型应用程序细分为单独且逻辑上合理的不一样的子应用程序。例如,控制器能够具备跨区域相同的名称,实际上,在应用程序内的每一个区域中具备 HomeController 类是很常见的。

若要添加对 ASP.NET MVC Core 项目的 Areas 的支持,只需新建一个名为“Areas”的根级文件夹。在该文件夹中,为你想要在 Area 内组织的应用程序的每一个部分新建一个文件夹。而后,在该文件夹内,为“控制器”和“视图”新添文件夹。

所以,你的控制器文件应位于:

/Areas/[area name]/Controllers/[controller name].cs

你的控制器需具备对其适用的 Area 属性,以使框架知道它们属于某个特定区域内:

namespace WithAreas.Areas.Ninjas.Controllers
{
  [Area("Ninjas")]
  public class HomeController : Controller

而后,你的视图应位于:

/Areas/[area name]/Views/[controller name]/[action name].cshtml

应更新已移至区域中的视图的任何连接。若是你正在使用标记帮助程序,则能够将区域名称指定为标记帮助程序的一部分。例如,

<a asp-area="Ninjas" asp-controller="Home" asp-action="Index">Ninjas</a>

同一区域内的视图间的连接可省略 asp-­area 属性。

须要对支持你的应用中的区域所作的最后一件事是,在“配置”方法中对 Startup.cs 中的应用程序的默认路由规则进行更新:

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

例如,管理各类 Ninjas、Pirates 等的示例应用程序能够利用 Areas 来实现项目组织结构,如图 3 中所示。

 

图 3 使用 Areas 组织 ASP.NET Core 项目

 

相对于默认约定,Areas 功能经过为应用程序的每一个逻辑部分提供单独的文件夹提供了改进。Areas 是 ASP.NET Core MVC 中的内置功能,它须要最少的设置。若是你还未使用它们,请记得它们是将你的应用的相关部分组合在一块儿并从应用的剩余部分分离的一个简单的方法。

可是,Areas 组织的文件夹仍然负荷很重。你能够在所需的垂直空间中看到这一点,它显示了 Areas 文件夹中相对较小的文件数量。若是你的每一个区域并无不少控制器,且你的每一个控制器中并无不少视图,则上面的这个文件夹带来的麻烦与使用默认约定几乎是相同的。

幸运的是,你能够轻松地建立你本身的约定。

 

ASP.NET Core MVC 中的功能文件夹

在默认文件夹约定或内置 Areas 功能使用之外,组织 MVC 项目最热门的方法是使用每一个功能的文件。对已在垂直细分中采用了交付功能的团队尤为如此(可参阅 http://deviq.com/vertical-slices/),由于大部分垂直细分的用户界面关注点能够存在于任一功能文件夹中。

经过功能(而非经过文件类型)组织你的项目时,一般会有一个根文件夹(如“功能”),其中会有每一个功能的子文件夹。这与组织 areas 的方法很是类似。可是,在每一个功能文件夹内,你将包括全部所需的控制器、视图和 ViewModel 类型。在大部分应用程序中,这会致使文件夹中有 5 到 15 个项目,全部这些项目都紧密相关。功能文件夹的整个内容均可以保留在解决方案资源管理器中,以供查看。你能够在图 4 中看到有关此示例项目的组织的示例。

图 4 功能文件夹组织

请注意,即便根级别“控制器”和“视图”文件夹也被消除了。如今,应用的主页位于名为“主页”的其本身的功能文件夹中,而共享文件(如 _Layout.cshtml)也位于“功能”文件夹内的“共享”文件夹。该项目组织结构扩展性很好,使开发人员在进行应用程序的某个特定部分时,能够将其注意力集中在更少的文件夹上。

在此示例中,与使用 Areas 的不一样之处在于,并不须要控制器的其余路由和属性(但须要注意的是,该控制器名称必须在该实施中的功能间是惟一的)。若要支持此组织,须要自定义 IViewLocationExpander 和 IControllerModelConvention。将二者与一些自定义 ViewLocationFormats 一块儿使用,以配置你的“启动”类中的 MVC。

对于给定的控制器,了解它与什么功能关联是颇有用的。Areas 经过使用属性来实现此目的,而该方法使用约定。约定设定控制器位于名为“功能”的命名空间中,而在“功能”后的命名空间层中的下一项是功能名称。该名称被添加到属性(在视图位置过程当中可用),如图 5 所示。

public class FeatureConvention: IControllerModelConvention
{
  public void Apply(ControllerModel controller)
  {
    controller.Properties.Add("feature", 
      GetFeatureName(controller.ControllerType));
  }
  private string GetFeatureName(TypeInfo controllerType)
  {
    string[] tokens = controllerType.FullName.Split('.');
    if (!tokens.Any(t => t == "Features")) return "";
    string featureName = tokens
      .SkipWhile(t => !t.Equals("features",
        StringComparison.CurrentCultureIgnoreCase))
      .Skip(1)
      .Take(1)
      .FirstOrDefault();
    return featureName;
  }
}

图 5 FeatureConvention: IControllerModelConvention

 

在启动中添加 MVC 时,将该约定添加为 MvcOptions 的一部分:

services.AddMvc(o => o.Conventions.Add(new FeatureConvention()));

若要将 MVC 使用的正常视图位置逻辑替换为基于功能的约定,你能够清除由 MVC 使用的 View­LocationFormats,并将其替换为你本身的列表。该操做做为 AddMvc 调用的一部分执行,如图 6 中所示。

services.AddMvc(o => o.Conventions.Add(new FeatureConvention()))
  .AddRazorOptions(options =>
  {
    // {0} - Action Name
    // {1} - Controller Name
    // {2} - Area Name
    // {3} - Feature Name
    // Replace normal view location entirely
    options.ViewLocationFormats.Clear();
    options.ViewLocationFormats.Add("/Features/{3}/{1}/{0}.cshtml");
    options.ViewLocationFormats.Add("/Features/{3}/{0}.cshtml");
    options.ViewLocationFormats.Add("/Features/Shared/{0}.cshtml");
    options.ViewLocationExpanders.Add(new FeatureViewLocationExpander());
  }

图 6 替换由 MVC 使用的正常视图位置逻辑

默认状况下,这些格式字符串包含操做的占位符(“{0}”)、控制器(“{1}”)和区域(“{2}”)。该方法添加功能的第 4 个标记(“{3}”)。

使用的视图位置格式应支持在功能内具备相同名称但由不一样控制器使用的视图。例如,在功能中具备多个控制器,而且多个控制器具备一个索引方法,这是很常见的。经过在与控制器名称匹配的文件夹中搜索视图支持该功能。所以,NinjasController.Index 和 SwordsController.Index 应在各自的 /Features/Ninjas/Ninjas/Index.cshtml 和 /Features/Ninjas/Swords/Index.cshtml 中定位视图(参见图 7)。

图 7 每一个功能的多个控制器

请注意,该功能是可选的 - 若是你的功能没有必要区分视图(例如,由于功能仅有一个控制器),则只需将视图直接置入功能文件夹中。一样,相较于文件夹,你更愿意使用文件前缀,则只需轻松地将格式字符串从“{3}/{1}”调整为“{3}{1}”以供使用,从而产生视图文件名,如 NinjasIndex.cshtml 和 SwordsIndex.cshtml。

功能文件夹的根中和共享子文件夹中均支持共享视图。

IViewLocationExpander 界面提供了一个 ExpandViewLocations 方法(框架使用该方法识别包含视图的文件夹)。当操做返回视图时将搜索这些文件夹。该方法仅须要 ViewLocation­Expander 将“{3}”标记替换为控制器的功能名称(由前面提到的 FeatureConvention 指定):

public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context,
  IEnumerable<string> viewLocations)
{
  // Error checking removed for brevity
  var controllerActionDescriptor =
    context.ActionContext.ActionDescriptor as ControllerActionDescriptor;
  string featureName = controllerActionDescriptor.Properties["feature"] as string;
  foreach (var location in viewLocations)
  {
    yield return location.Replace("{3}", featureName);
  }
}

若要正确支持发布,你还须要更新 project.json 的 publishOptions 以包括“功能”文件夹:

"publishOptions": {
  "include": [
    "wwwroot",
    "Views",
    "Areas/**/*.cshtml",
    "Features/**/*.cshtml",
    "appsettings.json",
    "web.config"
  ]
},

使用名为“功能”的文件夹的新约定以及文件夹在其中的组织方式彻底由你控制。经过修改 View­LocationFormats 集(或者 FeatureViewLocationExpander 类型的行为),你能够彻底控制你的应用的视图所处的位置,这是从新组织你的文件惟一须要的操做,由于控制器类型不管位于哪一个文件夹中均会被发现。

 

并行功能文件夹

若是想要与默认 MVC Area 和视图约定并行尝试功能文件夹,只需很小的修改便可实现此功能。将功能格式插入列表起始位置来替代清除 ViewLocationFormats(注意顺序是颠倒的):

options.ViewLocationFormats.Insert(0, "/Features/Shared/{0}.cshtml");
options.ViewLocationFormats.Insert(0, "/Features/{3}/{0}.cshtml");
options.ViewLocationFormats.Insert(0, "/Features/{3}/{1}/{0}.cshtml");

若要支持与区域组合的功能,则对 AreaViewLocationFormats 集合也进行修改:

options.AreaViewLocationFormats.Insert(0, "/Areas/{2}/Features/Shared/{0}.cshtml");
options.AreaViewLocationFormats.Insert(0, "/Areas/{2}/Features/{3}/{0}.cshtml");
options.AreaViewLocationFormats.Insert(0, "/Areas/{2}/Features/{3}/{1}/{0}.cshtml");

如何处理模型?

目光敏锐的读者将会注意到,我并无将个人模型类型移入功能文件夹(或 Areas)。在该示例中,我没有单独的 ViewModel 类型,由于我正在使用的模型极其简单。在实际的应用中,你的域或持久性模型所具备的复杂性可能超过你的视图所需的复杂性,此种状况将在单独项目中由其本身定义。你的 MVC 应用可能会定义仅包含给定视图所需数据的 ViewModel 类型,针对显示进行了优化(或由客户端的 API 请求使用)。毫无疑问,这些 ViewModel 类型应置于它们被使用的功能文件夹中(这些类型在功能间被共享的状况应当是不多见的)。

总结

示例包括 NinjaPiratePlant­Zombie 组织者应用程序的全部三个版本,并支持添加和查看每种数据类型。下载示例(或在 GitHub 上查看该示例)并思考每种方法在你如今所使用的应用程序的上下文中的运行方式。试验将 Area 或功能文件夹添加到你所使用的一个大型应用程序中,并肯定与使用基于文件类型的顶级文件夹相比,你是否更愿意将功能细分做为你的应用的文件夹结构的顶级组织使用。

此示例的源代码可经过 https://github.com/smallprogram/OrganizingAspNetCore 获取。

相关文章
相关标签/搜索