認識ASP.NET Core / 基礎

來自專欄.NET Core筆記與思考13 人贊了文章

本篇主題:ASP.NET Core進程的啟動及核心組件。

啟動代碼概覽

首先使用.NET Core cli 命令 dotnet new web 來創建一個空白的ASP.NET Core項目,這裡使用的.NET Core版本為2.1.3,ASP.NET Core版本是2.1.2。(如果你願意完全可以從一個空白的cs項目開始開發ASP.NET Core應用)

新建空白asp.net core web項目的目錄結構

創建好項目後項目目錄下有兩個代碼文件Program.cs和Startup.cs,一個空的wwwroot文件夾以及Properties文件夾和其中的launch.json文件。了解了項目文件後,我們從程序的入口Program.cs文件開始來看看asp.net core的啟動過程。

Program.cs

public class Program{ public static void Main(string[] args) { BuildWebHost(args).Run(); } public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() .Build();}

Startup.cs

public class Startup{ // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit Application startup in ASP.NET Core public void ConfigureServices(IServiceCollection services) { } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.run(async (context) => { await context.Response.WriteAsync("Hello World!"); }); }}

Program.cs中代碼執行了下面4個方法:

  1. 執行WebHost.CreateDefaultBuilder(args)靜態方法,創建IWebHostBuilder對象。
  2. 執行IWebHostBuilder.UseStartup<Startup>()。
  3. 執行IWebHostBuilder.Build(),創建一個IWebHost對象。
  4. 執行IWebHost.Run()。

這就是ASP.NET Core啟動的全過程,下面簡單解讀這四個方法的源代碼。

CreateDefaultBuilder(args),這個方法new了一個WebHostBuilder對象並讀取或設置了一些默認配置,如應用目錄,WebServer,應用配置,環境變數,日誌對象等。需要說明的是這裡的WebHost類是一個靜態類並不是實現IWebHost介面的類。

CreateDefaultBuilder(args)方法源代碼?

github.com
圖標

然後是IWebHostBuilder對象的UseStartup<Startup>()方法,這個方法的邏輯很簡單,將Starup類註冊到容器中,這個容器非常重要。其中Startup類中包含了配置服務與Middleware的方法,我們會稍後介紹這些內容。

UseStartup&lt;Startup&gt;()方法源代碼?

github.com
圖標

WebHostBuilder對象的Build()方法,這個方法使用之前的配置構造了new WebHost所需的一系列對象,包括:已創建的服務對象集合,容器對象,配置對象等,然後執行WebHost的初始化方法返回WebHost對象。

WebHostBuilder.Build()方法源代碼?

github.com
圖標

IWebHost.Run()方法,這個方法是IWebHost的擴展方法,它只有一行代碼:

public static void Run(this IWebHost host){ //執行了host.StartAsync( )返回Task對象,並等待Task返回結果。 host.RunAsync().GetAwaiter().GetResult();}

WebHostExtensions.cs源代碼?

github.com圖標

這便是ASP.NET Core啟動過程非常簡單的描述,限於篇幅並沒有更深入的解讀每個方法,有興趣的讀者可以自行查閱源代碼。


配置與WebServer

appsettings.json

ASP.NET Core已經不使用web.config作為配置文件,默認使用appsettings.json文件,只要你在應用程序根目錄中添加該文件,應用就會被讀取。通過Configuration類或者注入IConfiguration對象來訪問配置。除了簡單的字元串和數值,配置文件也支持POCO對象(plain old CLR object/簡單CLR對象,即只包含簡單類型屬性的類),下面是配置文件內容及配置讀取的示例。

配置文件:

{ "str_config": "value1_from_json", "int_config": 2, "simple_obj": { "value1": "subvalue1_from_json" }}

配置讀取代碼:

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.public void Configure(IApplicationBuilder app, IHostingEnvironment env,IConfiguration config){ if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } var sb = new StringBuilder(); sb.AppendLine("str_config:" + config["str_config"]); sb.AppendLine("int_config:" + config["int_config"]); //POCO對象配置讀取方式1 //sb.AppendLine("str_config:" + config["simple_obj:value1"]); //POCO對象配置讀取方式2 var simple_object_config = new simple_object(); config.GetSection("simple_obj").Bind(simple_object_config); sb.AppendLine("POCO_config:" + simple_object_config.value1); app.Run(async (context) => { await context.Response.WriteAsync("Hello World!
"
+ sb.ToString()); });}//simple_object類public class simple_object{ public string value1 { get; set; }}

