.NET 使用自帶 DI 批量注入服務(Service)和 後台服務(BackgroundService)
- 2022 年 7 月 19 日
- 筆記
今天教大家如何在asp .net core 和 .net 控制台程序中 批量注入服務和 BackgroundService 後台服務
在默認的 .net 項目中如果我們注入一個服務或者後台服務,常規的做法如下
註冊後台服務
builder.Services.AddHostedService<ClearLogTask>();
針對繼承自接口的服務進行注入:
builder.Services.AddTransient<IOperationTransient, Operation>(); builder.Services.AddScoped<IOperationScoped, Operation>(); builder.Services.AddSingleton<IOperationSingleton, Operation>(); builder.Services.AddSingleton(new Operation()); builder.Services.AddScoped(typeof(Operation)); builder.Services.AddTransient(typeof(Operation));
針對非繼承自接口的無構造函數類進行注入
builder.Services.AddSingleton(new Operation()); builder.Services.AddSingleton(typeof(Operation)); builder.Services.AddScoped(typeof(Operation)); builder.Services.AddTransient(typeof(Operation));
針對非繼承自接口的有構造函數的類進行注入(此類型只支持進行單例注入)
builder.Services.AddSingleton(new Operation("參數1","參數2"));
上面是常見的幾種在項目啟動時注入服務的寫法,當項目存在很多服務的時候,我們需要一條條的注入顯然太過繁瑣,所以今天來講一種批量注入的方法,本文使用的是微軟默認的DI 沒有去使用 AutoFac ,個人喜歡大道至簡,能用官方實現的,就盡量的少去依賴第三方的組件,下面直接展示成果代碼。
public static class IServiceCollectionExtension { public static void BatchRegisterServices(this IServiceCollection services) { var allAssembly = GetAllAssembly(); services.RegisterServiceByAttribute(ServiceLifetime.Singleton, allAssembly); services.RegisterServiceByAttribute(ServiceLifetime.Scoped, allAssembly); services.RegisterServiceByAttribute(ServiceLifetime.Transient, allAssembly); services.RegisterBackgroundService(allAssembly); } /// <summary> /// 通過 ServiceAttribute 批量註冊服務 /// </summary> /// <param name="services"></param> /// <param name="serviceLifetime"></param> private static void RegisterServiceByAttribute(this IServiceCollection services, ServiceLifetime serviceLifetime, List<Assembly> allAssembly) { List<Type> types = allAssembly.SelectMany(t => t.GetTypes()).Where(t => t.GetCustomAttributes(typeof(ServiceAttribute), false).Length > 0 && t.GetCustomAttribute<ServiceAttribute>()?.Lifetime == serviceLifetime && t.IsClass && !t.IsAbstract).ToList(); foreach (var type in types) { Type? typeInterface = type.GetInterfaces().FirstOrDefault(); if (typeInterface == null) { //服務非繼承自接口的直接注入 switch (serviceLifetime) { case ServiceLifetime.Singleton: services.AddSingleton(type); break; case ServiceLifetime.Scoped: services.AddScoped(type); break; case ServiceLifetime.Transient: services.AddTransient(type); break; } } else { //服務繼承自接口的和接口一起注入 switch (serviceLifetime) { case ServiceLifetime.Singleton: services.AddSingleton(typeInterface, type); break; case ServiceLifetime.Scoped: services.AddScoped(typeInterface, type); break; case ServiceLifetime.Transient: services.AddTransient(typeInterface, type); break; } } } } /// <summary> /// 註冊後台服務 /// </summary> /// <param name="services"></param> /// <param name="serviceLifetime"></param> private static void RegisterBackgroundService(this IServiceCollection services, List<Assembly> allAssembly) { List<Type> types = allAssembly.SelectMany(t => t.GetTypes()).Where(t => typeof(BackgroundService).IsAssignableFrom(t) && t.IsClass && !t.IsAbstract).ToList(); foreach (var type in types) { services.AddSingleton(typeof(IHostedService), type); } } /// <summary> /// 獲取全部 Assembly /// </summary> /// <returns></returns> private static List<Assembly> GetAllAssembly() { var allAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToList(); HashSet<string> loadedAssemblies = new(); foreach (var item in allAssemblies) { loadedAssemblies.Add(item.FullName!); } Queue<Assembly> assembliesToCheck = new(); assembliesToCheck.Enqueue(Assembly.GetEntryAssembly()!); while (assembliesToCheck.Any()) { var assemblyToCheck = assembliesToCheck.Dequeue(); foreach (var reference in assemblyToCheck!.GetReferencedAssemblies()) { if (!loadedAssemblies.Contains(reference.FullName)) { var assembly = Assembly.Load(reference); assembliesToCheck.Enqueue(assembly); loadedAssemblies.Add(reference.FullName); allAssemblies.Add(assembly); } } } return allAssemblies; } } [AttributeUsage(AttributeTargets.Class)] public class ServiceAttribute : Attribute { public ServiceLifetime Lifetime { get; set; } = ServiceLifetime.Transient; }
實現的邏輯其實並不複雜,首先利用循環檢索找出項目中所有的 Assembly
獲取項目所有 Assembly 這個方法,需要格外注意,因為 .NET 項目在啟動的時候並不會直接把所有 dll 都進行加載,甚至有時候項目經過分層之後服務可能分散於多個類庫中,所以我們這裡需要循環的將項目所有的 Assembly 信息全部查詢出來,確保萬無一失。
當找到全部的 Assembly 之後只要查詢中 包含我們指定的 ServiceAttribute 裝飾屬性的類和 繼承自 BackgroundService 類型的所有類型,然後進行依次注入即可。
主要在原先的服務類頭部加上
[Service(Lifetime = ServiceLifetime.Scoped)]
或
[Service(Lifetime = ServiceLifetime.Singleton)]
或
[Service(Lifetime = ServiceLifetime.Transient)]
像下面的 AuthorizeService 只要只要在頭部加上 [Service(Lifetime = ServiceLifetime.Scoped)]
[Service(Lifetime = ServiceLifetime.Scoped)] public class AuthorizeService { private readonly DatabaseContext db; private readonly SnowflakeHelper snowflakeHelper; private readonly IConfiguration configuration; public AuthorizeService(DatabaseContext db, SnowflakeHelper snowflakeHelper, IConfiguration configuration) { this.db = db; this.snowflakeHelper = snowflakeHelper; this.configuration = configuration; } /// <summary> /// 通過用戶id獲取 token /// </summary> /// <param name="userId"></param> /// <returns></returns> public string GetTokenByUserId(long userId) { //此處省略業務邏輯 } }
至於註冊後台服務,則連裝飾屬性都不需要加,如下面的的一個後台服務示例代碼
public class ClearLogTask : BackgroundService { private readonly IServiceProvider serviceProvider; public DemoTask(IServiceProvider serviceProvider) { this.serviceProvider = serviceProvider; } protected override Task ExecuteAsync(CancellationToken stoppingToken) { return Task.Run(() => { var timer = new Timer(1000 * 5); timer.Elapsed += TimerElapsed; timer.Start(); }, stoppingToken); } private void TimerElapsed(object? sender, ElapsedEventArgs e) { //省略業務邏輯 } }
像上面的這個清理日誌服務,每5秒鐘會執行一次,按照微軟的語法所有的後台服務都是繼承自 BackgroundService 類型的。
然後我們項目啟動的時候只要調用一下我們寫的批量註冊服務擴展方法即可。這樣就批量完成了對項目中所有的服務和後台服務的注入。
builder.Services.BatchRegisterServices();
至此 .NET 使用自帶 DI 批量注入服務(Service) 和 後台服務(BackgroundService)就講解完了,有任何不明白的,可以在文章下面評論或者私信我,歡迎大家積極的討論交流,有興趣的朋友可以關注我目前在維護的一個 .net 基礎框架項目,項目地址如下