.NET Core 依赖注入(IOC) 使用技巧

前言

.NET Core 在使用IOC后,我们不必再浪费精力在管理实例的生命周期上,交给IOC代替我们管理,减少我们成吨的代码,面向接口编程更是灵活到了极致,而IOC的三种生命周期应该怎么去使用呢,Transient(瞬态)、Scoped(作用域)、Singleton(单例)。

Transient(瞬态)

这个没什么好说的,就是每次注入的时候,容器自动 new 一个实例,用完就丢;

Scoped(作用域).

以Web来说,作用域的生命周期就是当次请求,请求开始后的第一次注入,就是它生命的开始,直到请求结束;

我个人常用来减少数据获取,提升请求响应,举一个例子:A服务是获取全国地级市信息的,以作用域的方式注册到IOC容器中,B、C、D 都注入了A服务并使用了它;一个业务接口,刚好涉及到了B、C、D,当接口被调用,代码执行到了B,第一次调用了 A 服务请求数据库获取了全国地级市数据;然后执行到了C,又一次使用了A服务获取了数据,最后D;一个请求下来,A被使用了3次,获取了3个同样的数据结果,这完全是在浪费资源;

public class AService
{
    public async CityData GetCityDataAsync()
    {
        return await GetDatasFromApi();
    }
}

public class BService
{
    private readonly AService _aService;

    public BService(AService aService)
    {
        _aService = aService;
    }
    
    public async Task Execute()
    {
        await _aService.GetCityDataAsync();
    }
}

public class CService
{
    private readonly AService _aService;

    public CService(AService aService)
    {
        _aService = aService;
    }

    public async Task Execute()
    {
        await _aService.GetCityDataAsync();
    }
}

public class DService
{
    private readonly AService _aService;

    public DService(AService aService)
    {
        _aService = aService;
    }

    public async Task Execute()
    {
        await _aService.GetCityDataAsync();
    }
}

不是可以定义一个变量,请求数据前先判断这个变量有没有值,没有就去获取数据,给它赋值,下次再调用的时候,直接返回这个变量的数据,这样不管这个服务被调用多少次,它也只是调用了一次数据库,大大节省了资源。

public class AService
{
    private List<CityData> cityDatas;
        
    public async List<CityData> GetCityDataAsync()
    {
        if(cityDatas == null|| !cityDatas.Any())
        {
            cityDatas = await GetDatasFromApi();
        }
        
        return cityDatas;
    }
}

有人可能说会说了,不就是多调用几次数据库,现在服务器的性能这么好,不在乎这一点的资源;确实,如果只是多调用几次数据库,对于一些小系统来说,跟挠痒痒一样,那这里的调用数据库换成调用 WebApi 呢?

如果还是调用第三方的 API 呢?一次Http请求,不往大的说,从请求到获取到数据,花个100ms很正常吧(网络非常好的情况当我没说),那这个接口不需要多,调用10次就1s了,还没算上其它业务逻辑的耗时呢,如果还需要调用其它的api,那响应时间就更长咯,难道你让用户打开一个页面,或者点击一个按钮,需要等上两三秒才有响应吗,这样的用户体验就非常糟糕了。

Singleton(单例)

来自依赖关系注入容器的服务实现的每一个后续请求都使用同一个实例。如果应用需要单一实例行为,则允许服务容器管理服务的生存期。必须是线程安全的,并且通常在无状态服务中使用。

在单例中,不要直接注入作用域的服务,这会引起很多莫名其妙的错误(经过评论区大佬的指正,修正这个不恰当的用词,这里引用大佬的一段话) 单例中引用Scoped,Scoped就会提升为单例,这就很容易发生错误,一定要使用的话,就自己创建,自己管理它的生命周期:

public class Scope
{
    private readonly IServiceScopeFactory _serviceScopeFactory;
    public Scope(IServiceScopeFactory serviceScopeFactory)
    {
        _serviceScopeFactory = serviceScopeFactory;
    }

    public async Task CreateScope()
    {
        using var scope = _serviceScopeFactory.CreateScope();

        var service = scope.ServiceProvider.GetRequiredService<IService>();
    }
}

ActivatorUtilities

有些情况下,例如当你不想把使用次数极低的类注册到容器中,或者这个类的构造函数需要传入一些参数,但是又需要用到容器中的服务的时候,你可以使用 ActivatorUtilities 中的 CreateInstance 去创建它,它会自动给构造函数注入所需的服务,并且还可以给构造函数传参,满足上面所说情况的需求。

public class TestTask : ITask
{
    private readonly IServiceScopeFactory _serviceScopeFactory;

    private readonly IRabbitMQService _rabbitMQService;

    private readonly string _name;

    public TestTask(IServiceScopeFactory serviceScopeFactory, IRabbitMQService rabbitMQService, string name)
    {
        _serviceScopeFactory = serviceScopeFactory;
        _rabbitMQService = rabbitMQService;
        _name = name;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    /// <exception cref="NotImplementedException"></exception>
    public async Task Execute(CancellationToken cancellationToken)
    {
        try
        {
            using var scope = _serviceScopeFactory.CreateScope();

            var executionService = scope.ServiceProvider.GetService<ITaskExecutionService>();

            if (executionService != null)
            {
                await _rabbitMQService.RabbitMQReceiveService.SingleAsync(executionService.ExecuteAsync);
            }
        }
        catch (Exception err)
        {

            Console.WriteLine(err.Message);
        }
    }
}

使用 ActivatorUtilities 创建:

var testTask = ActivatorUtilities.CreateInstance<TestTask>(serviceProvider, "test");
await testTask.Execute(new CancellationToken());

写在最后

Bootstrap Blazor 官网地址:https://www.blazor.zone

希望大佬们看到这篇文章,能给项目点个star支持下,感谢各位!

Star流程:

1、访问点击项目链接:https://gitee.com/LongbowEnterprise/BootstrapBlazor

2、点击star,如下图,即可完成star,关注项目不迷路:

.NET Core 依赖注入(IOC) 使用技巧

另外还有两个GVP项目,大佬们方便的话也点下star呗,非常感谢:

BootstrapAdmin:https://gitee.com/LongbowEnterprise/BootstrapAdmin

SliderCaptcha :https://gitee.com/LongbowEnterprise/SliderCaptcha