1. 程式人生 > >Asp.Net Core 輕鬆學-在.Net Core 中使用鉤子

Asp.Net Core 輕鬆學-在.Net Core 中使用鉤子

原文: Asp.Net Core 輕鬆學-在.Net Core 中使用鉤子

前言

    Host startup hook,是2.2中提供的一項新的功能,通過使用主機啟動鉤子,允許開發人員在不修改程式碼的情況下,在服務啟動之前注入程式碼;通過使用鉤子,可以對已部署好的服務在服務啟動期間自定義託管程式的行為;通過使用鉤子,可以對服務進行跟蹤或者遙測,也可以在服務啟動前對託管環境進行健康檢查;還可以通過鉤子動態載入程式集進行依賴注入等功能。

什麼是鉤子

鉤子的作用原理是通過設定環境變數 DOTNET_STARTUP_HOOKS 的值將鉤子程式掛載到託管程式之中,在託管程式啟動的時候,CoreCLR 將按照鉤子列表順序進行檢查,初始化後執行每個鉤子程式,當鉤子列表中的鉤子程式被逐一執行完成後,託管程式將返回到程式主入口 Main 方法,進入一系列的啟動,鉤子程式可以是任何 .Net Core 版本的類庫專案,在專案內必須包含類 StartupHook 這是固定命名,且 StartupHook 必須是一個沒有名稱空間的內部類,包含預設的靜態方法 Initialize(),符合此規範即可作為鉤子程式進行託管掛載

使用鉤子

1.首先建立一個控制檯專案 Ron.HooksDemo ,作為託管主機,用於掛載鉤子程式 Ron.Init

Ron.HooksDemo 的程式碼非常簡單,僅僅輸出一句話

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("\n程式已啟動");
            Console.ReadKey();
        }
    }
2. 建立鉤子程式,Ron.Init

2.1 按照鉤子程式的規範,建立一個無名稱空間的內部類 StartupHook ,且包含預設靜態方法 Initialize()

internal class StartupHook
{
    public static void Initialize()
    {
        Console.WriteLine("程式集:Ron.Init.dll");
        Console.WriteLine("正在獲取伺服器資訊.....");
        string[] drives = Environment.GetLogicalDrives();
        Console.WriteLine("machineName:{0},\nOSVersion:{1},\nversion:{2},\nuserName:{3},\nCurrentDirectory:{4}\nCore Count:{5}\nWorkSet:{6}\nDrives:{7}",
            Environment.MachineName,
            Environment.OSVersion,
            Environment.Version,
            Environment.UserName,
            Environment.CurrentDirectory,
            Environment.ProcessorCount,
            Environment.WorkingSet,
            string.Join(",", drives));

        Console.WriteLine("\n\n正在獲取網路配置.....");
        var hostName = Dns.GetHostName();
        Console.WriteLine("HostName:{0}", hostName);
        var addresses = Dns.GetHostAddresses(hostName);
        foreach (var item in addresses)
        {
            IPAddress ip = item.MapToIPv4();
            Console.WriteLine("AddressFamily:{0} \tAddress:{1}", ip.AddressFamily, ip);
        }

        Console.WriteLine("\n\n正在上報啟動資訊.....");
        Console.WriteLine("=========== Ron.Init.dll 結束 ===========");
    }
}

上面的程式碼即表示一個標準的鉤子程式,在 Initialize() 內部,進行託管主機檢查,獲取網路配置等行為,最好,還列印一條上報到遙測伺服器的資訊,這裡是模擬上報檢查報告,最後輸出結束資訊
程式碼非常檢查,現在開啟 Ron.HooksDemo 專案屬性頁進行鉤子掛載

上圖新增環境變數 DOTNET_STARTUP_HOOKS ,並設定其值為 C:\Users\Administrator\Source\Repos\Ron.HooksDemo\Ron.Init\bin\Debug\netcoreapp2.2\Ron.Init.dll,這是本次示例的鉤子程式絕對路徑
注意:該環境變數的值不支援相對路徑,如果嘗試使用相對路徑,託管主機將丟擲 ArgumentException 異常

2.2 執行程式,看看是否正確掛載了鉤子程式 Ron.Init

上圖紅色部分輸出資訊表示鉤子程式掛載成功,藍色部分表示託管主機已啟動,可以看到,託管主機啟動是在掛載鉤子之後執行的
一定要注意,鉤子是在託管程式的 Main 方法之前執行的

3. 掛載多個鉤子

3.1 一個託管程式可以掛載多個鉤子

掛載多個鉤子的方法是設定環境變數 DOTNET_STARTUP_HOOKS 的值,多個鉤子按順序執行,其中 Windows 和 Unix 掛載多個鉤子的方式基本相同,這其中,有一點微小的區別

  • Windows 平臺掛載方式
DOTNET_STARTUP_HOOKS = C:\Hooks_1.dll;C:\Hooks_2.dll
  • Unix 平臺掛載方式