ASP.NET Core支持從下列的位置/方式讀取配置:

  • Azure Key Vault/Azure鍵值庫
  • Command-line arguments/命令行參數
  • Custom providers (installed or created)/自定義配置提供程序
  • Directory files/目錄文件
  • Environment variables/環境變數
  • In-memory .NET objects/內存中的.NET對象
  • Settings files/配置文件

Kestrel&Http.sys

ASP.NET不同,ASP.NET Core「內置」了兩個WebServer,Kestrel與HTTP.sys,作為進程內的HTTP伺服器,其主要功能是監聽HTTP請求並將一系列請求功能組成HttpContext提供Web應用使用。

其中Kestrel是一款微軟為ASP.NET Core開發跨平台的開源Web伺服器,Kestrel意為紅隼,是一種分布廣泛的小型猛禽,飛行快速。寓意該Web伺服器小而快的特點。

飛行中的紅隼

你可以在部署中單獨使用Kestrel或配合一個反向代理伺服器,如下圖所示。

單獨使用Kestrel

結合反向代理伺服器

Http.sys為Windows平台內核功能,實際上IIS的HTTP監聽正是運行在Http.sys之上。當你需要在Windows平台上部署但又不使用IIS時你可以使用Http.sys作為WebSever或者需要僅Http.sys支持的功能,如Windows Authentication時也應選擇使用Http.sys。不過Kestrel與反向代理伺服器的結合可以支持絕大多數WebServer功能,所以推薦使用Kestrel。

兩種WebServer對比:

Kestrel對比Http.sys

WebServer的配置可以通過Program.cs中BuildWebHost方法中的IWebHostBuilder對象來行進,如下面代碼示例(ASP.NET Core默認使用Kestrel)。

//使用Http.sys及配置代碼public static IWebHost BuildWebHost(string[] args){ return WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() //使用Http.sys並配置 .UseHttpSys(options => { // The following options are set to default values. options.Authentication.Schemes = AuthenticationSchemes.None; options.Authentication.AllowAnonymous = true; options.MaxConnections = null; options.MaxRequestBodySize = 30000000; options.UrlPrefixes.Add("http://localhost:5000"); }) .Build();}//使用Kestrel及配置代碼public static IWebHost BuildWebHost(string[] args){ return WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() ////使用Kestrel並配置 .UseKestrel(options => { options.Limits.MaxConcurrentConnections = 100; options.Limits.MaxConcurrentUpgradedConnections = 100; options.Limits.MaxRequestBodySize = 10 * 1024; options.Limits.MinRequestBodyDataRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10)); options.Limits.MinResponseDataRate = new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10)); options.Listen(IPAddress.Loopback, 5000); options.Listen(IPAddress.Loopback, 5001, listenOptions => { listenOptions.UseHttps("testCert.pfx", "testPassword"); }); }) .Build();}

配置選項及具體內容請查閱下面的文檔:

Web server implementations in ASP.NET Core?

docs.microsoft.com
圖標

核心概念(Services,Pipeline,Middleware)

重點!重點!重點!

ASP.NET Core中有三個核心概念:服務(Services),中間件(Middleware)與請求管道(Request Pipeline),下面先介紹服務。

什麼是服務?APS.NET Core中的服務就是實現業務邏輯所需的功能組件,之所以稱之為服務是因為這些功能組件都放在內置的容器中或者說我們將放在容器中的功能組件稱作服務。

什麼是容器?ASP.NET Core內置一個DI容器(或稱為IOC容器),我們通過IWebHost對象的Services屬性(IServiceProvider類型)獲取服務,通過Startup類中ConfigureServices方法的services參數(IServiceCollection類型)來註冊服務,ASP.NET Core的大多數功能組件都註冊在容器中。該容器由包Microsoft.Extensions.DependencyInjection實現。

Microsoft.Extensions.DependencyInjection源代碼?

github.com
圖標

為什麼需要容器?通過這個容器可以實現依賴注入(DI)/控制反轉(IoC)的設計模式,這個模式是ASP.NET Core設計的核心。如果不了解依賴注入(Dependency Injection)請另行查閱,下面兩個鏈接提供參考。

Dependency Inversion Principle?

deviq.com圖標Inversion of Control?

deviq.com

依賴注入的設計模式有下面的優勢:

  • 通過介面讓組件的使用與實現分離,松耦合。
  • 管理組件的依賴樹,比如一個組件有另外的依賴,這種情況我們會需要不停的new對象。如果使用依賴注入容器我們只需要指定我們需要的組件(比如構造函數注入)容器就會管理好所有的依賴,並且在組件不被需要時釋放它。
  • 更好的支持單元測試。
  • ASP.NET Core的容器支持組件的生命周期管理。

