.netcore基礎知識(一)

先來說說web伺服器
先來一張圖 一個典型的進程外託管模型 我們先看kestrel這一部分 我們在它前面放了一個方向代理伺服器nginx 對http請求做預處理 kestrel本身是可以直接用作web伺服器的 但是其功能較弱 只有基礎功能 說白了就是一個弱化版的IIS 所以不建議直接將他暴露出來 一般會在前面加上一個反向代理伺服器 當然圖中也可以將IIS作為反向代理伺服器 但是這有點多此一舉了 我們可以直接用IIS 這時有IIS集成技術 會消耗更少資源 這時叫做進程內託管 所以其實一共就有兩種最優方案:
(1)確定要用IIS情況下 直接用IIS集成 進程內託管
(2)不確定要用IIS 建議Nginx/apache + Kestrel 這樣外部有一層反向代理 能有效做負載均衡,動靜分離,訪問控制,重定向等等 kestrel僅僅用來處理http請求
然後來講講Host主機
Program類就是用來創建主機的
public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }); }
看一下這段程式碼 是不是很眼熟? Core的main方法就在這裡 這是程式的入口 我們看一下他做了什麼
CreateHostBuilder(args).Build().Run();
上面這句就是:創建主機生成器 ----> 配置主機 ----> 創建主機 ----> 運行主機
底下那個方法是前兩步 從 .Build().Run() 這兩步就是後面兩步
主機有啥用? 微軟官方的說法是 ---- 主機負責應用程式啟動和生存期管理
說白了就是啟動程式和程式的生命周期的管理 主要用於配置伺服器和請求處理管道。 主機還可以設置日誌記錄、依賴關係注入和配置 就是一系列配置都是主機來做的
最後來講一下最重要的管道和中間件
public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } 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) { services.AddControllers(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } }
看一下這段程式碼 我們先看第一個方法 ConfigureServices 這個方法其實很多人都用過 它是用來配置IOC容器 因為Core最核心的點就在於我們要遵循依賴注入的原則 舉個例子 我們想要吃一個蘋果 我們要怎麼做 當然是去商店買一個 而不是我們自己造一個 為啥呢?因為我們沒有造蘋果的圖紙啊 也就是說 我們想要造一個蘋果 首先我們需要一個造蘋果的圖紙 這樣其實我們就和蘋果類聯繫在一起了 這個圖紙 在程式里就叫做構造方法 而例子裡面的商店 其實就是IOC容器 我們想要蘋果就去商店買 我們不關心蘋果是怎麼造出來的 都是商店來處理 我們就和蘋果解耦了 這就是非常典型的DI(依賴注入)思想 IOC容器其實就是反轉控制容器 幫我們造實例的容器 我們要的話直接找它拿就好了 我們就不用去 new() 對象了
話說回來 ConfigureServices 方法其實作用就是將我們這些「蘋果」進行註冊 說白了就是我們要給IOC容器提供構造方法吧 否則IOC容器也不知道怎麼生產蘋果 那怎麼註冊呢 請看下圖:
services.AddSingleton<IX,X>(); services.AddTransient<IX, X>(); services.AddScoped<IX, X>();
可以看出來 後面兩個泛型參數第一個是實現類的介面 第二個是實現類 一共是三種方法進行註冊 我們要怎麼區分這三種方法呢?
(1)AddSingleton<IX,X>() 這個表示單例 首次請求會創建這個服務實例 應用程式生命周期中從頭到尾只有這一個實例存在
(2)services.AddTransient<IX, X>() 這個表示瞬時 每次請求或用到這個實例 全部都是重新創建的
(3)services.AddScoped<IX, X>() 這個表示在同一個http請求中 或者說同一個執行緒中 這個實例只會被創建一次 舉個例子 A依賴B 請求進來了 A在方法里多次使用了B 那這個B的實例 都是同一個 加入下一次請求來了 那麼就不是同一個了
多提一句 系統本來就註冊好的服務是已經設定好生命周期的 假如我們要自定義服務 我們要給它設定生命周期 注意 若同一個服務介面我們多次註冊 後面會覆蓋前面註冊的
我們再來看第二個方法Configure 這個方法其實就是用來構建管道 增加中間件的
IApplicationBuilder 其實就是一個管道構建器 我們看到下面的方法不斷在app.UseXXX() 其實這都是擴展方法 其實都是在添加中間件 http請求進來 所有的一切 都是中間件 不論是什麼控制器 服務類 路由 身份驗證... 全部都是中間件 中間件之間的關係是怎麼樣的 執行順序是怎麼樣的 這是一個難點 首先說執行順序 實際上中間件是從第一個開始 執行到最後一個 然後從最後一個一直到第一個進行返回 中間件原則是:1.能將請求傳遞給下一個中間件 2.能夠進行響應,使管道短路 短路是非常有必要的 沒有短路就沒有返回了 假如我們沒有設置在最後的短路中間件 系統會有一個報錯的最後中間件 強制短路返回
執行順序很有意思 先看我們自己寫的自定義中間件:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.Use(async (context, next) => { await context.Response.WriteAsync("First Begin \r\n "); await next(); await context.Response.WriteAsync("First End \r\n "); }); app.Use(async (context, next) => { await context.Response.WriteAsync("Second Begin \r\n "); await next(); await context.Response.WriteAsync("Second End \r\n "); }); app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapGet("/", async context => { await context.Response.WriteAsync("Hello World! \r\n"); }); }); }
可以看到我們寫了兩個自定義中間件 我們直接運行 看看他們執行順序是咋樣的:

是不是很有意思!我們現在就能理解了 next()實際上就是一個去找下一個中間件的委託 或者說next其實就是下一個中間件 先按照順序執行第一個中間件 然後next去找下一個中間件 第二個中間件的begin執行 然後執行next 找到下面的helloworld 這時候沒有下一個輸出了 這時候就返回了 從helloworld 到第二個中間件裡面的程式碼 就到了第二個的end 然後返回到第一個中間件 就到了第一個的end 就是這樣一個順序
而且我們也可以自己寫中間件 就像我圖上所示 app.Use() 方法 一共兩個參數
public static IApplicationBuilder Use(this IApplicationBuilder app, Func<HttpContext, Func<Task>, Task> middleware) { throw null; }
第一個那個this是拓展方法用的 是一種方便用的簡寫 從第二個參數開始 我們看到實際上這個參數是一個委託 這個委託也有兩個參數 第一個是Http的上下文對象 第二個參數是一個Task型委託 其實就是我們的next 返回值也是一個Task 表明它指向的是一個非同步方法
說白了 兩個參數其實就是 (context, next) 整體是非同步的 所以前面要加 async 方法體裡面調用的時候要用await 因為next本身是非同步方法
實際上在Core底層 next指向的就是下一個方法 委託其實就是一個函數指針 整體的處理是 先用一個List將所有中間件倒序放入 然後挨個取出 然後把反序第一個 也就是原先最後一個中間件當參數傳遞給前一個中間件的next 所以就能串起來 執行的時候第一個便可以根據next獲得下一個中間件 其實我們想一下 這不就是鏈表嗎 委託就是函數指針 鏈表裡面的元素就是方法 不過都是用指針來處理的而已
最後還要提一點 要是我們寫自定義中間件 要用擴展方法 這是約定原則

