“第一次把它弄好並不重要。 最後一次正確完成它至關重要。 - 安德魯·亨特和大衛·湯瑪斯
ASP.NET Core 是一種跨平臺的開放原始碼架構,可用於建置現代化雲端優化的 Web 應用程式。 ASP.NET Core 應用程式是輕量型且模組化的,內建支援相依性插入,可提高可測試性和可維護性。 結合MVC,除了以檢視為基礎的應用程式之外,還支援建置現代化Web API,ASP.NET Core 是建置企業Web應用程式的強大架構。
MVC 和 Razor 頁面
ASP.NET Core MVC 提供許多功能,可用於建置 Web 型 API 和應用程式。 MVC 一詞代表「Model-View-Controller」,這是一種 UI 模式,可分解將回應使用者要求的責任分成數個部分。 除了遵循此模式之外,您也可以將 ASP.NET Core 應用程式中的功能實作為 Razor Pages。
Razor Pages 內建於 ASP.NET Core MVC 中,並使用相同的功能來路由、模型系結、篩選、授權等。不過,Razor Pages 會根據控制器、模型、檢視等的相對位置,而不是使用不同的資料夾和檔案,而是使用以屬性為基礎的路由,將Razor Pages 放在單一資料夾(“/Pages”),並根據其相對位置在此資料夾中路由,並使用處理程式來處理要求,而不是控制器動作。 因此,使用 Razor Pages 時,您需要的所有檔案和類別通常都集中在一起,而不會分散到整個網頁專案中。
深入瞭解 如何在 eShopOnWeb 範例應用程式中套用MVC、Razor Pages 和相關模式。
當您建立新的 ASP.NET Core 應用程式時,應該要有一個想要建置之應用程式類型的計劃。 在 IDE 中或使用 dotnet new CLI 命令建立新專案時,您會從數個範本中選擇。 最常見的專案範本為 Empty、Web API、Web App 和 Web App(Model-View-Controller)。 雖然您只能在第一次建立專案時做出此決策,但這不是不可撤銷的決定。 Web API 專案使用標準的模型View-Controller 控制器,但預設沒有視圖。 同樣地,預設 Web 應用程式範本會使用 Razor Pages,因此也缺少 Views 資料夾。 您可以稍後將 Views 資料夾新增至這些專案,以支援以檢視為基礎的行為。 Web API 和模型View-Controller 項目預設不包含Pages資料夾,但您可以稍後新增一個,以支援以Razor Pages為基礎的行為。 您可以將這三個範本視為支援三種不同類型的預設使用者互動:數據(Web API)、網頁型和以檢視為基礎的。 不過,如果您想要的話,您可以混合並比對單一專案中的任何或所有範本。
為什麼是Razor Pages?
Razor Pages 是 Visual Studio 中新 Web 應用程式的預設方法。 Razor Pages 提供更簡單的方式來建置頁面型應用程式功能,例如非 SPA 表單。 使用控制器和檢視時,應用程式通常會有非常大型的控制器,這些控制器使用許多不同的相依性和檢視模型,並傳回許多不同的檢視。 這會導致更複雜的問題,而且通常導致沒有遵循單一責任原則或有效開放/封閉原則的控制器。 Razor Pages 會使用其 Razor 標記,在 Web 應用程式中封裝給定邏輯「頁面」的伺服器端邏輯,以解決此問題。 沒有伺服器端邏輯的Razor頁面只能包含Razor檔案(例如“Index.cshtml”。 不過,大多數較複雜的 Razor Pages 都會有相關聯的頁面模型類別,其命名依慣例會與具有 “.cs” 副檔名的 Razor 檔案相同(例如,“Index.cshtml.cs”)。
Razor Page 的頁面模型結合了MVC控制器和 ViewModel 的責任。 默認會執行 「OnGet()」等頁面模型處理程式,而不是使用控制器動作方法來處理要求,而是轉譯其相關聯的頁面。 Razor Pages 可簡化在 ASP.NET Core 應用程式中建置個別頁面的程式,同時仍然提供 ASP.NET Core MVC 的所有架構功能。 它們是新頁面式功能的良好預設選擇。
使用MVC的時機
如果您要建置 Web API,MVC 模式比嘗試使用 Razor 頁面更有意義。 如果您的專案只會公開 Web API 端點,您應該最好從 Web API 專案範本開始。 否則,輕鬆地將控制器和相關聯的 API 端點新增至任何 ASP.NET Core 應用程式。 如果您要將現有的應用程式從 ASP.NET MVC 5 或更早版本移轉至 ASP.NET Core MVC,而且想要以最少的努力執行,請使用以檢視為基礎的 MVC 方法。 進行初始移轉之後,您可以評估是否適合採用Razor Pages進行新功能,或甚至是大規模移轉。 如需將 .NET 4.x 應用程式移植到 .NET 8 的詳細資訊,請參閱 將現有的 ASP.NET 應用程式移植到 ASP.NET Core 電子書。
無論您選擇使用Razor Pages 或MVC檢視來建置 Web 應用程式,您的應用程式會有類似的效能,而且將包含相依性插入、篩選、模型系結、驗證等支援。
將請求映射至響應
ASP.NET Core 應用程式的核心功能是將傳入的要求對應至傳出的回應。 在低層級上,此對應是使用中間件完成,而簡單的 ASP.NET Core 應用程式和微服務可能只包含自定義中間件。 使用 ASP.NET Core MVC 時,您可以在稍微較高的層級工作,並考慮 路由、 控制器和 動作。 每個連入要求都會與應用程式的路由表進行比較,如果找到相符的路由,則會呼叫相關聯的動作方法(屬於控制器),以處理要求。 如果找不到相符的路由,則會呼叫錯誤處理程式(在此情況下,傳回 NotFound 結果)。
ASP.NET Core MVC 應用程式可以使用傳統路由、屬性路由或兩者。 傳統路由是在程式代碼中定義,使用語法來指定路由 慣例 ,如下列範例所示:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
});
在此範例中,名為 「default」 的路由已新增至路由表。 它使用controller、 action及id的佔位元來定義路由範本。
controller和action佔位符指明了預設值(Home和Index),而id佔位符是選擇性的(透過套用 "?" 至它)。 這裡定義的慣例指出,要求的第一個部分應該對應至控制器的名稱、動作的第二個部分,然後在必要時,第三個部分將代表標識元參數。 傳統路由通常在應用程式中某個地方定義,例如在 Program.cs 中配置請求中間件管線。
屬性路由會直接套用至控制器和動作,而不是全域指定。 當您查看特定方法時,這種方法的優點是使它們更容易被發現,但這意味着路由資訊不會集中保留在應用程式中。 透過屬性路由,您可以輕鬆地指定指定動作的多個路由,以及結合控制器和動作之間的路由。 例如:
[Route("Home")]
public class HomeController : Controller
{
[Route("")] // Combines to define the route template "Home"
[Route("Index")] // Combines to define route template "Home/Index"
[Route("/")] // Does not combine, defines the route template ""
public IActionResult Index() {}
}
路由可以在 [HttpGet] 和類似的屬性上指定,以避免需要新增個別的 [Route] 屬性。 屬性路由也可以使用令牌來減少重複控制器或動作名稱的需求,如下所示:
[Route("[controller]")]
public class ProductsController : Controller
{
[Route("")] // Matches 'Products'
[Route("Index")] // Matches 'Products/Index'
public IActionResult Index() {}
}
Razor 頁面不使用屬性路由。 您可以將 Razor 頁面的其他路由模板資訊指定為其 @page 指令的一部分。
@page "{id:int}"
在上一個範例中,所討論的頁面會比對一個包含整數 id 參數的路由。 例如,位於根目錄中的/Pages頁面會回應如下要求:
/Products/123
一旦指定的要求與路由相符,但在呼叫動作方法之前,ASP.NET Core MVC 將會對要求執行 模型系結 和 模型驗證 。 模型系結負責將連入 HTTP 資料轉換成指定為要呼叫之動作方法參數的 .NET 類型。 例如,如果動作方法需要 int id 參數,模型系結會嘗試從要求中提供的值提供此參數。 若要這樣做,模型系結會尋找提交表單中的參數值、路由本身中的參數值,以及查詢字串中的參數值。 假設找到值 id ,則會在傳遞至 action 方法之前,將它轉換成整數。
系結模型之後,但在呼叫動作方法之前,就會發生模型驗證。 模型驗證會在模型類型上使用選擇性屬性,並可協助確保提供的模型物件符合特定數據需求。 某些值可指定為必要值,或限制為特定長度或數值範圍等等。如果已指定驗證屬性,但模型不符合其需求,則 Property ModelState.IsValid 會是 false,而且一組失敗的驗證規則將可用來傳送給發出要求的用戶端。
如果您使用模型驗證,請務必一律檢查模型是否有效,再執行任何狀態改變命令,以確保您的應用程式不會因無效的數據損毀。 您可以使用 篩選 條件,避免需要在每個動作中新增此驗證的程序代碼。 ASP.NET Core MVC 篩選器提供攔截要求群組的方式,以便以目標為基礎套用一般原則和交叉考慮。 篩選可以套用至個別動作、整個控制器,或針對應用程式全域套用。
針對 Web API,ASP.NET Core MVC 支援 內容交涉,允許要求指定應如何格式化回應。 根據要求中提供的標頭,傳回數據的動作會以 XML、JSON 或其他支援的格式格式化回應。 這項功能可讓多個用戶端使用不同的數據格式需求來使用相同的 API。
Web API 專案應考慮使用 [ApiController] 屬性,此屬性可以套用至個別控制器、基底控制器類別或整個元件。 此屬性會新增自動模型驗證檢查,且任何具有無效模型的動作都會傳回 BadRequest,其中包含驗證錯誤的詳細數據。 屬性也需要所有動作都有屬性路由,而不是使用傳統路由,並傳回更詳細的 ProblemDetails 資訊以回應錯誤。
持續掌控控制器
對於以頁面為基礎的應用程式,Razor Pages 能有效防止控制器變得過於龐大。 每個個別頁面都會提供專屬於其處理程式的檔案和類別。 在引進Razor Pages之前,許多以檢視中心的應用程式會有負責許多不同的動作和檢視的大型控制器類別。 這些類別自然會發展出許多責任和相依性,使其更難以維護。 如果您發現以檢視為基礎的控制器變得太複雜,請考慮重構它們以使用 Razor Pages,或引入類似仲介者模式的設計。
調解器設計模式可用來減少類別之間的結合,同時允許它們之間的通訊。 在 ASP.NET Core MVC 應用程式中,此模式經常用來使用 處理程式 來執行動作方法的工作,將控制器分成較小的部分。 常用的 MediatR NuGet 套件 通常用來達成此目的。 一般而言,控制器包含許多不同的動作方法,每個方法可能需要特定的相依性。 任何動作所需的所有相依性集合都必須傳遞至控制器的建構函式。 使用 MediatR 時,控制器通常會擁有的唯一相依性是調解器的實例。 然後,每個動作都會使用調解器實例來傳送訊息,該訊息是由處理程序處理。 處理程式是單一動作特有的,因此只需要該動作所需的相依性。 使用 MediatR 的控制器範例如下所示:
public class OrderController : Controller
{
private readonly IMediator _mediator;
public OrderController(IMediator mediator)
{
_mediator = mediator;
}
[HttpGet]
public async Task<IActionResult> MyOrders()
{
var viewModel = await _mediator.Send(new GetMyOrders(User.Identity.Name));
return View(viewModel);
}
// other actions implemented similarly
}
在動作中MyOrders,此類別會處理對訊息的呼叫SendGetMyOrders:
public class GetMyOrdersHandler : IRequestHandler<GetMyOrders, IEnumerable<OrderViewModel>>
{
private readonly IOrderRepository _orderRepository;
public GetMyOrdersHandler(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
public async Task<IEnumerable<OrderViewModel>> Handle(GetMyOrders request, CancellationToken cancellationToken)
{
var specification = new CustomerOrdersWithItemsSpecification(request.UserName);
var orders = await _orderRepository.ListAsync(specification);
return orders.Select(o => new OrderViewModel
{
OrderDate = o.OrderDate,
OrderItems = o.OrderItems?.Select(oi => new OrderItemViewModel()
{
PictureUrl = oi.ItemOrdered.PictureUri,
ProductId = oi.ItemOrdered.CatalogItemId,
ProductName = oi.ItemOrdered.ProductName,
UnitPrice = oi.UnitPrice,
Units = oi.Units
}).ToList(),
OrderNumber = o.Id,
ShippingAddress = o.ShipToAddress,
Total = o.Total()
});
}
}
此方法的最終結果是控制器要小得多,主要著重於路由和模型系結,而個別處理程式則負責指定端點所需的特定工作。 您可以使用 ApiEndpoints NuGet 套件,在不使用 MediatR 的情況下實現這一方法。這個套件試圖將 Razor Pages 所賦予檢視控制器的相同優點帶到 API 控制器上。
參考資料 – 映射要求至回應
- 路由至控制器操作
https://learn.microsoft.com/aspnet/core/mvc/controllers/routing- 模型系結
https://learn.microsoft.com/aspnet/core/mvc/models/model-binding- 模型驗證
https://learn.microsoft.com/aspnet/core/mvc/models/validation- 篩選
https://learn.microsoft.com/aspnet/core/mvc/controllers/filters- ApiController 屬性
https://learn.microsoft.com/aspnet/core/web-api/
使用相依關係
ASP.NET Core 有內建支援,並在內部使用稱為 相依性插入的技術。 相依性插入是一種技術,可在應用程式的不同部分之間進行鬆散結合。 鬆散結合是可取的,因為它可讓您更輕鬆地隔離應用程式的元件,以便進行測試或取代。 這也使得應用程式某一部分的變更不太可能對應用程式中其他位置造成意外的影響。 相依性插入是以相依性反轉原則為基礎,而且通常是達成開放/封閉原則的關鍵。 評估應用程式與其相依性的運作方式時,請留意 靜態依賴 程式碼異味,並記住「新即黏附」的概念。
當您的類別呼叫靜態方法或存取靜態屬性時,就會發生靜態固定,這些屬性對基礎結構有副作用或相依性。 例如,如果您有呼叫靜態方法的方法,而靜態方法接著會寫入資料庫,則方法會緊密結合至資料庫。 中斷資料庫呼叫操作的任何因素都會影響您的方法。 測試這類方法是出了名的困難,因為這類測試要麼需要商業模擬庫來模擬靜態呼叫,要麼必須在測試資料庫到位的情況下才能進行。 對基礎結構沒有任何相依性的靜態呼叫,尤其是完全無狀態的呼叫,可以呼叫,而且不會影響結合或可測試性(除了結合程式代碼與靜態呼叫本身之外)。
許多開發人員瞭解靜態固定和全域狀態的風險,但仍會透過直接具現化將程式代碼緊密結合到特定實作。 “New is glue” 是為了提醒這種結合,而不是普遍譴責使用 new 關鍵字。 就像靜態方法呼叫一樣,沒有外部相依性的新類型實例通常不會緊密綁定程式碼至實作細節,也不會使測試變得更加複雜。 但是每次實例化類別時,花一點時間考慮是否有必要在特定位置對特定實例進行硬編碼,或者將該實例作為相依性來請求是否是更好的設計。
宣告相依性
ASP.NET Core 是以方法與類別宣告其相依性為基礎所建置,要求它們作為自變數。 ASP.NET 應用程式通常會在 Program.cs 或類別中 Startup 設定。
備註
在 .NET 6(及更新版本)和 Visual Studio 2022(及更新版本)的應用程式中,將應用程式完全配置在 Program.cs 中是一種預設做法。 項目範本已更新,可協助您開始使用這個新方法。 如有需要,ASP.NET Core 專案仍然可以使用 Startup 類別。
在 Program.cs 中設定服務
對於相當簡單的應用程式,您可以直接在 Program.cs 檔案中使用WebApplicationBuilder來連接相依性。 一旦新增所有必要的服務,建置器就會用來建立應用程式。
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
var app = builder.Build();
在 Startup.cs 中設定服務
Startup.cs 本身已設定為支援多個管道的依賴注入。 如果您使用 類別 Startup ,您可以為它提供建構函式,而且可以透過它要求相依性,如下所示:
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
}
}
類別 Startup 很有趣,因為它沒有明確的類型需求。 它不會繼承自特殊 Startup 基類,也不會實作任何特定介面。 您可以選擇是否提供建構函式,並且可以視需求在建構函式上指定任意多個參數。 當您為應用程式設定的 Web 主機啟動時,它會呼叫 Startup 類別(如果您已告知其使用其中一個),並使用相依性插入來填入類別所需的任何相依性 Startup 。 當然,如果您要求未在 ASP.NET Core 所使用的服務容器中設定的參數,您將會收到例外,但只要您使用容器已知的相依性,就可以要求您想要的任何內容。
當您從一開始建立 Startup 實例時,依賴注入就被內建於您的 ASP.NET Core 應用程式。 對於初創企業類別而言,這只是開始而已。 您也可以在 Configure 方法中要求依賴項。
public void Configure(IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
}
ConfigureServices 方法是此行為的例外狀況;它必須只接受一個IServiceCollection型的參數。 它不需要真正支援相依性插入,因為它一方面負責將物件新增至服務容器,另一方面它可透過 IServiceCollection 參數存取所有目前設定的服務。 因此,您可以在 Startup 類別的每個部分中處理在 ASP.NET Core 服務集合中定義的相依性,無論是透過請求所需服務作為參數,或是在 IServiceCollection 中使用 ConfigureServices。
備註
如果您需要確保某些服務可供您的 Startup 類別使用,您可以在 IWebHostBuilder 呼叫中使用 ConfigureServices 的 CreateDefaultBuilder 方法來配置這些服務。
Startup 類別是一種模型,說明您應該如何建構 ASP.NET 核心應用程式的其他部分,從控制器到中間件到篩選到您自己的服務。 在每個案例中,您應該遵循明確相 依性原則、要求相依性,而不是直接建立相依性,並在整個應用程式中運用相依性插入。 請注意您實例化實作的位置和方式,特別是涉及基礎設施或可能產生副作用的服務與物件。 偏好使用應用程式核心中定義的抽象概念,並以參數的形式傳入,而不是硬性編碼到特定實作類型的參照。
建構應用程式
整合型應用程式通常具有單一進入點。 在 ASP.NET Core Web 應用程式的情況下,進入點會是 ASP.NET Core Web 專案。 不過,這並不表示解決方案應該只包含單一專案。 為了遵循關注點的分離,將應用程式分成不同的層級會很有用。 一旦將內容分解成圖層後,透過專案分隔而非僅依靠資料夾,能有效幫助達成更佳的封裝效果。 使用 ASP.NET Core 應用程式達成這些目標的最佳方法是第 5 章所討論的 Clean Architecture 變化。 遵循此方法,應用程式的解決方案將由界面、基礎設施和應用核心的獨立函式庫組成。
除了這些專案之外,也會包含個別的測試專案(第 9 章會討論測試)。
應用程式的物件模型和介面應該放在ApplicationCore專案中。 此專案將盡可能少相依性(且不會涉及特定基礎結構考慮),而解決方案中的其他專案將會參考它。 需要保存的商務實體會定義於 ApplicationCore 專案中,與不會直接相依於基礎結構的服務一樣。
實作詳細數據,例如持續性的執行方式,或通知如何傳送給使用者,都會保留在基礎結構專案中。 此項目會參考 Entity Framework Core 之類的實作特定套件,但不應該公開專案外部這些實作的詳細數據。 基礎結構服務和存放庫應該實作ApplicationCore專案中定義的介面,而其持續性實作會負責擷取和儲存ApplicationCore中定義的實體。
ASP.NET Core UI 專案負責任何 UI 層級考慮,但不應包含商業規則或基礎結構詳細數據。 事實上,在理想情況下,它甚至不應該相依於基礎結構專案,這有助於確保不會意外導入這兩個專案之間的相依性。 這可以使用 Autofac 之類的第三方 DI 容器來達成,這可讓您在每個專案中的 Module 類別中定義 DI 規則。
將應用程式與實作詳細數據分離的另一種方法是讓應用程式呼叫微服務,或許部署在個別 Docker 容器中。 這比利用兩個專案之間的 DI 更能區分考慮和分離,但具有額外的複雜度。
功能規劃
根據預設,ASP.NET Core 應用程式會組織其資料夾結構以包含 Controllers 和 Views,以及經常使用 ViewModels。 支援這些伺服器端結構的用戶端程式代碼通常會分別儲存在 wwwroot 資料夾中。 不過,大型應用程式可能會遇到此組織的問題,因為處理任何指定的功能通常需要在這些資料夾之間跳躍。 隨著每個資料夾中的檔案和子資料夾數量增加,這項工作變得越來越困難,導致需要在方案總管中進行大量的捲動。 此問題的其中一個解決方案是依 功能 而非檔案類型來組織應用程式程序代碼。 此組織樣式通常稱為功能資料夾或 功能切片 (另參見: 垂直切片)。
ASP.NET Core MVC 支援區域以供此用途使用。 使用區域,您可以在每個區域資料夾中建立獨立的 Controllers 和 Views 資料夾(以及任何相關的模型)。 圖 7-1 顯示使用 Areas 的範例資料夾結構。
圖 7-1。 範例區域組織
使用區域時,您必須使用標註為控制器添加其所屬區域名稱:
[Area("Catalog")]
public class HomeController
{}
您也需要將區域支援新增至您的路由:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(name: "areaRoute", pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
});
除了區域內建支援之外,您也可以使用自己的資料夾結構,以及慣例來取代屬性和自定義路由。 這可讓您擁有不包含檢視、控制器等個別資料夾的功能資料夾,讓階層保持一般,並讓您更輕鬆地在單一位置查看每項功能的所有相關檔案。 針對 API,資料夾可用來取代控制器,而每個資料夾都可以包含所有 API 端點及其相關聯的 DTO。
ASP.NET Core 使用內建慣例類型來控制其行為。 您可以修改或取代這些慣例。 例如,您可以建立一個慣例,根據其命名空間自動取得指定控制器的功能名稱(這通常與控制器所在的資料夾相互關聯):
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;
}
}
當您在 ConfigureServices 中將MVC的支援新增至您的應用程式時,請將此慣例指定為選項(或 Program.cs):
// ConfigureServices
services.AddMvc(o => o.Conventions.Add(new FeatureConvention()));
// Program.cs
builder.Services.AddMvc(o => o.Conventions.Add(new FeatureConvention()));
ASP.NET Core MVC 也會使用慣例來尋找檢視。 您可以使用自定義慣例來覆寫它,讓檢視位於您的功能資料夾中(使用上述FeatureConvention所提供的功能名稱)。 您可以深入瞭解此方法,並從 MSDN Magazine 文章: ASP.NET Core MVC 的功能切片下載範例。
API 和 Blazor 應用程式
如果您的應用程式包含一組必須保護的 Web API,這些 API 最好是設定為與檢視或 Razor Pages 應用程式不同的專案。 將 API,特別是公用 API 與伺服器端 Web 應用程式分開,有許多優點。 這些應用程式通常會有獨特的部署和負載特性。 它們也極有可能採用不同的安全性機制,其中標準表單型應用程式使用以 Cookie 為基礎的驗證,而 API 則最有可能使用令牌型驗證。
此外, Blazor 無論是使用 Blazor 伺服器還是 BlazorWebAssembly的應用程式,都應該建置為個別專案。 應用程式有不同的運行時間特性,以及安全性模型。 它們可能會與伺服器端 Web 應用程式(或 API 專案)共用一般類型,而且這些類型應該定義在通用共享專案中。
將管理介面 BlazorWebAssembly 添加到 eShopOnWeb,這需要新增幾個新專案。 專案 BlazorWebAssembly 本身, BlazorAdmin。 在 BlazorAdmin 項目中,新定義了一組由 PublicApi 使用的公用 API 端點,並配置為使用令牌型驗證。 這兩個專案所使用的特定共享類型會保留在新 BlazorShared 專案中。
有人可能會問,為什麼要新增一個獨立的BlazorShared專案,當已經有一個通用的ApplicationCore專案可以用來共用PublicApi和BlazorAdmin所需的所有類型? 答案是,此專案包含應用程式的所有商業規則,因此比必要大得多,而且更可能需要在伺服器上保持安全。 請記住,所有BlazorAdmin引用的庫會在使用者載入Blazor應用程式時被下載到他們的瀏覽器中。
根據是否使用 Backends-For-Frontends (BFF) 模式,應用程式取用的 BlazorWebAssembly API 可能不會與 Blazor共用其類型 100%。 特別是,旨在供許多不同的用戶端使用的公用 API,可能會定義自己的請求和回應類型,而不是在針對用戶端的共用專案中共用它們。 在 eShopOnWeb 範例中,假設 PublicApi 專案實際上是裝載公用 API,因此並非所有的要求和回應類型都來自 BlazorShared 專案。
跨領域考慮
隨著應用程式成長,將跨領域考慮排除在外,以消除重複並維持一致性變得越來越重要。 ASP.NET Core 應用程式中交叉考慮的一些範例包括驗證、模型驗證規則、輸出快取和錯誤處理,但還有其他許多問題。 ASP.NET Core MVC 篩選器 可讓您在要求處理管線中的特定步驟之前或之後執行程序代碼。 例如,篩選可以在模型系結之前和之後、動作前後執行,或在動作的結果前後執行。 您也可以使用授權篩選器來控制對其餘管線的存取。 圖 7-2 顯示如果已設定,要求執行如何流經篩選。
圖 7-2。 透過過濾器與管線執行請求。
篩選通常會實作為屬性,因此您可以將它們套用至控制器或動作(甚至是全域)。 以這種方式新增時,動作層級所指定的篩選會覆寫或建置在控制器層級指定的篩選,而此篩選本身會覆寫全域篩選。 例如, [Route] 屬性可用來建立控制器和動作之間的路由。 同樣地,授權可以在控制器層級設定,然後由個別動作覆寫,如下列範例所示:
[Authorize]
public class AccountController : Controller
{
[AllowAnonymous] // overrides the Authorize attribute
public async Task<IActionResult> Login() {}
public async Task<IActionResult> ForgotPassword() {}
}
第一個方法 Login 使用 [AllowAnonymous] 篩選條件(attribute)來覆寫控制器層面的 Authorize 篩選集。
ForgotPassword動作(以及類別中沒有 AllowAnonymous 屬性的任何其他動作)都需要已驗證的請求。
篩選可用來消除 API 常見錯誤處理原則形式的重複。 例如,典型的 API 政策是對於參考不存在之密鑰的要求返回 NotFound 回應,如果模型驗證失敗,則返回 BadRequest 回應。 下列範例示範這兩個作用中的原則:
[HttpPut("{id}")]
public async Task<IActionResult> Put(int id, [FromBody]Author author)
{
if ((await _authorRepository.ListAsync()).All(a => a.Id != id))
{
return NotFound(id);
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
author.Id = id;
await _authorRepository.UpdateAsync(author);
return Ok();
}
不要讓您的操作方法因條件式代碼而變得混亂。 將政策整合至可視需要套用的篩選條件。 在此範例中,每當命令傳送至 API 時,應該會發生模型驗證檢查,可取代為下列屬性:
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
}
您可以將 ValidateModelAttribute 作為 NuGet 相依性,通過加入 Ardalis.ValidateModel 套件至專案。 針對 API,您可以使用 ApiController 屬性來強制執行此行為,而不需要個別 ValidateModel 篩選。
同樣地,篩選條件可以用來檢查記錄是否存在,並在執行動作之前傳回 404,而不需要在動作中執行這些檢查。 一旦您提取了一般慣例並組織解決方案來分隔基礎結構程式代碼和商業規則與 UI,您的 MVC 動作方法應該非常精簡:
[HttpPut("{id}")]
[ValidateAuthorExists]
public async Task<IActionResult> Put(int id, [FromBody]Author author)
{
await _authorRepository.UpdateAsync(author);
return Ok();
}
您可以深入瞭解如何實作篩選,並從 MSDN Magazine 文章下載工作範例, Real-World ASP.NET Core MVC 篩選器。
如果您發現您有一些來自 API 的常見回應,例如驗證錯誤(不正確的要求)、找不到資源,以及伺服器錯誤,您可能會考慮使用 結果 抽象概念。 結果抽象化會由 API 端點取用的服務傳回,而控制器動作或端點會使用篩選條件將這些轉換為 IActionResults。
參考 – 建構應用程式
- 區域
https://learn.microsoft.com/aspnet/core/mvc/controllers/areas- MSDN Magazine – ASP.NET Core MVC 的功能切片
https://learn.microsoft.com/archive/msdn-magazine/2016/september/asp-net-core-feature-slices-for-asp-net-core-mvc- 篩選
https://learn.microsoft.com/aspnet/core/mvc/controllers/filters- MSDN Magazine – 真實世界 ASP.NET 核心 MVC 篩選器
https://learn.microsoft.com/archive/msdn-magazine/2016/august/asp-net-core-real-world-asp-net-core-mvc-filters- eShopOnWeb 的結果
https://github.com/dotnet-architecture/eShopOnWeb/wiki/Patterns#result
安全性
保護 Web 應用程式是一個大主題,有許多考慮。 在最基本的層級上,安全性牽涉到確保您知道指定要求來自誰,然後確保要求只能存取它應該的資源。 身份驗證是將隨請求提供的憑證與可信的資料庫中的資訊進行比較,以確定該請求是否應被視為來自已知實體。 授權是根據使用者身分識別限制特定資源的存取程式。 第三個安全性考慮是保護要求不受第三方竊聽,您至少應該 確保應用程式使用 SSL。
身份
ASP.NET Core Identity 是一種成員資格系統,可用來支援應用程式的登入功能。 它支援本機用戶帳戶,以及來自 Microsoft 帳戶、Twitter、Facebook、Google 等提供者的外部登入支援。 除了 ASP.NET Core Identity 之外,您的應用程式還可以使用 Windows 驗證,或 身分識別伺服器之類的第三方識別提供者。
如果已選取 ASP.NET [個別使用者帳戶] 選項,則 [核心身分識別] 會包含在新的項目範本中。 此範本包含註冊、登入、外部登入、忘記密碼和其他功能的支援。
圖 7-3。 選取 [個別用戶帳戶] 以預先設定身分識別。
身分識別支援是在 Program.cs 或 Startup中設定,包括設定服務和中間件。
在 Program.cs 中設定身分識別
在 Program.cs 中,您會從 WebHostBuilder 實例設定服務,然後在應用程式建立之後,設定其中間件。 需要注意的重點是呼叫 AddDefaultIdentity 以獲取必要的服務,以及呼叫 UseAuthentication 和 UseAuthorization 以添加必要的中間件。
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
在應用程式啟動時設定身分識別
// Add framework services.
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
builder.Services.AddMvc();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
重要的是,UseAuthentication 和 UseAuthorization 必須在 MapRazorPages 之前出現。 設定身分識別服務時,您會注意到對 AddDefaultTokenProviders的呼叫。 這與可能用來保護網路通訊的令牌無關,而是指提供方透過簡訊或電子郵件發送給使用者的提示,以便讓使用者確認身份。
您可以深入瞭解 如何設定雙因素驗證 ,以及從官方 ASP.NET Core 檔 啟用外部登入提供者 。
認證
驗證是判斷誰在存取系統的過程。 如果您使用 ASP.NET Core Identity 和上一節所示的組態方法,它會自動在應用程式中設定某些驗證預設值。 不過,您也可以手動設定這些預設值,或覆寫由 AddIdentity 設定的預設值。 如果您使用身分識別,它會將 Cookie 型驗證設定為預設 配置。
在 Web 型驗證中,在驗證系統客戶端的過程中,通常最多可以執行五個動作。 這些包括:
- 驗證。 使用用戶端提供的資訊來建立身分識別,以供他們在應用程式內使用。
- 挑戰。 此動作是用來要求用戶端自行識別。
- 禁止。 告知客戶禁止他們執行某些動作。
- 登入。 以某種方式保存現有的用戶端。
- 登出。從持久化中移除客戶端。
在 Web 應用程式中執行驗證有許多常見技術。 這些稱為方案。 指定的配置會定義上述部分或所有選項的動作。 某些配置只支援動作的子集,而且可能需要個別的配置來執行不支持的動作。 例如,OpenId-Connect(OIDC)方案不支援登入或登出,但通常會設定為使用持續性 Cookie 驗證。
在您的 ASP.NET Core 應用程式中,您可以配置DefaultAuthenticateScheme,以及針對上述每個動作設定選擇性特定的方案。 例如,DefaultChallengeScheme 和 DefaultForbidScheme。 呼叫 AddIdentity 會設定應用程式的許多層面,並新增許多必要的服務。 它也包含此呼叫來設定驗證設定:
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
});
這些配置預設會使用 Cookie 進行持續性和重新導向至登入頁面以進行驗證。 這些配置適用於透過網頁瀏覽器與用戶互動的 Web 應用程式,但不建議用於 API。 相反地,API 通常會使用另一種形式的驗證,例如 JWT 持有人令牌。
Web API 會由程式碼取用,例如 HttpClient 在 .NET 應用程式和其他框架中的等效類型。 這些客戶端預期來自 API 呼叫的可用回應,或狀態代碼,指出發生問題的情況。 這些用戶端不會透過瀏覽器互動,也不會轉譯或與 API 可能傳回的任何 HTML 互動。 因此,如果 API 端點未通過驗證,則不適合將其用戶端重新導向至登入頁面。 另一個方案更合適。
若要設定 API 的驗證,您可能會在 eShopOnWeb 參考應用程式中設定專案所使用的 PublicApi 驗證,如下所示:
builder.Services
.AddAuthentication(config =>
{
config.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(config =>
{
config.RequireHttpsMetadata = false;
config.SaveToken = true;
config.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
雖然可以在單一項目中設定多個不同的驗證配置,但設定單一預設配置會比較簡單。 基於這個原因和其他原因,eShopOnWeb 參考應用程式將其 API 分成獨立專案,與包含應用程式視圖和 Razor Pages 的主要 PublicApi 專案分開。
在 Blazor 應用程式中的驗證
Blazor 伺服器應用程式可以利用與任何其他 ASP.NET Core 應用程式相同的驗證功能。 Blazor WebAssembly 不過,應用程式無法使用內建的身分識別和驗證提供者,因為它們是在瀏覽器中執行。 Blazor WebAssembly 應用程式可以在本機儲存使用者驗證狀態,並可存取宣告,以判斷使用者應該能夠執行的動作。 不過,不論應用程式內 BlazorWebAssembly 實作的任何邏輯為何,都應該在伺服器上執行所有驗證和授權檢查,因為使用者可以輕鬆地略過應用程式並直接與 API 互動。
參考 – 驗證
- 驗證動作和預設值
https://stackoverflow.com/a/52493428- SPA 的驗證和授權
https://learn.microsoft.com/aspnet/core/security/authentication/identity-api-authorization- ASP.NET 核心 Blazor 驗證和授權
https://learn.microsoft.com/aspnet/core/blazor/security/- 安全性:在 ASP.NET Web Forms 中的驗證和授權 Blazor
https://learn.microsoft.com/dotnet/architecture/blazor-for-web-forms-developers/security-authentication-authorization
授權
最簡單的授權形式是限制匿名使用者的存取權。 將屬性套用 [Authorize] 至特定控制器或動作,即可達成這項功能。 如果使用角色,則可以進一步擴充 屬性來限制對屬於特定角色的使用者存取,如下所示:
[Authorize(Roles = "HRManager,Finance")]
public class SalaryController : Controller
{
}
在此情況下,屬於 HRManager 或 Finance 角色 (或兩者) 的使用者將有權存取SalaryController。 若要要求使用者屬於多個角色(不只是數個角色之一),您可以多次套用 屬性,每次指定必要的角色。
將特定角色集指定為許多不同的控制器和動作中的字串可能會導致不想要的重複。 至少,請為這些字串常值定義常數,並在您需要指定字串的任何位置使用常數。 您也可以設定授權原則,以封裝授權規則,然後在套用 [Authorize] 屬性時指定原則,而不是個別角色:
[Authorize(Policy = "CanViewPrivateReport")]
public IActionResult ExecutiveSalaryReport()
{
return View();
}
透過這種方式使用原則,您可以將限制的動作類型與套用至它的特定角色或規則分開。 稍後,如果您建立需要存取特定資源的新角色,您可以只更新政策,而不必更新每個屬性上的每個角色清單。
索賠
宣告是名稱值組,代表已驗證用戶的屬性。 例如,您可以將使用者的員工號碼儲存為宣稱。 然後,宣告可以當做授權原則的一部分使用。 您可以建立名為 “EmployeeOnly” 的原則,要求有稱為 "EmployeeNumber"的宣告存在,如下列範例所示:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAuthorization(options =>
{
options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber"));
});
}
然後,此原則可以與 屬性搭配 [Authorize] 使用,以保護任何控制器和/或動作,如上所述。
保護 Web API
大部分的 Web API 都應該實作令牌型驗證系統。 令牌驗證是無狀態的,且設計為可調整。 在令牌型驗證系統中,客戶端必須先向驗證提供者進行驗證。 如果成功,用戶端就會發出令牌,而令牌只是密碼編譯有意義的字元字串。 令牌最常見的格式是 JSON Web Token 或 JWT(通常發音為 “jot” )。 當客戶端接著需要向 API 發出要求時,它會將此令牌新增為要求上的標頭。 接著,伺服器會先驗證要求標頭中找到的令牌,再完成要求。 圖 7-4 示範此程式。
圖 7-4。 Web API 的令牌型驗證。
您可以建立自己的驗證服務、與 Azure AD 和 OAuth 整合,或使用 IdentityServer 等開放原始碼工具來實作服務。
JWT 令牌可以內嵌有關使用者的宣告,這可以在用戶端或伺服器上讀取。 您可以使用 jwt.io 之類的工具來檢視 JWT 令牌的內容。 請勿將機密數據如密碼或密鑰儲存在 JTW 令牌中,因為它們的內容很容易讀取。
搭配 SPA 或 BlazorWebAssembly 應用程式使用 JWT 令牌時,您必須將令牌儲存在用戶端的某處,然後將它新增至每個 API 呼叫。 此活動通常作為標題,如下列程式碼所示:
// AuthService.cs in BlazorAdmin project of eShopOnWeb
private async Task SetAuthorizationHeader()
{
var token = await GetToken();
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
}
呼叫上述方法之後,使用 _httpClient 提出的要求將會將令牌內嵌在要求的標頭中,讓伺服器端 API 能夠驗證並授權要求。
自訂安全性
謹慎
一般情況下,請避免實作您自己的自定義安全性實作。
務必特別小心自行開發密碼編譯系統、用戶成員資格系統或令牌生成系統。 有許多商業和開放原始碼替代方案可供使用,這幾乎肯定會有比自定義實作更好的安全性。
參考 – 安全性
- 安全性文件概觀
https://learn.microsoft.com/aspnet/core/security/- 在 ASP.NET 核心應用程式中強制執行 SSL
https://learn.microsoft.com/aspnet/core/security/enforcing-ssl- 身分識別簡介
https://learn.microsoft.com/aspnet/core/security/authentication/identity- 授權簡介
https://learn.microsoft.com/aspnet/core/security/authorization/introduction- Azure App Service 中的 API Apps 驗證與授權
https://learn.microsoft.com/azure/app-service-api/app-service-api-authentication- 身分識別伺服器
https://github.com/IdentityServer
用戶端通訊
除了透過 Web API 提供頁面和回應資料要求之外,ASP.NET Core 應用程式也可以直接與已連線的客戶端通訊。 此輸出通訊可以使用各種傳輸技術,最常見的是 WebSocket。 ASP.NET Core SignalR 是一個連結庫,可讓您輕鬆地將即時伺服器對客戶端通訊功能新增至您的應用程式。 SignalR 支援各種傳輸技術,包括 WebSocket,並從開發人員擷取許多實作詳細數據。
即時用戶端通訊,無論是直接使用 WebSocket 或其他技術,在各種應用程式案例中都很有用。 一些範例包括:
即時聊天室應用程式
監視應用程式
作業進度更新
通知
互動式表單應用程式
在應用程式中建置用戶端通訊時,通常有兩個元件:
伺服器端連接管理員 (SignalR Hub, WebSocketManager WebSocketHandler)
用戶端程式庫
用戶端不限於瀏覽器 – 行動應用程式、控制台應用程式和其他原生應用程式也可以使用 SignalR/WebSocket 進行通訊。 下列簡單的程式會回應傳送至聊天應用程式至主控台的所有內容,做為 WebSocketManager 範例應用程式的一部分:
public class Program
{
private static Connection _connection;
public static void Main(string[] args)
{
StartConnectionAsync();
_connection.On("receiveMessage", (arguments) =>
{
Console.WriteLine($"{arguments[0]} said: {arguments[1]}");
});
Console.ReadLine();
StopConnectionAsync();
}
public static async Task StartConnectionAsync()
{
_connection = new Connection();
await _connection.StartConnectionAsync("ws://localhost:65110/chat");
}
public static async Task StopConnectionAsync()
{
await _connection.StopConnectionAsync();
}
}
請考慮應用程式直接與用戶端應用程式通訊的方式,並考慮即時通訊是否會改善應用程式的用戶體驗。
參考 – 客戶端通訊
- ASP.NET Core SignalR
https://github.com/dotnet/aspnetcore/tree/main/src/SignalR- WebSocket 管理員
https://github.com/radu-matei/websocket-manager
網域驅動設計 – 您應該套用嗎?
Domain-Driven 設計 (DDD) 是建置軟體的敏捷式方法,強調專注於 商業領域。 它重視與商務領域專家的溝通和互動,這些專家能夠向開發人員闡述真實世界系統的運作方式。 例如,如果您要建置處理股票交易的系統,您的領域專家可能是經驗豐富的股票經紀人。 DDD 的設計訴求是解決大型、複雜的商務問題,而且通常不適合較小型、更簡單的應用程式,因為對瞭解和模型化領域的投資並不值得。
在採用 DDD 方法建置軟體時,您的小組(包括非技術項目關係人和參與者)應該為問題空間開發 無處不在的語言 。 也就是說,相同的術語應該用於正在建立模型的實際概念、軟體對等專案,以及可能存在以保存概念的任何結構(例如資料庫數據表)。 因此,無處不在語言中所述的概念應該構成領域 模型的基礎。
您的領域模型是由彼此互動的對象所組成,以代表系統的行為。 這些物件可能屬於下列類別:
實體,表示具有身分識別線程的物件。 實體通常會以鍵值儲存在持久儲存中,以便稍後擷取。
匯總,代表應該保存為單位的物件群組。
Value 物件,代表可以根據屬性值總和來比較的概念。 例如,DateRange 是由開始和結束日期所組成。
領域事件,代表系統內發生對系統其他部分感興趣的事件。
DDD 領域模型應該在模型中封裝複雜的行為。 特別是實體不應該只是屬性的集合。 當領域模型缺乏行為,而且只代表系統的狀態時,據說它是 非必要模型,在 DDD 中是不想要的。
除了這些模型類型之外,DDD 通常會採用各種模式:
DDD 也建議使用先前討論的 Clean Architecture,這樣能實現鬆散結合和封裝,並使程式碼可以輕鬆透過單元測試進行驗證。
何時應套用 DDD
DDD 非常適合具有重要商務(不只是技術)複雜性的大型應用程式。 應用程式應該需要領域專家的知識。 領域模型本身應該有顯著的行為,代表商務規則和互動,而不只是從數據存放區儲存和擷取各種記錄的目前狀態。
什麼時候不應該使用 DDD
DDD 包含在建模、架構和溝通上的投資,這對小型應用程式或基本上只是 CRUD(建立/讀取/更新/刪除)的應用程式來講可能並不必要。 如果您選擇遵循 DDD 來開發應用程式,但發現您的網域具有無行為的 anemic 模型,您可能需要重新思考您的方法。 您的應用程式可能不需要 DDD,或者您可能需要協助重構應用程式,以在領域模型中封裝商業規則,而不是在資料庫或使用者介面中。
混合式方法只會針對應用程式的交易或更複雜的區域使用 DDD,但不適用於較簡單的 CRUD 或應用程式的唯讀部分。 例如,如果您要查詢數據以顯示報表或可視化儀錶板的數據,則不需要匯總的條件約束。 對於這類需求,擁有個別、更簡單的讀取模型是完全可以接受的。
參考:Domain-Driven 設計
部署
ASP.NET Core 應用程式的部署過程中有幾個步驟,不論其主機位置為何。 第一個步驟是發佈應用程式,您可以使用 dotnet publish CLI 命令來完成。 此步驟會編譯應用程式,並將執行應用程式所需的所有檔案放入指定的資料夾。 當您從 Visual Studio 部署時,會自動為您執行此步驟。 publish 資料夾包含應用程式及其相依性的 .exe 和 .dll 檔案。 獨立應用程式也會包含 .NET 運行環境的版本。 ASP.NET Core 應用程式也會包含組態檔、靜態客戶端資產和MVC檢視。
ASP.NET Core 應用程式是當伺服器開機時必須啟動的控制台應用程式,如果應用程式(或伺服器)當機,則必須重新啟動。 進程管理員可用來將此程序自動化。 ASP.NET Core 最常見的進程管理員是 Linux 上的 Nginx 和 Apache,以及 Windows 上的 IIS 或 Windows 服務。
除了進程管理員之外,ASP.NET Core 應用程式也可以使用反向 Proxy 伺服器。 反向 Proxy 伺服器會接收來自因特網的 HTTP 要求,並在一些初步處理之後將它們轉送至 Kestrel。 反向 Proxy 伺服器為應用程式提供一層安全性。 Kestrel 也不支援在相同的埠上裝載多個應用程式,因此主機標頭等技術無法搭配它使用,以便在相同的埠和 IP 位址上裝載多個應用程式。
圖 7-5。 ASP.NET 裝載於 Kestrel 之上,在反向代理伺服器後方。
另一個反向 Proxy 有助於使用 SSL/HTTPS 保護多個應用程式的情況。 在此情況下,只有反向 Proxy 必須設定 SSL。 反向 Proxy 伺服器與 Kestrel 之間的通訊可能會透過 HTTP 進行,如圖 7-6 所示。
圖 7-6。 ASP.NET 透過 HTTPS 保護的反向代理伺服器進行裝載
越來越受歡迎的方法是將 ASP.NET Core 應用程式裝載在 Docker 容器中,然後裝載在本機或部署至 Azure 以進行雲端式裝載。 Docker 容器可能包含在 Kestrel 上執行的應用程式程式代碼,而且會部署在反向 Proxy 伺服器後方,如上所示。
如果您要在 Azure 上裝載應用程式,您可以使用 Microsoft Azure 應用程式閘道作為專用虛擬裝置來提供數個服務。 除了作為個別應用程式的反向 Proxy 之外,應用程式閘道也可以提供下列功能:
HTTP 負載平衡
SSL 負載分擔(僅限於網際網路)
端對端 SSL
多站點路由(將最多 20 個站點合併至單一應用程式閘道)
Web 應用程式防火牆
Websocket 支援
進階診斷
深入瞭解 第 10 章中的 Azure 部署選項。
參考文獻 – 部署
- 裝載和部署概觀
https://learn.microsoft.com/aspnet/core/publishing/- 何時搭配反向代理伺服器使用 Kestrel
https://learn.microsoft.com/aspnet/core/fundamentals/servers/kestrel#when-to-use-kestrel-with-a-reverse-proxy- 在 Docker 中裝載 ASP.NET 核心應用程式
https://learn.microsoft.com/aspnet/core/publishing/docker- Azure 應用程式閘道簡介
https://learn.microsoft.com/azure/application-gateway/application-gateway-introduction