1.避免Scoped模式註冊的服務變成Singleton模式 當提供一個生命周期模式為Singleton的服務實例時,如果發現該服務中還依賴生命周期模式為Scoped的服務實例(Scoped服務實例將被一個Singleton服務實例所引用),那麼這個被依賴的Scoped服務實例最終會成為一個Sing ...
1.避免Scoped模式註冊的服務變成Singleton模式
當提供一個生命周期模式為Singleton的服務實例時,如果發現該服務中還依賴生命周期模式為Scoped的服務實例(Scoped服務實例將被一個Singleton服務實例所引用),那麼這個被依賴的Scoped服務實例最終會成為一個Singleton模式的服務實例。這是因為提供Singleton服務的容器是根容器,Scoped服務間接的被根容器所創建提供了,如果Scoped服務由子容器進行提供,那麼Singleton和Scoped這兩種生命周期模式才會產生差別。
在ASP.NET Core應用中,將某個服務註冊的生命周期設置為Scoped,其意圖是希望Scoped服務實例的創建和釋放是作用於某個HTTP請求範圍內的。如果不註意,將Scoped服務實例引用到了Singleton服務實例中,對於這種情況Scoped和Singleton的服務實例沒有區別的。這樣的Scoped服務實例直到應用關閉才會被釋放,這無疑違背我們使用Scoped模式的初衷。這種“混淆”如果沒有察覺到,可能會在實際的應用中造成難以估量的後果,例如在Singleton服務中引用的Scoped服務是一個資料庫連接對象,這會導致資料庫長時間連接沒有及時釋放,從而導致程式出現異常。
為了避免Scoped模式註冊的服務“隱式”的變成Singleton模式的服務帶來的風險,.NET Core為我們提供了一種驗證方式來規避這樣的行為,這個方式就是將ServiceProviderOptions配置對象的ValidateScopes屬性設置為True。
當開啟了這個驗證後,依賴註入框架則會對註冊Scoped模式的服務進行檢查,確保不會出現如下情況:
- 有根容器去提供Scoped的服務實例;
- Singleton服務中存在對Scoped服務的依賴;
一旦開啟針對Scoped模式服務的註冊驗證,如果存在以上的兩種情況,那麼程式啟動時會拋出異常。
ValidateScopes的值在開發環境下預設值是為True,為了確保在生產環境或其他環境始終開啟驗證,我們可以在Program類的CreateHostBuilder方法中配置ServiceProviderOptions對象。
該驗證方式被官方稱作為“服務範圍”的驗證,並建議應用程式開啟此驗證,以確保我們註冊Scoped模式的服務僅作用於某個服務範圍,而不會“悄悄地”演變成作用於整個應用程式範圍的Singleton模式。
2.驗證服務註冊是否能夠提供相應的實例
依賴註入框架中進行服務註冊的信息一般都存放於ServiceDescriptor的對象中,而容器對象就是根據ServiceDescriptor對象中的註冊信息進行服務實例的提供。ServiceProviderOptions配置類型除了用於針對“服務範圍”的驗證ValidateScopes屬性之外,還有一個ValidateOnBuild屬性。如果將該屬性設置為True,這就意味著容器對象在構建時,會對每個ServiceDescriptor對象中的註冊信息實施有效性驗證,如果服務註冊信息不能提供出對應的實例則會拋出異常。
使用ValidateOnBuild屬性進行驗證的目的是因為,往往有些服務能夠正常註冊但不代表,容器能夠根據註冊信息成功的提供實例。下麵我將通過一個代碼示例來印證這一情況,並演示使用ValidateOnBuild屬性進行驗證的方式。
1 using Microsoft.Extensions.DependencyInjection;
2 using System;
3 using System.Diagnostics;
4
5 namespace ConsoleApp1
6 {
7
8 public interface IFooBar { }
9 public class FooBar : IFooBar
10 {
11 private FooBar() { }
12 }
13
14 internal class Program
15 {
16 static void Main(string[] args)
17 {
18 Console.WriteLine("ValidateOnBuild的值為True:");
19 BuildServiceProvider(true);
20 Console.WriteLine();
21
22 Console.WriteLine("ValidateOnBuild的值為False:");
23 BuildServiceProvider(false);
24
25 } // END Main()
26
27 static void BuildServiceProvider(bool validateOnBuild)
28 {
29 try
30 {
31 var options = new ServiceProviderOptions { ValidateOnBuild = validateOnBuild };
32 var provider= new ServiceCollection()
33 .AddSingleton<IFooBar, FooBar>()
34 .BuildServiceProvider(options);
35 Console.WriteLine($"程式運行正常;");
36 }
37 catch (Exception e)
38 {
39 Console.WriteLine($"程式出現異常;異常信息:{e.Message};");
40 }
41
42 } // END BuildServiceProvider()
43
44
45 }
46 }
在上面的代碼示例中,服務註冊時指定的實現類型為FooBar,而該類型中唯一的構造函數是一個私有的。我們都知道創建對象的實例,則必須要調用對象類型的構造函數,而FooBar的構造函數是私有的,無法對外界所調用,則也意味著無法創建相應的實例。
運行上面的代碼示例後我們會發現,對於ValidateOnBuild屬性值設置為False的情況,程式可以正常的執行服務註冊的方法,這種現象隱瞞了註冊時的錯誤(類型中存在私有構造函數),這種做法顯然會對後續使用到FooBar對象的程式功能帶來影響。而對於ValidateOnBuild屬性值設置為True的情況,程式則直接拋出了異常並且給出了詳細的錯誤信息,對於這種做法我們可以更好的規避服務註冊時所產生的風險。
本示例代碼是在“服務定位器模式”下配置的ValidateOnBuild屬性,這是為了更好的演示。當然,我們在實際的開發中通常是依賴註入的形式,那麼相應的方式是通過在CreateHostBuilder方法中ServiceProviderOptions對象進行配置,如下圖:
3.總結
本文介紹的關於“針對服務註冊的驗證”的主題,實際上就是介紹ServiceProviderOptions類型中ValidateScopes和ValidateOnBuild屬性的使用場景和方式。VailedateScopes屬性主要確保我們註冊的Scoped服務不會在某些情況下變成Singleton服務,ValidateOnBuild屬性主要用於驗證服務註冊信息是否能成功的提供出對應的服務示例。
知識改變命運