依赖注入在 dotnet core 中实现与使用:2 使用 Extensions DependencyInjection
既然是依賴注入容器,必然會涉及到服務的注冊,獲取服務實例,管理作用域,服務注入這四個方面。
服務注冊涉及如何將我們的定義的服務注冊到容器中。這通常是實際開發中使用容器的第一步,而容器本身通常是由框架來實例化的,大多數時候,并不需要自己初始化容器。
獲取服務實例這一步,在實際開發中通常也不涉及,服務示例一般是通過注入來實現的。這里只是為了讓我們對容器的使用了解的更全面一些。
管理作用域一般在開發中也不涉及,框架,例如 .NET 的 MVC 框架已經幫我們把這個問題處理了。
服務注入是我們需要關注的,不同的依賴注入容器支持不同的注入方式。在使用中,我們會通過注入來獲取服務對象的實例。而不是自己 new 出來。
?看起來很復雜,使用的時候其實很簡單。
1. 服務注冊
1.1 支持不同的作用域 Scope
DependencyInjection 通過 Add 方法來進行服務注冊,三種不同的作用域通過三種帶有不同后綴的 Add 方法來支持。
?
| services.AddSingleton<IUserService, UserService>();services.AddScoped<IUserService, UserService>();services.AddTransient<IUserService, UserService>(); |
Singleton 就是單例,以后通過該容器獲取出來的,都是同一個服務對象實例。
Scoped 就是限定了作用域,在每個特定的作用域中,只會有一個服務對象實例。作用域需要你自己來創建,后面在使用服務的時候,我們再介紹。
而 Transient 則在每次從容器中獲取的時候,都對創建新的服務對象實例。
三種作用域簡單明了,后面我們介紹服務注冊的時候,就不再關注服務的作用域,都使用單例來介紹,其它兩種方式是相同的。
1.2 基于接口注冊
這是最為常見的注冊方式,在實際開發中,服務一般都有對應的接口。為了方便注冊,在 .NET Core 中一般使用泛型方式進行注冊,這樣比較簡潔。是最推薦的方式。
| 1 | services.AddSingleton<IUserService, UserService>(); |
當然,也可以使用基于類型的方式注冊,不過代碼沒有使用泛型方式優雅。你需要先獲取服務的類型,再通過類型進行注冊。
| 1 | services.AddSingleton(typeof(IUserService),?typeof(UserService)); |
1.3 直接注冊實例
如果服務并沒有對應的接口,可以直接使用對象實例注冊,ServiceCollection 會直接使用該實例類型作為服務接口類型來注冊,這種方式比較簡單粗暴。
| 1 | services.AddSingleton(typeof(UserService)); |
?
1.4 注冊泛型服務
泛型是很強大的特性,例如,泛型的倉儲,我們只需要一個泛型倉儲,就可以通過它訪問針對不同類型的數據。
容器必須要支持泛型的服務,例如,針對下面的簡化倉儲示例,注意這里的 Get() 簡化為只返回默認值。
| 1234567891011121314 | public?interface?IRepository<T>{????T Get(int?id);}public?class?Repository<T>: IRepository<T> {????public?Repository() {????????Console.WriteLine(typeof(T).Name);????}????public?T Get(int?id){????????return?default(T);????}} |
只需要注冊一次,就可以獲得不同實現的支持。但是,泛型比較特殊,不能這樣寫:
| 1 | services.AddSingleton<IRepository<>, Repository<>>(); |
你需要通過類型的方式來進行服務注冊。
| 1 | services.AddSingleton(typeof(IRepository<>),?typeof(Repository<>)); |
如果沒有這個倉儲接口的話,就可以這樣注冊。
| 1 | services.AddSingleton(typeof(Repository<>)); |
如果涉及到多個類型的泛型,例如倉儲的定義變成下面這樣,id 的類型使用 K 來指定。
| 1234 | public?interface?IRepository<T, K>{????T Get(K id);} |
而倉儲的實現也變成下面這樣:
| 123456789 | public?class?Repository<T, K>: IRepository<T, K> {????public?Repository() {????????Console.WriteLine(typeof(T).Name);????}????public?T Get(K id){????????return?default(T);????}} |
注冊的時候,就需要寫成下面的樣子:
| 1 | services.AddSingleton (typeof?(IRepository<,>),?typeof?(Repository<,>)); |
| 1 |
1.5 工廠模式注冊
除了讓容器幫助構造服務對象實例,我們也可以自己定義構建服務對象實例的工廠供容器使用。
如果服務實現沒有參數,例如 UserRepository,可以這樣寫:
| 123 | services.AddSingleton<IUserRepository>( (serviceProvider) => {????return?new?UserReposotory();????} ); |
如果構造函數是有參數的,比如:
| 12345 | public?class?UserReposotory: IUserRepository {????public?UserReposotory(string?name){????????Console.WriteLine( name);????}} |
注冊就變成了下面的樣子,需要注意的是,工廠會得到一個 ServiceProvider 的實例供你使用。
| 1234 | services.AddSingleton<IUserRepository> ((serviceProvider) => {????????????Console.WriteLine ("construction IUserReposority");????????????return?new?UserReposotory ("Tom");????????}); |
1.6 直接使用?ServiceDescriptor 注冊
實際上,上面各種注冊方式最終都會通過創建一個服務描述對象來完成注冊,它的定義如下,你可以看到上面各種注冊方式所涉及的痕跡。
| 12345678 | public?class?ServiceDescriptor{????public?ServiceLifetime Lifetime {?get; }????public?Type ServiceType {?get; }????public?Type ImplementationType {?get; }????public?object?ImplementationInstance {?get; }????public?Func<IServiceProvider,?object> ImplementationFactory {?get; }} |
所以,實際上,你可以通過自己創建這個服務描述對象來進行注冊。
| 1 | services.Add(ServiceDescriptor.Singleton<IUserService, UserService>()); |
殊途同歸,其實與使用上面的方式效果是一樣的。
2 管理 Scope
如果注冊服務的時候,指定了服務是單例的,無論從哪個 Scope 中獲得的服務實例,都會是同一個。所以,單例的服務與 Scope 就沒有什么關系了。
如果注冊服務的時候,指定了服務是瞬態的,無論從哪個 Scope 中獲取服務實例,都會是新創建的,每次獲取就新創建一個。所以,瞬態的服務與 Scope 也沒有什么關系了。
只有在注冊時聲明是 Scoped 的服務,才會涉及到 Scope。所以,只有 Scoped 的服務才會涉及到 Scope。
在容器初始化之后,會創建一個根的 Scope 作用域,它是默認的。?
| 1 | IServiceProvider provider = services.BuildServiceProvider (); |
在需要作用域的時候,可以通過已經創建的 provider 來創建作用域,然后從這個新創建的作用域再獲取當前的服務提供器。
| 1234 | IServiceProvider provider = services.BuildServiceProvider ();IServiceScope scope = provider.CreateScope();IServiceProvider scopedServiceProvider = scope.ServiceProvider; |
通過作用域的服務提供器獲取服務對象實例的時候,就會影響到通過 Scoped 注冊的服務了。
這種類型的服務在每一個 Scope 中只會創建一個。
在微軟的實現中,不支持嵌套的 Scope。
3 注入服務
?現在又回到了涉及編程中的使用,在需要服務對象實例的時候,我們只需要注入。
微軟的實現實際上只支持了構造函數注入。例如:
| 12345678 | public?class?HomeController : Controller{????private?readonly?IDateTime _dateTime;????public?HomeController(IDateTime dateTime)????{????????_dateTime = dateTime;????} |
4 常見問題
?多次注冊問題
你可以對同一個服務類型多次注冊,這并不會遇到異常。在獲取服務對象實例的時候,是通過最后一次注冊來支持的。
需要的話,也可以獲取到所有注冊的服務對象實例。
| 1 | var?instances = provider.GetServices(typeof(IUserService)); |
不是直接注入服務,而是注入容器
容器本身也可以注入,它的類型是 IServiceProvider,這樣,你可以自己來通過容器完成特殊的處理。
| 123 | var?sp = provider.GetService<IServiceProvider> ();var?userService = sp.GetService<IUserService> ();Console.WriteLine (userService); |
5 簡化注冊過程
?https://www.cnblogs.com/catcher1994/p/handle-multi-implementations-with-same-interface-in-dotnet-core.html
?
參考資料:
ServiceCollectionServiceExtensions at GitHub
Does .net core dependency injection support Lazy<T>
總結
以上是生活随笔為你收集整理的依赖注入在 dotnet core 中实现与使用:2 使用 Extensions DependencyInjection的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 用.NET解索尼相机ARW格式照片
- 下一篇: 拿 C# 搞函数式编程 - 2