.NET 使用自帶 DI 批次注入服務(Service)和 後臺服務(BackgroundService)

2022-07-20 06:01:14
今天教大家如何在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 基礎框架專案,專案地址如下