依賴注入在 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 中一般使用泛型方式進行注冊,這樣比較簡潔。是最推薦的方式。

services.AddSingleton<IUserService, UserService>();

當然,也可以使用基於類型的方式注冊,不過代碼沒有使用泛型方式優雅。你需要先獲取服務的類型,再通過類型進行注冊。

services.AddSingleton(typeof(IUserService), typeof(UserService));

1.3 直接注冊實例

如果服務並沒有對應的接口,可以直接使用對象實例注冊,ServiceCollection 會直接使用該實例類型作為服務接口類型來注冊,這種方式比較簡單粗暴。

services.AddSingleton(typeof(UserService));

 

1.4 注冊泛型服務

泛型是很強大的特性,例如,泛型的倉儲,我們只需要一個泛型倉儲,就可以通過它訪問針對不同類型的數據。

容器必須要支持泛型的服務,例如,針對下面的簡化倉儲示例,注意這里的 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<,>));
 

1.5 工廠模式注冊

除了讓容器幫助構造服務對象實例,我們也可以自己定義構建服務對象實例的工廠供容器使用。

如果服務實現沒有參數,例如 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");
        });

1.6 直接使用 ServiceDescriptor 注冊

實際上,上面各種注冊方式最終都會通過創建一個服務描述對象來完成注冊,它的定義如下,你可以看到上面各種注冊方式所涉及的痕跡。

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>());

殊途同歸,其實與使用上面的方式效果是一樣的。

2 管理 Scope

如果注冊服務的時候,指定了服務是單例的,無論從哪個 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。

3 注入服務

 現在又回到了涉及編程中的使用,在需要服務對象實例的時候,我們只需要注入。

微軟的實現實際上只支持了構造函數注入。例如:

public class HomeController : Controller
{
    private readonly IDateTime _dateTime;

    public HomeController(IDateTime dateTime)
    {
        _dateTime = dateTime;
    }

 

4 常見問題

 多次注冊問題

你可以對同一個服務類型多次注冊,這並不會遇到異常。在獲取服務對象實例的時候,是通過最后一次注冊來支持的。

需要的話,也可以獲取到所有注冊的服務對象實例。

var instances = provider.GetServices(typeof(IUserService));  

不是直接注入服務,而是注入容器

容器本身也可以注入,它的類型是 IServiceProvider,這樣,你可以自己來通過容器完成特殊的處理。

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

 

參考資料:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM