首页 > 其他分享 >.NET 使用自带 DI 批量注入服务(Service)和 后台服务(BackgroundService)

.NET 使用自带 DI 批量注入服务(Service)和 后台服务(BackgroundService)

时间:2022-08-30 23:47:00浏览次数:46  
标签:服务 Service DI builder BackgroundService Services ServiceLifetime services type

在默认的 .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();

 

标签:服务,Service,DI,builder,BackgroundService,Services,ServiceLifetime,services,type
From: https://www.cnblogs.com/lgx5/p/16641375.html

相关文章

  • (三)redis命令学习,List
    packageredis.clients.jedis.commands;importjava.util.List;importredis.clients.jedis.args.ListDirection;importredis.clients.jedis.args.ListPosition;im......
  • (二)Redis命令学习之 StringCommands
    packageredis.clients.jedis.commands;importjava.util.List;importredis.clients.jedis.args.BitCountOption;importredis.clients.jedis.args.BitOP;importr......
  • (开集检测系列)MDETR - Modulated Detection for End-to-End Multi-Modal Understandin
    caption数据+DETR做开集检测1、动机1、只在固定object和属性上训练,解决不了长尾的问题/开集2、方法2.1优势1、MDETR仅依赖于文本和对齐的框作为图像中概念形式进......
  • isdigit() isalpha()
    需要包含头文件#include<ctype.h>  isdigit()函数可以用来判断字符是否为数字功能:当判断的字符是数字时,函数返回1~9的非零值,当判断的字符不是数字时,函数返回0......
  • 手机播放linux PulseAudio 声音 rtp推流
    https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/Network/RTP/#howtolistentothepulseaudiortpstreamhttps://www.freedesktop.org/wiki/Softw......
  • 50道Redis高频面试题(13-20)
    十三、线上Redis持久化策略一般如何设置如果对性能要求较高,在master最好不要做持久化,可以在某个slave开启aof备份数据,策略设置为每秒同步一次即可。十四、一次线上事故,Re......
  • Redis基本使用
    /***@Author:KongXiao*@Date:2022/8/30-14:01*@Description:*www.redis.net.cn中文网站*Redis字符串类型*SETkeyvalue*GETkey......
  • redis+lua实现令牌桶限流
    github下载直接使用。无任何冗余代码: [email protected]:wangbensen/zhongtai-ext.gitLua脚本--[[1.key-令牌桶的key2.intervalPerTokens-生成令牌的间隔(m......
  • service+controller+dao(mapper)趣味解读
    service:服务层,你可以理解为服务员controller:控制层,你可以理解为厨师dao:数据持久层,你可以理解为除使用的你自己服务员就负责接待你和给厨师反馈是不是,你可以想一下,你去吃......
  • Linq:Distinct()不能排除重复对象的解决方案
    1.数据准备:假设有几个重复数据,如下,(正常使用Distinct()方法,我们想要排除掉重复的对象)usingSystem.Collections.Generic;namespaceLINQTutorial{publicclassS......