我們一步一步來看在這個容器的使用,首先是如何在容器中註冊一個服務,這個操作在Startup類的ConfigureServices方法中進行,其中的services參數提供了一系列的註冊服務的方法。

public void ConfigureServices(IServiceCollection services){ services.Configure<CookiePolicyOptions>(options => { options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); services.AddScoped<IMyDependency, MyDependency>(); services.AddTransient<IOperationTransient, Operation>(); services.AddScoped<IOperationScoped, Operation>(); services.AddSingleton<IOperationSingleton, Operation>();}

在註冊服務的同時可以配置對象的生命周期,ASP.NET Core提供了三種生命周期:

  • Transient,每次被使用到時就創建新的實例。
  • Scoped,每次請求創建一個實例。
  • Singleton,在第一次被用到時創建之後都是用這個實例,也就是單例模式。

需要注意的是不同生命周期服務之間的相互依賴會導致服務生命周期的變化,參考下面的文檔。

Dependency injection in ASP.NET Core?

docs.microsoft.com
圖標

服務的使用相對簡單的多,仍以Starup類為例,其中的Configure方法就可以使用容器提供的依賴注入,所以你只需要在方法中添加你所需要的類型就可以使用該服務了。

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.public void Configure(IApplicationBuilder app, IHostingEnvironment env,IConfiguration config){ ... var sb = new StringBuilder(); sb.AppendLine("str_config:" + config["str_config"]); sb.AppendLine("int_config:" + config["int_config"]); ...}

在實際開發中,我們更多地會在MVC的Controller中使用服務,在Controller中一般使用構造函數注入來使用服務。下面代碼所示使用了一個IDateTime的服務。

using ControllerDI.Interfaces;using Microsoft.AspNetCore.Mvc;namespace ControllerDI.Controllers{ public class HomeController : Controller { private readonly IDateTime _dateTime; public HomeController(IDateTime dateTime) { _dateTime = dateTime; } public IActionResult Index() { return View(); } }}

ASP.NET Core包含了很多內置服務,如下圖所示:

ASP.NET Core內置服務

理解並使用好這些服務可以讓我們的開發事半功倍,關於服務與容器還有很多細節,更多內容可查看下面的文檔。

Dependency injection in ASP.NET Core?

docs.microsoft.com
圖標

下面介紹Pipeline和Middleware,我們知道Web應用的核心是處理HTTP請求,而HTTP請求所經過的「通道」在ASP.NET Core中我稱之為請求管道Pipeline,Middleware則是組成這個管道的零件,我們可以把Middleware想像為一節一節的管子,一起拼湊了一個完整的管道。 Middleware的本質是一個處理HttpContext的委託,而委託的「本質」又是方法,所以Middleware就是處理請求的方法,這個方法做兩件事情:

  1. 決定是否執行下一個Middleware。
  2. 在其後的Middleware被調用前和完成後執行自身的業務邏輯。

Middleware在請求管道中的模型

我們通過Starup類中Configure方法參數app(IApplicationBuilder類型)的三個方法來對Pipeline進行配置,它們分別是Run,Use,Map。其中Use方法就是為管道註冊一個Middleware,而Run方法則是管道的終點,通過它註冊的Middleware委託不存在next參數。

public class Startup{ public void Configure(IApplicationBuilder app) { app.Use(async (context, next) => { // Do work that doesnt write to the Response. await next.Invoke(); // Do logging or other work that doesnt write to the Response. }); app.Run(async context => { await context.Response.WriteAsync("Hello from 2nd delegate."); }); }}

Map則相當於為特定的請求Url創建一整個管道。

public class Startup{ private static void HandleMapTest1(IApplicationBuilder app) { app.Run(async context => { await context.Response.WriteAsync("Map Test 1"); }); } public void Configure(IApplicationBuilder app) { app.Map("/map1", HandleMapTest1); app.Run(async context => { await context.Response.WriteAsync("Hello from non-Map delegate. <p>"); }); }}

Middleware的順序就是你在Conifg方法中Use,Run,Map的順序。順序十分重要,比如在app.Run方法後面註冊的Middleware將不會被執行到,還有像處理異常的Middleware應該放在最開始這樣它才能夠捕獲所有Middleware上的異常。

上面解釋了Pipleline與Middleware以及在代碼層面的體現,現在說說如何在實際開發中開發Middlerware,如果把全部Middlerware都以app.Use的方式來註冊的話代碼會顯得非常冗長也不符合模塊化的設計理念,幸好ASP.NET Core中已經有標準的Middleware開發流程。

一句話說明標準化的Middleware:將委託封裝在一個類中,並以擴展方法的方式提供使用。

下面就是一個「標準」的Middleware,InvokeAsync是它處理請求的方法,而next則在構造函數中注入。

using Microsoft.AspNetCore.Http;using System.Globalization;using System.Threading.Tasks;namespace Culture{ public class RequestCultureMiddleware { private readonly RequestDelegate _next; public RequestCultureMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { var cultureQuery = context.Request.Query["culture"]; if (!string.IsNullOrWhiteSpace(cultureQuery)) { var culture = new CultureInfo(cultureQuery); CultureInfo.CurrentCulture = culture; CultureInfo.CurrentUICulture = culture; } // Call the next delegate/middleware in the pipeline await _next(context); } }}

這樣我們就可以UseMiddleware<RequestCultureMiddleware>()的方式使用這個Middleware了,但是這樣還是不夠「優雅」,我們需要為IApplicationBuilder添加一個擴展方法UseRequestCulture,代碼如下

using Microsoft.AspNetCore.Builder;namespace Culture{ public static class RequestCultureMiddlewareExtensions { public static IApplicationBuilder UseRequestCulture( this IApplicationBuilder builder) { return builder.UseMiddleware<RequestCultureMiddleware>(); } }}

這樣我就可以在Startup類中「優雅」的使用我們的Middleware了。

public class Startup{ public void Configure(IApplicationBuilder app) { //優雅的使用Middleware app.UseRequestCulture(); app.Run(async (context) => { await context.Response.WriteAsync( $"Hello {CultureInfo.CurrentCulture.DisplayName}"); }); }}

關於Middleware有下面幾個重點需要了解:

  1. 生命周期與應用的生命周期相同,也就是Middleware在應用啟動的時候被構造並不是在應用收到請求時,而且只生成一次。
  2. 需要有一個InvokeAsync方法來做為Middleware處理請求的入口,方法需要返回一個Task,你可以在這個方法中添加額外由容器提供服務作為參數。
  3. 基於工廠的Middleware可以實現生命周期與每次請求相同,限於篇幅詳細信息請查看下面的文檔:

Factory-based middleware activation in ASP.NET Core?

docs.microsoft.com圖標

ASP.NET Core也內置了很多Middleware,它們提供的功能可以極大的方便我們的開發。

ASP.NET Core內置Middleware

關於Middleware更多內容請查看下面的官方文檔。

ASP.NET Core Middleware?

docs.microsoft.com
圖標

其他需要知道的

整個ASP.NET Core基礎包含很多內容,無法在一篇文章中介紹完整,下面是一些個人認為在開發中會用到或者是需要了解的知識點。

環境變數

ASP.NET Core會讀取系統中環境變數ASPNETCOREENVIRONMENT的值來確定當前的環境,你可以設置任何的值,不過「Development」,「Staging」,「Production」這三個值是由框架支持的,通過env.IsDevelopment(),env.IsDevelopment(),env.IsDevelopment()體現,當然如果你使用其他值的話也可以通過env.IsEnvironment("your_value")方法來判斷當前環境是否是「your_value」。

launch.json

這個存在於Properties文件夾的文件用於在開發時對Web應用運行環境進行配置,包括WebServer與環境變數。

StaticFiles

ASP.NET Core默認將應用目錄下的wwwroot文件夾作為靜態文件的根目錄,你也可以使用下面代碼所示的方式配置自己的靜態文件目錄。

public void Configure(IApplicationBuilder app){ app.UseStaticFiles(); // For the wwwroot folder app.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider( Path.Combine(Directory.GetCurrentDirectory(), "MyStaticFiles")), RequestPath = "/StaticFiles" });}

GenericHost

我們之前所使用的都是WebHost,因為我們處理的是HTTP請求,其實ASP.NET Core也支持開發非HTTP的Host應用,如消息隊列,後台任務等。

除此之外還有一些其他的相對重要的知識點,提供大家作為擴展閱讀的參考。

  • Routing
  • WebSockets
  • WebHost啟動過程中的異常捕獲與處理
  • 全球化與本地化
  • IHttpClientFactory服務
  • ASP.NET Core Module

有興趣的讀者也可以前往ASP.NET Core官方文檔頁面深入閱讀。

ASP.NET Documentation?

docs.microsoft.com
圖標

總的來說ASP.NET Core並不是ASP.NET簡單複製的開源版本,它經過重新設計,完全支持跨平台,有很強的設計模式痕迹(如依賴注入/DI,倉庫模式)並在此基礎上實現了很好的模塊化與松耦合,以及大量使用非同步方法,在擴展性和性能上都有提升,而且它還是開源的!

希望這篇文章能成你學習ASP.NET Core的一塊敲門磚。有任何疑問或問題歡迎通過評論或私信指出,非常感謝。

推薦閱讀:

相关文章