既然是依賴注入容器,必然會涉及到服務的註冊,獲取服務實例,管理做用域,服務注入這四個方面。html
看起來很複雜,使用的時候其實很簡單。git
services.AddSingleton<IUserService, UserService>(); services.AddScoped<IUserService, UserService>(); services.AddTransient<IUserService, UserService>();
Singleton 就是單例,之後經過該容器獲取出來的,都是同一個服務對象實例。github
Scoped 就是限定了做用域,在每一個特定的做用域中,只會有一個服務對象實例。做用域須要你本身來建立,後面在使用服務的時候,咱們再介紹。編程
而 Transient 則在每次從容器中獲取的時候,都對建立新的服務對象實例。 框架
三種做用域簡單明瞭,後面咱們介紹服務註冊的時候,就再也不關注服務的做用域,都使用單例來介紹,其它兩種方式是相同的。ide
這是最爲常見的註冊方式,在實際開發中,服務通常都有對應的接口。爲了方便註冊,在 .NET Core 中通常使用泛型方式進行註冊,這樣比較簡潔。是最推薦的方式。函數
services.AddSingleton<IUserService, UserService>();
固然,也可使用基於類型的方式註冊,不過代碼沒有使用泛型方式優雅。你須要先獲取服務的類型,再經過類型進行註冊。ui
services.AddSingleton(typeof(IUserService), typeof(UserService));
若是服務並無對應的接口,能夠直接使用對象實例註冊,ServiceCollection 會直接使用該實例類型做爲服務接口類型來註冊,這種方式比較簡單粗暴。spa
services.AddSingleton(typeof(UserService));
泛型是很強大的特性,例如,泛型的倉儲,咱們只須要一個泛型倉儲,就能夠經過它訪問針對不一樣類型的數據。.net
容器必需要支持泛型的服務,例如,針對下面的簡化倉儲示例,注意這裏的 Get() 簡化爲只返回默認值。
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); } }
只須要註冊一次,就能夠得到不一樣實現的支持。可是,泛型比較特殊,不能這樣寫:
services.AddSingleton<IRepository<>, Repository<>>();
你須要經過類型的方式來進行服務註冊。
services.AddSingleton(typeof(IRepository<>), typeof(Repository<>));
若是沒有這個倉儲接口的話,就能夠這樣註冊。
services.AddSingleton(typeof(Repository<>));
若是涉及到多個類型的泛型,例如倉儲的定義變成下面這樣,id 的類型使用 K 來指定。
public interface IRepository<T, K> { T Get(K id); }
而倉儲的實現也變成下面這樣:
public class Repository<T, K>: IRepository<T, K> { public Repository() { Console.WriteLine(typeof(T).Name); } public T Get(K id){ return default(T); } }
註冊的時候,就須要寫成下面的樣子:
services.AddSingleton (typeof (IRepository<,>), typeof (Repository<,>));
除了讓容器幫助構造服務對象實例,咱們也能夠本身定義構建服務對象實例的工廠供容器使用。
若是服務實現沒有參數,例如 UserRepository,能夠這樣寫:
services.AddSingleton<IUserRepository>( (serviceProvider) => { return new UserReposotory(); } );
若是構造函數是有參數的,好比:
public class UserReposotory: IUserRepository { public UserReposotory(string name){ Console.WriteLine( name); } }
註冊就變成了下面的樣子,須要注意的是,工廠會獲得一個 ServiceProvider 的實例供你使用。
services.AddSingleton<IUserRepository> ((serviceProvider) => { Console.WriteLine ("construction IUserReposority"); return new UserReposotory ("Tom"); });
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; } }
因此,實際上,你能夠經過本身建立這個服務描述對象來進行註冊。
services.Add(ServiceDescriptor.Singleton<IUserService, UserService>());
異曲同工,其實與使用上面的方式效果是同樣的。
若是註冊服務的時候,指定了服務是單例的,不管從哪一個 Scope 中得到的服務實例,都會是同一個。因此,單例的服務與 Scope 就沒有什麼關係了。
若是註冊服務的時候,指定了服務是瞬態的,不管從哪一個 Scope 中獲取服務實例,都會是新建立的,每次獲取就新建立一個。因此,瞬態的服務與 Scope 也沒有什麼關係了。
只有在註冊時聲明是 Scoped 的服務,纔會涉及到 Scope。因此,只有 Scoped 的服務纔會涉及到 Scope。
在容器初始化以後,會建立一個根的 Scope 做用域,它是默認的。
IServiceProvider provider = services.BuildServiceProvider ();
在須要做用域的時候,能夠經過已經建立的 provider 來建立做用域,而後從這個新建立的做用域再獲取當前的服務提供器。
IServiceProvider provider = services.BuildServiceProvider (); IServiceScope scope = provider.CreateScope(); IServiceProvider scopedServiceProvider = scope.ServiceProvider;
經過做用域的服務提供器獲取服務對象實例的時候,就會影響到經過 Scoped 註冊的服務了。
這種類型的服務在每個 Scope 中只會建立一個。
在微軟的實現中,不支持嵌套的 Scope。
如今又回到了涉及編程中的使用,在須要服務對象實例的時候,咱們只須要注入。
微軟的實現實際上只支持了構造函數注入。例如:
public class HomeController : Controller { private readonly IDateTime _dateTime; public HomeController(IDateTime dateTime) { _dateTime = dateTime; }
你能夠對同一個服務類型屢次註冊,這並不會遇到異常。在獲取服務對象實例的時候,是經過最後一次註冊來支持的。
須要的話,也能夠獲取到全部註冊的服務對象實例。
var instances = provider.GetServices(typeof(IUserService));
容器自己也能夠注入,它的類型是 IServiceProvider,這樣,你能夠本身來經過容器完成特殊的處理。
var sp = provider.GetService<IServiceProvider> (); var userService = sp.GetService<IUserService> (); Console.WriteLine (userService);
https://www.cnblogs.com/catcher1994/p/handle-multi-implementations-with-same-interface-in-dotnet-core.html
參考資料: