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