前言
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 中,掛載鈎子的方式是一致的,其行為也相同
結束語
使用鈎子程序注意事項
- 鈎子程序不能依賴於托管主機的TPA列表之外的任何程序集,否則會拋出 FileNotFoundException 的異常
- 不要掛載過多的鈎子程序,這可能會出現兼容性問題,如果要使用多個鈎子,必須確保每個鈎子程序的行為都是獨立的,互不干擾的,如果一定要使用,建議修改托管主機的代碼,使用依賴注入的方式而不是鈎子
- StartupHook 類應該是 internal 類型的,如果是使用 public 進行修飾,還是可以正常加載鈎子程序
演示代碼下載
https://github.com/lianggx/EasyAspNetCoreDemo/tree/master/Ron.HooksDemo