前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >.NET 使用自带 DI 批量注入服务(Service)和 后台服务(BackgroundService)

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

作者头像
乌拉栋
发布2022-10-28 09:13:06
8490
发布2022-10-28 09:13:06
举报
文章被收录于专栏:编程宝典编程宝典

今天教大家如何在asp .net core 和 .net 控制台程序中 批量注入服务和 BackgroundService 后台服务

在默认的 .net 项目中如果我们注入一个服务或者后台服务,常规的做法如下

注册后台服务

代码语言:javascript
复制
builder.Services.AddHostedService<ClearLogTask>();

针对继承自接口的服务进行注入:

代码语言:javascript
复制
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));

针对非继承自接口的无构造函数类进行注入

代码语言:javascript
复制
builder.Services.AddSingleton(new Operation());
builder.Services.AddSingleton(typeof(Operation));
builder.Services.AddScoped(typeof(Operation));
builder.Services.AddTransient(typeof(Operation));

针对非继承自接口的有构造函数的类进行注入(此类型只支持进行单例注入)

代码语言:javascript
复制
builder.Services.AddSingleton(new Operation("参数1","参数2"));

上面是常见的几种在项目启动时注入服务的写法,当项目存在很多服务的时候,我们需要一条条的注入显然太过繁琐,所以今天来讲一种批量注入的方法,本文使用的是微软默认的DI 没有去使用 AutoFac ,个人喜欢大道至简,能用官方实现的,就尽量的少去依赖第三方的组件,下面直接展示成果代码。

代码语言:javascript
复制
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 类型的所有类型,然后进行依次注入即可。

只要在原先的服务类头部加上

代码语言:javascript
复制
[Service(Lifetime = ServiceLifetime.Scoped)]

代码语言:javascript
复制
[Service(Lifetime = ServiceLifetime.Singleton)]

代码语言:javascript
复制
[Service(Lifetime = ServiceLifetime.Transient)]

像下面的 AuthorizeService 只要只要在头部加上 [Service(Lifetime = ServiceLifetime.Scoped)]

代码语言:javascript
复制
[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)
    {
        //此处省略业务逻辑
    }

}

至于注册后台服务,则连装饰属性都不需要加,如下面的的一个后台服务示例代码

代码语言:javascript
复制
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 类型的。

然后我们项目启动的时候只要调用一下我们写的批量注册服务扩展方法即可。这样就批量完成了对项目中所有的服务和后台服务的注入。

代码语言:javascript
复制
builder.Services.BatchRegisterServices();

至此 .NET 使用自带 DI 批量注入服务(Service) 和 后台服务(BackgroundService)就讲解完了,有任何不明白的,可以在文章下面评论或者私信我,欢迎大家积极的讨论交流,有兴趣的朋友可以关注我目前在维护的一个 .net 基础框架项目,项目地址如下

https://github.com/berkerdong/NetEngine.git

https://gitee.com/berkerdong/NetEngine.git

本文的实现逻辑参考自 https://www.cnblogs.com/willardzmh/p/14393696.html 感谢 @willardzmh 提供的思路

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-07-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
日志服务
日志服务(Cloud Log Service,CLS)是腾讯云提供的一站式日志服务平台,提供了从日志采集、日志存储到日志检索,图表分析、监控告警、日志投递等多项服务,协助用户通过日志来解决业务运维、服务监控、日志审计等场景问题。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档