2016 年 9 月

第 31 卷,第 9 期

本文章是由機器翻譯。

ASP.NET Core - ASP.NET Core MVC 的 Feature Slices

Steve Smith

大型的 Web 應用程式需要比小型的較佳的組織。大型應用程式中,開始針對您使用 ASP.NET MVC (和核心 MVC) 的預設組織結構。您可以使用兩個簡單的技巧來更新您的組織方法,並趕上日益增加的應用程式。

模型檢視控制器 (MVC) 模式是成熟,即使是在 Microsoft ASP.NET 空間。ASP.NET MVC 的第一個版本發行在 2009 年,第一個完整的重新開機,平台,ASP.NET 核心 MVC 的出貨早期今年夏天的。這次請在 ASP.NET MVC 發展,因為預設專案結構已將維持不變︰ 控制器和檢視表,且經常模型 (或可能是 Viewmodel) 的資料夾。事實上,如果您立即建立新的 ASP.NET 核心應用程式,您會看到預設範本,建立這些資料夾中所示 [圖 1

預設的 ASP.NET 核心 Web 應用程式範本結構
[圖 1 預設 ASP.NET 核心 Web 應用程式範本結構

有許多優點,此組織結構。十分熟悉。如果您所處理的 ASP.NET MVC 專案,在過去幾年來,您會立即辨識。它有經過組織。如果您要尋找的控制器或檢視,您必須是個好主意要從何處開始。當您開始新的專案、 組織結構都相當好,因為沒有尚未多檔案。隨著專案變大,不過,也會參與這些階層中尋找所需的控制器或檢視表檔案內持續增加的數字的檔案和資料夾的摩擦。

若要查看我的意思,想像一下是否您的組織您電腦中的檔案相同的結構。不需要針對不同專案或種類的工作不同的資料夾,您必須完全依哪種檔案的目錄。可能的文字文件、 Pdf、 影像和試算表的資料夾。當處理牽涉到多個文件類型的特定工作,您需要不同的資料夾和捲動或搜尋不相關的工作,每個資料夾中的許多檔案之間保持彈跳。這就是如何在 MVC 應用程式中的預設方式組織內的功能。

發生這種問題的原因是群組的檔案類型,而不是目的,組織通常缺乏一致性。一致性指的是一個模組的項目屬於在一起的程度。在典型的 ASP.NET MVC 專案中,指定的控制器會參考其中一個或多個相關檢視 (在資料夾,對應至控制器的名稱)。控制器和檢視將會參考其中一個或多個 ViewModels 控制站的責任。一般而言,不過幾個 ViewModel 類型或檢視表由一個以上的控制器類型 (和網域模型或持續性模型通常,移至其自己的個別專案)。

範例專案

簡單的專案管理這四個鬆散相關的應用程式的概念,請考慮︰ 忍、 植物、 海盜及 Zombie。實際的範例只能讓您列出、 檢視以及新增這些概念。不過,假設沒有額外的複雜性會牽涉多個檢視。此專案的預設組織結構看起來就像 [圖 2

預設的組織具有範例專案
[圖 2 範例專案,以預設的組織

若要處理新有點涉及海盜的功能,您必須往下瀏覽到控制站和尋找 PiratesController,然後瀏覽下檢視到海盜到適當的檢視表檔案。即使有五個控制站,您可以看到,很多資料夾上的導覽。這通常會更糟的是當專案根目錄中包含許多其他的資料夾,控制器和檢視無法彼此靠近字母順序排列 (因此通常介於這兩個資料夾清單中的其他資料夾)。

組織依其類型的檔案的替代方法是將它們組織順著應用程式的用途。而不是用於控制站、 模型和檢視的資料夾,您的專案會有資料夾組織功能或責任範圍。當處理的 bug 或應用程式的特定功能的相關的功能,您需要保留較少資料夾開啟,因為相關的檔案無法一起儲存。這可以完成許多種,包括使用內建的 [區域] 功能,輪流自己慣例功能資料夾中。

ASP.NET Core MVC 看到檔案的方式

值得花點時間來討論 ASP.NET 核心 MVC 如何使用標準的建置中的應用程式使用的檔案類型。大部分的伺服器端的應用程式中包含的檔案都要以某個.NET 語言撰寫的類別。只要可以編譯和應用程式參考,這些程式碼檔案可以隨處存留在磁碟上。特別是,不需要控制器類別檔案儲存在任何特定的資料夾。各種模型類別 (網域模型、 檢視模型、 持續性模型等等) 都相同,並可以輕鬆地存在於不同的專案,從 ASP.NET MVC 核心專案中。您可以排列,並重新整理大多數的程式碼檔案,無論您要在應用程式中。

檢視,然而,有不同。檢視是內容檔案。相對於應用程式的控制器類別儲存在為無關,但很重要,MVC 知道要去哪裡尋找它們。區域會提供內建支援,以便在不同的位置,比預設的 [檢視] 資料夾檢視。您也可以自訂 MVC 如何判斷檢視表的位置。

組織使用區域的 MVC 專案

區域則提供組織中的 ASP.NET MVC 應用程式的獨立模組。每個區域有模擬專案根慣例的資料夾結構。因此,MVC 應用程式會有相同的根資料夾慣例,而其他的資料夾稱為的領域,是每個區段的應用程式中的一個資料夾中的資料夾包含的控制器和檢視 (和或許模型或 ViewModels,如有需要)。

區域是一項強大功能,可讓您區隔至個別的邏輯子應用程式的大型應用程式。控制站,比方說,可以有相同名稱跨區域,而事實上,這是很常見的應用程式中的每個區域在 HomeController 類別。

若要將支援區域加入 ASP.NET MVC 核心的專案,您只需要建立新的根資料夾,稱為區域。在這個資料夾中,建立您想要在某個區域中組織的應用程式的每個部分的新資料夾。然後,在此資料夾中,將新資料夾新增控制器和檢視。

因此應該位於您的控制器檔案中︰

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

您的控制站需要套用它們,讓架構知道其所屬的特定區域內的區域屬性︰

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 區域屬性。

您需要執行,應用程式中支援區域的最後一件事是更新 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?}");
});

例如,管理各種忍、 海盜等等的範例應用程式無法利用區域,以達到專案的組織結構中所示 [圖 3

組織區域的 ASP.NET 核心專案
[圖 3 組織區域的 ASP.NET 核心專案

「 區域 」 功能提供方法是提供應用程式的每個邏輯區段的個別資料夾的預設慣例一大改進。區域是 ASP.NET 核心 MVC,需要最少的安裝中的內建功能。如果您未使用它們,記住它們就可以輕鬆組成群組的應用程式的相關的章節,並與其他應用程式分開來。

不過,區域組織仍然是非常頻繁的資料夾。您可以檢視此檔案的相對較小的數字顯示區域的資料夾中所需的垂直空間中。如果您不需要每個區域的多個控制站,而且您不需要每個控制器的許多檢視,此資料夾的額外負荷可能人事非常類似的方式加入依照預設慣例。

幸運的是,您可以輕鬆地建立您自己的慣例。

在 ASP.NET 核心 MVC 功能資料夾

以外的預設資料夾慣例或使用內建的 「 區域 」 功能,最常見的方式來組織 MVC 專案,而且每個功能的資料夾。這特別適用於團隊採用垂直配量中提供的功能 (請參閱 bit.ly/2abpJ7t),因為大部分的垂直配量 UI 考量可以存在於其中一個功能資料夾。

組織您的專案功能時 (而不是由檔案類型,) 通常會有根資料夾 (例如功能),您將在其中有每項功能的子資料夾。這是非常類似於區域的組織方式。不過,在每個功能資料夾中,您要包含的所有必要的控制器、 檢視和 ViewModel 型別。在大多數的應用程式,這會導致資料夾可能是 5 到 15 中的項目,其中都密切相關另一台。功能資料夾的整個內容可以在檢視中保留在 [方案總管] 中。您可以看到此組織中的範例專案範例 [圖 4

功能資料夾組織
[圖 4 功能資料夾組織

請注意,即使是根層級控制站,並檢視資料夾已消除。應用程式的首頁現在是在其自己功能資料夾中名為 「 主,和共用的檔案,如 _Layout.cshtml 位於 [功能] 資料夾,以及內的共用資料夾中。此專案的組織結構,調整,並可讓開發人員保持為其少很多資料夾上的應用程式的特定區段中工作時。

在此範例中,不同於區域中,沒有額外的路由都需要,控制站 (但請注意,控制站名稱必須是唯一在此實作的功能之間) 所需的任何屬性。若要支援此組織,您需要自訂的 IViewLocationExpander 和 IControllerModelConvention。這些同時使用,以及一些自訂的 ViewLocationFormats MVC 設定在您啟動的類別。

針對指定的控制站,是很有幫助與其相關聯的功能。區域達到此目的使用屬性。這個方法會使用一種命名慣例。慣例會預期控制器之後 「 功能 」 功能名稱必須以命名空間,稱為 「 功能 」,並針對命名空間階層架構中的下一個項目。這個名稱會加入至可用檢視的位置,在屬性中所示 [圖 5

[圖 5 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;
  }
}

新增 MVC 在啟動時,您可以加入 MvcOptions 一部分的這個慣例︰

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

若要使用 MVC 功能為基礎的慣例的標準檢視位置邏輯,您可以清除 ViewLocationFormats MVC 所使用的清單,取代您自己的清單。這是 AddMvc 呼叫中,過程中所示 [圖 6

[圖 6 取代一般檢視使用 MVC 位置邏輯

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

根據預設,這些格式字串會包含預留位置的動作 ("{0}")、 控制站 ("{1 \}"),以及領域 ("{2 \}")。這個方法會加入第四個語彙基元 ("{3 \}") 的功能。

使用的檢視位置格式應該支援具有相同名稱的檢視,但使用不同的控制器在 feature 內。比方說,是相當常見,一項功能,而且有 Index 方法的多個控制站有多個控制站。支援此功能,藉由搜尋相符的控制站名稱的資料夾中的檢視。因此,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,由架構用來識別包含檢視的資料夾。動作傳回的檢視時,會搜尋這些資料夾。這個方法只需要將稍早所述的 FeatureConvention 所指定的控制站的功能名稱取代成"\ {3 \"語彙基元 ViewLocationExpander:

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"
  ]
},

新的慣例,使用名為功能的資料夾的完全是在您的控制,以及資料夾內的組織方式。藉由修改的一組 ViewLocationFormats (和可能 FeatureViewLocationExpander 類型的行為),您可以完全控制透過檢視您的應用程式的所在位置,也就是只需要重新組織您的檔案,因為控制器類型會探索無論他們所處的資料夾。

並存功能資料夾

如果您想要試試功能資料夾-並存預設 MVC 區域和檢視慣例,您可以這樣使用小型的修改。而非清除 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");

模型呢?

那些精明的讀者會發現我並未將我的模型型別移到功能資料夾 (或區域)。在此範例中,我不需要不同的 ViewModel 類型,因為我使用的模型都非常簡單。在真實世界應用程式中,很有可能您的網域或持續性模型將會有更複雜,需要您的檢視,而且它會定義在它自己,個別的專案。MVC 應用程式可能會定義包含所需的特定檢視中,顯示 (或從用戶端的 API 要求的耗用量) 的最佳化資料的 ViewModel 型別。這些 ViewModel 類型絕對應放在功能所在的資料夾及其使用 (它應該很少見的功能之間會共用這些型別)。

總結

此範例包含所有三個 NinjaPiratePlantZombie 組合管理應用程式,以及加入和檢視每個資料類型的支援版本。下載 (或檢視 GitHub 上) 和想法每一種方法會立即處理的應用程式的內容中的運作方式。嘗試將區域或功能資料夾加入至較大的應用程式上運作,並決定是否您想為您的應用程式資料夾結構,而不最上層資料夾的檔案類型為基礎的最上層的組織使用功能配量。

此範例的原始程式碼位於 bit.ly/29MxsI0


Steve Smith 是獨立的訓練、 指導和顧問,以及 ASP.NET 的 MVP。 他有貢獻的數十個到正式的 ASP.NET 核心文件的文件 (docs.asp.net),並協助小組快速掌握使用 ASP.NET 的核心。他的連絡 ardalis.com


感謝以下協助校閱本篇文章的技術專家: Ryan Nowak
Ryan Nowak 是使用 Microsoft ASP.NET 團隊的開發人員。