.Net Core 依賴注入(IOC) 一些簡單的使用技巧

語言: CN / TW / HK

.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個同樣的資料結果,這完全是在浪費資源;

 1 public class AService
 2 {
 3     public async CityData GetCityDataAsync()
 4     {
 5         return await GetDatasFromApi();
 6     }
 7 }
 8 
 9 public class BService
10 {
11     private readonly AService _aService;
12 
13     public BService(AService aService)
14     {
15         _aService = aService;
16     }
17     
18     public async Task Execute()
19     {
20         await _aService.GetCityDataAsync();
21     }
22 }
23 
24 public class CService
25 {
26     private readonly AService _aService;
27 
28     public CService(AService aService)
29     {
30         _aService = aService;
31     }
32 
33     public async Task Execute()
34     {
35         await _aService.GetCityDataAsync();
36     }
37 }
38 
39 public class DService
40 {
41     private readonly AService _aService;
42 
43     public DService(AService aService)
44     {
45         _aService = aService;
46     }
47 
48     public async Task Execute()
49     {
50         await _aService.GetCityDataAsync();
51     }
52 }

那我們應該怎麼做呢,首先我們回顧一下 Scoped 的生命週期,可以說是一個例項,貫穿了整一個請求,那我們是不是可以定義一個變數,請求資料前先判斷這個變數有沒有值,沒有就去獲取資料,給它賦值,下次再呼叫的時候,直接返回這個變數的資料,這樣不管這個服務被呼叫多少次,它也只是呼叫了一次資料庫,大大節省了資源。

 1 public class AService
 2 {
 3     private List<CityData> cityDatas;
 4         
 5     public async List<CityData> GetCityDataAsync()
 6     {
 7         if(cityDatas == null|| !cityDatas.Any())
 8         {
 9             cityDatas = await GetDatasFromApi();
10         }
11         
12         return cityDatas;
13     }
14 }

有人可能說會說了,不就是多呼叫幾次資料庫,現在伺服器的效能這麼好,不在乎這一點的資源;確實,如果只是多呼叫幾次資料庫,對於一些小系統來說,跟撓癢癢一樣,那這裡的呼叫資料庫換成呼叫 WebApi 呢?如果還是呼叫第三方的 API 呢?一次Http請求,不往大的說,從請求到獲取到資料,花個100ms很正常吧(網路非常好的情況當我沒說),那這個介面不需要多,呼叫10次就1s了,還沒算上其它業務邏輯的耗時呢,如果還需要呼叫其它的api,那響應時間就更長咯,難道你讓使用者開啟一個頁面,或者點選一個按鈕,需要等上兩三秒才有響應嗎,這樣的使用者體驗就非常糟糕了。

Singleton(單例)

來自依賴關係注入容器的服務實現的每一個後續請求都使用同一個例項。 如果應用需要單一例項行為,則允許服務容器管理服務的生存期。必須是執行緒安全的,並且通常在無狀態服務中使用。

在單例中,不要直接注入作用域的服務,這會引起很多莫名其妙的錯誤,一定要使用的話,就自己建立,自己管理它的生命週期:

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 去建立它,它會自動給建構函式注入所需的服務,並且還可以給建構函式傳參,滿足上面所說情況的需求。

 1 public class TestTask : ITask
 2 {
 3     private readonly IServiceScopeFactory _serviceScopeFactory;
 4 
 5     private readonly IRabbitMQService _rabbitMQService;
 6 
 7     private readonly string _name;
 8 
 9     public TestTask(IServiceScopeFactory serviceScopeFactory, IRabbitMQService rabbitMQService, string name)
10     {
11         _serviceScopeFactory = serviceScopeFactory;
12         _rabbitMQService = rabbitMQService;
13         _name = name;
14     }
15 
16     /// <summary>
17     /// 
18     /// </summary>
19     /// <param name="cancellationToken"></param>
20     /// <returns></returns>
21     /// <exception cref="NotImplementedException"></exception>
22     public async Task Execute(CancellationToken cancellationToken)
23     {
24         try
25         {
26             using var scope = _serviceScopeFactory.CreateScope();
27 
28             var executionService = scope.ServiceProvider.GetService<ITaskExecutionService>();
29 
30             if (executionService != null)
31             {
32                 await _rabbitMQService.RabbitMQReceiveService.SingleAsync(executionService.ExecuteAsync);
33             }
34         }
35         catch (Exception err)
36         {
37 
38             Console.WriteLine(err.Message);
39         }
40     }
41 }

使用 ActivatorUtilities 建立:

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

寫在最後

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

希望大佬們看到這篇文章,能給專案點個star支援下,感謝各位!

star流程:

1、訪問點選專案連結: BootstrapBlazor

2、點選star,如下圖,即可完成star,關注專案不迷路:

另外還有兩個GVP專案,大佬們方便的話也點下star唄,非常感謝:

BootstrapAdmin 專案地址:

https://gitee.com/LongbowEnterprise/BootstrapAdmin

SliderCaptcha 專案地址: https://gitee.com/LongbowEnterprise/SliderCaptcha

交流群(QQ)歡迎加群討論

BA & Blazor ①(795206915)    BA & Blazor ②(675147445)