在這個特殊的春節,大家想必都在家出不了們,遠看已經到了回城里上班的日子,但是因為一只蝙蝠的原因導致我們無法回到工作崗位,大家可能有的在家遠程辦公,有些在家躺着看書,有的是在家打游戲;在這個特殊無聊的日志,我果斷從無聊的被窩中 開啟了流量共享wifi 來進行.net core 3.1 源代碼的解讀和學習,並且把學習到的東西分享給大家。
疑問
剛剛接觸ASP.NET CORE 項目的同學可能會有如下疑問:
- ASP.NET CORE 項目的啟動過程是怎么樣的?
- 為什么ASP.NET CORE項目可以在控制台中運行啟動后變成了一個網站程序?
現在我這里使用.NETCORE 3.1 最新穩定發布版本來進行以上問題的解析,帶大家解決以上問題的疑惑,學習完大家可以對ASP.NETCORE 項目會有一個不一樣的理解和領悟.
啟動過程
剛剛接觸ASP.NET core 的同學們估計都會覺得和之前的ASP.NET 設計大不一樣,代碼風格也有很大的變化,以前的ASP.NET 是全家桶框架模式,里面包含了所有的實現,你用的到的用不到的都集成在里面;然而ASP.NET CORE 框架做了大的改變,以最小化抽象設計,通過擴展方法完成易用性擴展.
解讀過源代碼的同學們都可以發現大多api都是最小化單元抽象接口方式進行設計,其他復雜的方法api都是通過擴展方法進行擴展提供,這也是.NET Core 高效易擴展的一大優勢原因.
對於ASP.NET Core應用程序來說,我們要記住非常重要的一點是:其本質上是一個獨立的控制台應用,它並不是必需在IIS內部托管且並不需要IIS來啟動運行(而這正是ASP.NET Core跨平台的基石)。ASP.NET Core應用程序擁有一個內置的Self-Hosted(自托管)的Web Server(Web服務器),用來處理外部請求。
不管是托管還是自托管,都離不開Host(宿主)。在ASP.NET Core應用中通過配置並啟動一個Host來完成應用程序的啟動和其生命周期的管理。而Host的主要的職責就是Web Server的配置和Pilpeline(請求處理管道)的構建。
我們現在來創建一個ASP.NETCORE WEB 項目 步驟如下
文件-> 新建 -> 項目 -> 選擇ASP.Net Core Web應用程序 -> 選擇.NETCORE 3.1 框架 如圖:
創建項目后我們從Program 類中可以看到以下代碼:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)//開啟一個默認的通用主機Host建造者
.ConfigureWebHostDefaults(webBuilder =>
{
// 從前綴為“ASPNETCORE” 環境變量加載WEB主機配置
// 默認是Kestrel 設置為web 服務器並對其進行默認配置, 支持iis集成
//注冊系統啟動所使用的組件,比如配置的組件,容器的組件等
webBuilder.UseStartup<Startup>();
});
}
查看以上代碼可以發現 Main 方法中代碼很簡單 ,清晰可見
CreateHostBuilder(args)
:方法創建了一個IHostBuilder 抽象對象,創建過程包含CreateDefaultBuilder(args)
:開啟創建一個默認的通用宿主機Host建造者,再通過ConfigureWebHostDefaults()
方法配置開啟默認的Kestrel
為默認的Web服務器並對其進行默認配置,並集成對iis的集成
Build()
:負責創建IHost
,看過源代碼的同學可以發現Build
的過程 會配置各種東西,本身通過管道模式進行了一系列的默認或者自定義的配置以及服務注冊的構建(下面會詳細講解)
Run()
:啟動IHost
所以,ASP.NET Core應用的啟動本質上是啟動作為宿主的Host對象。
其主要涉及到兩個關鍵對象IHostBuilder
和IHost
,它們的內部實現是ASP.NET Core應用的核心所在。下面我們就結合源碼並梳理調用堆棧來一探究竟!
源代碼詳細圖如下:
從上圖中我們可以看出CreateDefaultBuilder()
方法主要干了五件大事:
UseContentRoot
:指定Web host使用的content root(內容根目錄),比如Views。默認為當前應用程序根目錄。ConfigureHostConfiguration
:啟動時宿主機需要的環境變量等相關,支持命令行ConfigureAppConfiguration
:設置當前應用程序配置。主要是讀取 appsettinggs.json 配置文件、開發環境中配置的UserSecrets、添加環境變量和命令行參數 。ConfigureLogging
:讀取配置文件中的Logging節點,配置日志系統。UseDefaultServiceProvider
:設置默認的依賴注入容器。
從圖中可以看出CreateDefaultBuilder
后調用了ConfigureWebHostDefaults
方法,該方法默認主要做了以下幾個事情
UseStaticWebAssets
:靜態文件環境的配置啟用UseKestrel
:開啟Kestrel為默認的web 服務器.ConfigureServices
:服務中間件的注冊,包含路由的中間件的注冊UseIIS
:對iis 集成的支持UseStartup
:程序Startup 啟動,該啟動類中可以注冊中間件、擴展第三方中間件,以及相關應用程序配置的處理等等操作
現在所有的配置都已經配置創建好了,接下來我們來看看Build 方法主要做了哪些不為人知的事情,先來看下源代碼
/// <summary>
/// Run the given actions to initialize the host. This can only be called once.
/// </summary>
/// <returns>An initialized <see cref="IHost"/></returns>
public IHost Build()
{
if (_hostBuilt)
{
throw new InvalidOperationException("Build can only be called once.");
}
_hostBuilt = true;
BuildHostConfiguration();
CreateHostingEnvironment();
CreateHostBuilderContext();
BuildAppConfiguration();
CreateServiceProvider();
return _appServices.GetRequiredService<IHost>();
}
從代碼中可以發現有一個_hostBuilt 的變量,細心的同學可以發現該變量主要是用於控制是否build 過,所以這里可以大膽猜測只能build 一次該Host;現在看下源代碼解析圖:
經過查看源代碼得到的執行結構如上,因此我把代碼改造成如下結構。
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)//開啟一個默認的通用主機Host建造者
.ConfigureAppConfiguration(config => {
//注冊應用程序內所使用的配置文件,比如數據庫鏈接等等
Console.WriteLine("ConfigureAppConfiguration");
})
.ConfigureServices(service =>
{
//注冊服務中間件等操作
Console.WriteLine("ConfigureServices");
})
.ConfigureHostConfiguration(builder => {
//啟動時需要的組件配置等,比如監聽的端口 url地址等
Console.WriteLine("ConfigureHostCOnfiguration");
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Startup 代碼如下:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
Console.WriteLine("Startup ");
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
Console.WriteLine("ConfigureServices");
services.AddControllersWithViews();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
Console.WriteLine("Configure");
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/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.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
通過執行發現執行代碼順序正如我們源代碼的執行順序一致,執行順序如下圖:
為何通過控制台命令運行后自動啟動了一個網站程序?
以前ASP.NET web項目是需要搭建在iis 中托管運行,但是ASP.NETCORE 項目可以直接通過命令行進行托管運行,運行后可以直接瀏覽器打開,你們有沒有考慮過為什么?,細心的同學查看項目屬性也會發現項目的輸出類型也是控制台項目,如圖:
查看這圖,有沒有發現很神奇,為什么輸出類型竟然可以通過控制台命令行進行啟動項目呢?
在上面的源代碼分析過程中可以發現啟動時會啟動一個Kestrel
服務器(ConfigureWebHostDefaults
方法中會調用UseKestrel
),所以命令后啟動一個控制台應用程序后相當於啟動了一台web服務器;下面簡要的概括下Kestrel
服務器的優勢:
Kestrel
:Kestrel 是個精簡高效的HttpServer
,以包形式提供,自身不能單獨運行。
內部封裝了對 libuv
的調用,作為I/O底層,屏蔽各系統底層實現差異;有了Kestrel才能真正的實現跨平台
.
好了,想必同學們到這里已經對上面 兩個疑惑有了清晰的答案了。這里我拋出一個疑問,看了上面的代碼解讀,大家有沒有發現ASP.NET CORE 和ASP.NET 有了很大的不同,這是什么樣的設計改進呢?敬請期待下期我們一起來學習ASP.NET CORE 的牛逼的管道模型
.