.NET 使用自带 DI 批量注入服务(Service)和 后台服务(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 ClearLogTask(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();