DOTNET_STARTUP_HOOKS =/data/Hooks_1.dll:/data/Hooks_2.dll

以上 DOTNET_STARTUP_HOOKS 變數的值包含兩個鉤子程式,其中 Windows 平臺的值為使用分號(;)進行分隔,Unix 平臺使用冒號(:)進行分隔,這於傳統使用方式一致

3.2 執行掛載了多個鉤子的託管程式
  • 下面把兩個鉤子掛載到 Ron.HooksDemo 專案後,他們分別是:Ron.Init 和 Ron.License

    Ron.Init 鉤子輸出的是檢查伺服器資訊,這個資訊在之前已經演示,這裡不再重複,下面看 Ron.License 程式碼

    public static void Initialize()
    {
        Console.WriteLine("\n\n程式集:Ron.License.dll");
        Console.WriteLine("作者:Ron.liang");
        Console.WriteLine("部落格地址:https://www.cnblogs.com/viter/\n\n");
        Console.WriteLine("=========== Ron.License.dll 結束 ===========");
    }
  • 鉤子程式的 Ron.License 程式碼也非常簡單,結構和 Ron.Init 鉤子程式一致,只是簡單的輸出版權資訊
3.3 執行 Ron.HooksDemo 程式,看下圖輸出結果

紅色部分是 Ron.Init 鉤子輸出資訊,黃色部分是 Ron.License 輸出資訊,藍色部分是託管主機 Ron.HooksDemo 輸出資訊
可以看到,鉤子上安裝掛載的順序執行的

4. 在鉤子中載入額外的程式集

我們應該這麼理解,鉤子程式也是一個普通的應用程式集;所以一個普通的程式集能做到事情,鉤子也一樣可以

4.1 在 Ron.License 載入一個程式集 Ron.Service,Ron.Service 中定義了一個類 UserService,繼承自並實現 IDisposable 介面
    public class UserService : IDisposable
    {
        public void Dispose()
        {
            Console.WriteLine("程式集:Ron.Service.dll");
            Console.WriteLine("動態載入程式集,執行清理任務已完成\n\n");
            Console.WriteLine("=========== Ron.Service.dll 結束 ===========");
        }
    }
4.2 在 Ron.License 的鉤子方法中載入 Ron.Service 程式集,建立 IDisposable 的實現,並呼叫 Dispose() 方法
internal class StartupHook
{
    public static void Initialize()
    {
        Console.WriteLine("\n\n程式集:Ron.License.dll");
        Console.WriteLine("作者:Ron.liang");
        Console.WriteLine("部落格地址:https://www.cnblogs.com/viter/\n\n");

        string path = @"C:\Users\Administrator\Source\Repos\Ron.HooksDemo\Ron.Service\bin\Debug\netcoreapp2.2\Ron.Service.dll";
        var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(path);
        dynamic obj = assembly.CreateInstance("Ron.Service.UserService");
        obj.Dispose();

        Console.WriteLine("=========== Ron.License.dll 結束 ===========");
    }
}
4.3 執行程式 Ron.HooksDemo

從輸出結果看到,Ron.Service 程式集已被成功載入並呼叫,控制檯紅色輸出資訊部分表示載入成功

5. 在 Asp.Net Web Api 專案中使用鉤子

Web Api 專案掛載鉤子的方式和控制檯方式相同,首先我們還是建立一個 Web Api 專案 Ron.HooksDemo.Web
接著掛載鉤子

  "DOTNET_STARTUP_HOOKS": "C:\\Users\\Administrator\\Source\\Repos\\Ron.HooksDemo\\Ron.Init\\bin\\Debug\\netcoreapp2.2\\Ron.Init.dll;C:\\Users\\Administrator\\Source\\Repos\\Ron.HooksDemo\\Ron.License\\bin\\Debug\\netcoreapp2.2\\Ron.License.dll"
5.1 執行 Web Api 專案 Ron.HooksDemo.Web

紅色輸出部分表示 Web Api 程式的 Main 方法在鉤子列表執行完成之後成功啟動,這表示在 .Net Core 中,掛載鉤子的方式是一致的,其行為也相同

結束語

使用鉤子程式注意事項

  1. 鉤子程式不能依賴於託管主機的TPA列表之外的任何程式集,否則會丟擲 FileNotFoundException 的異常
  2. 不要掛載過多的鉤子程式,這可能會出現相容性問題,如果要使用多個鉤子,必須確保每個鉤子程式的行為都是獨立的,互不干擾的,如果一定要使用,建議修改託管主機的程式碼,使用依賴注入的方式而不是鉤子
  3. StartupHook 類應該是 internal 型別的,如果是使用 public 進行修飾,還是可以正常載入鉤子程式

演示程式碼下載

https://files.cnblogs.com/files/viter/Ron.HooksDemo.zip