程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> ASP.NET Core,asp.netcore

ASP.NET Core,asp.netcore

編輯:關於.NET

ASP.NET Core,asp.netcore


概述

大型 Web 應用比小型 Web 應用需要更好的組織。在大型應用中,ASP.NET MVC(和 Core MVC)所用的默認組織結構開始成為你的負累。你可以使用兩種簡單的技術來更新組織方法並及時跟進不斷增長的應用程序。

Model-View-Controller (MVC) 模式相當成熟,即使在 Microsoft ASP.NET 空間中亦是如此。第一版 ASP.NET MVC 在 2009 年推出,並且在今年夏初全面啟動了 ASP.NET Core MVC 平台。至今,隨著 ASP.NET MVC 的改進,默認項目結構已保持不變:“控制器”和“視圖”的文件夾,通常還有“模型”(或可能是 ViewModels)的文件夾。實際上,如果你現在新建 ASP.NET Core 應用,你會看到這些文件夾是由默認模板創建的,如圖 1 中所示。

圖 1 默認 ASP.NET Core Web 應用模板結構

 

該組織結構具有很多優點。它很熟悉;如果你在過去的幾年中一直使用 ASP.NET MVC 項目,你會很快識別它。它井然有序;如果你正在尋找某個控制器或視圖,你會很清楚從何處著手。當你開始進行一個新項目時,該組織結構運行良好,因為還沒有很多文件。但是,隨著項目的增加,有關定位所需控制器或在這些層中的不斷增長的文件和文件夾數目中查看文件的摩擦也隨之增加。

若要明白我的意思,請設想你在這種相同的結構中組織你的計算機文件。你並沒有不同項目或工作類型的單獨文件夾,而是只有完全由文件類型所組織的目錄。可能有針對文本文檔、PDF、圖像和電子表格的文件夾。當執行包含多種文檔類型的特殊任務時,你將需要在不同的文件夾之間來回跳動並在每個文件夾中的很多文件(這些文件與當前任務並不相關)中來回滾動和搜索。這正是以默認方式組織的 MVC 應用中功能的使用方式。

該方法的問題在於,通過類型(而非通過目的)組織的文件組往往缺少聚合。聚合是指一個模塊的元素共同所屬的程度。在典型的 ASP.NET MVC 項目中,給定的控制器將參考一個或多個相關視圖(在與控制器的名稱相對應的文件夾中)。控制器和視圖都將參考與控制器的責任相關的一個或多個 ViewModel。但是,通常情況下,ViewModel 類型或視圖很少被多種控制器類型使用(且通常情況下,域模型或持久性模型被移至其自己的單獨項目)。

 

示例項目

請考慮一個簡單的項目(管理 4 個松散並相關的應用程序概念的任務): Ninjas、Plants、Pirates 和 Zombies。實際示例僅允許你列出、查看並添加這些概念。但是,請設想在這裡有涉及更多視圖的其他復雜性。該項目的默認組織結構看上去應類似於圖 2

 

圖 2 使用默認組織的示例項目

若要使用包含 Pirates 的一些新功能,你需要導航到“控制器”並查找 PiratesController,然後從“視圖”依次導航到 Pirates 和相應的視圖文件。盡管只有 5 個控制器,但可以看到有很多上下移動的文件夾導航。當項目的根包含更多文件夾時,此問題更加嚴重,因為“控制器”和“視圖”並非按字母順序臨近彼此排列(因此其他文件夾往往會在文件夾列表中的這兩個文件夾間放置)。

通過文件類型組織文件的替代方法是按應用程序執行來對其進行組織。你的項目將圍繞功能或組織的區域來組織文件夾,以替代按控制器、模型和視圖組織的文件夾。當對應用的某個特定功能相關的 bug 或功能進行操作時,你需要將很少的文件夾處於打開狀態,因為相關文件可能被一起存儲。有多種方式可以實現此操作,包括對功能文件夾使用內置 Areas 功能和使用你自己的約定。

 

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 獲取。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved