.NET CORE 依賴注入 實踐總結

知識點回顧

  • 依賴包。 Microsoft.Extensions.DependencyInjection.Abstractions
  • 核心對象和方法。
    • IServiceCollection。注入對象的容器。注意它只存儲對象的元數據,並不保存實例對象。
    • IServiceProvider。注入對象的提供者。它負責提供具體的對象實例。在架構中,IServiceProvider有2種身份,一種是Root ServiceProvider,由service.BuildServiceProvider()建立,生命週期貫穿整個應用程序,AddSingleton對象保存在這裏。另一種則是普通IServiceProvider,由IServiceScope建立,生命週期即爲AddScoped的生命週期。AddScope 的對象保存在這裏。普通ServiceProvider由Root ServiceProvider建立的IServiceScope建立。
    • IServiceScope。表某一個生命週期範圍。由ServiceProvider.CreateScope()建立。
    • 注入方式,知識點一
      • 注入功能默認在Startup類中的ConfigureServices方法中。
    • 注入方式,知識點二。支持如下三種方式
      • AddScoped。生命週期爲Scoped類型。例如在web框架中,即表示一次Request請求範圍內。
      • AddSingleton。單例,應用程序全局使用同一個實例。
      • AddTransient。即時的,即對象每次使用都會從新實例化。  
    • 注入方式,知識點三。提供多種注入技巧,以Transient爲例
      • 實例注入。AddTransient<TService>(this IServiceCollection services)。
      • 泛型注入。AddTransient<TService, TImplementation>(this IServiceCollection services)。
      • 工廠注入。AddTransient<TService>(this IServiceCollection services, Func<IServiceProvider, TService> implementationFactory)。
      • TryAddXXX。僅當XXX還沒有註冊實現時,註冊該服務。此方法用來避免在容器中註冊一個實例的兩個副本。
    • 獲取實例的方法 GetServiceGetRequiredService的區別,前置若是service不存在會返回NULL,後者會直接拋出異常。根據須要選擇GetRequiredService,可能會讓你的代碼變得簡潔一點。

WHY 依賴注入

這裏只談益處。web

  • 使用接口或基類抽象化依賴關係實現,明確各個類之間的依賴關係。
  • 生命週期的統一管理,尤爲對於某些類被多處依賴,關係會變得分散難以管理,依賴注入容器能夠解決這點。
  • 很是利於單元測試。

最佳實踐

部分來自官方文檔的一些建議安全

  • 對於須要注入爲單例的實例,不要依賴Scoped實例。會觸發 .NET CORE做用域驗證失敗。
  • 不要從Root IServiceProvider解析有做用域的實例,這樣會致使該做用域的實例變成單一實例。一樣會觸發做用域驗證失敗。
  • 對於Asp.Net Core,儘可能經過構造函數而不是HttpContext.RequestServices獲取實例,這樣更易於單元測試。
  • 須要對某個組件服務或是一些服務集合(包括其依賴注入時),使用約定的 Add{SERVICE_NAME} 擴展方法來註冊該服務所需的全部服務。
  • 若必需要從IServiceProvider中解析實例(如在單元測試中),請經過using (var scope = ServiceProvider.CreateScope()){ }方式建立做用域來獲取實例。
  • 代碼中避免設計有狀態的、靜態類和成員。能夠考慮設計注入爲單一實例。
  • 代碼中避免在服務中直接實例化以來類。儘可能採用依賴注入的方式
  • 注意如下兩種方式注入的區別,後者的實例化不是服務容器建立的,全部容器不會處理實例的Dispose !!
    public class Service1 : IDisposable {}
    public class Service2 : IDisposable {}
    
    //方式一
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<Service1>();
        services.AddSingleton<IService2>(sp => new Service2());
    }
    
    //方式二
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<Service1>(new Service1());
        services.AddSingleton(new Service2());
    }
  • 延伸上一點,對於複雜對象的建立,儘可能採用提供的工廠注入方式。注意工廠注入的參數是IServiceProvider,能夠經過它獲取你須要的實例對象。
  • 繼續延伸上一點,不要在ConfigureServices方法中 經過collection.BuildServiceProvider()來獲取IServiceProvider。這個建立的是一個Root IServiceProvider。單例會實例化一次,而後ConfigureServices方法結束後框架會再次調用collection.BuildServiceProvider(),單例又會從新實例化一次。
  • 不支持基於async/await和Task的服務解析。C# 不支持異步構造函數;所以建議模式是在同步解析服務後使用異步方法
  • 避免在容器中直接存儲數據和配置。配置應使用NET CORE的選項模型。
  • 避免使用服務定位器模式。例如直接注入IServiceProvider來獲取多個須要的服務。PS,若是你的服務依賴項過多,應該考慮分割成幾個小功能服務了。

引入第三方IOC框架

.NET CORE 3.x版本後,引入第三方IOC框架的方式變動了,這裏再也不貼出2.x的方式。以Autofac框架爲例。架構

Program.cs框架

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .UseServiceProviderFactory(new AutofacServiceProviderFactory())
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>()
                            .ConfigureLogging((hostingContext, logging) =>
                            {
                                logging.ClearProviders();
                                logging.AddConsole();
                                logging.AddNLog();
                            });
                });

Startup.cs異步

     
     //原來的
ConfigureServices保留,也可使用原來的框架繼續注入
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddMemoryCache();
            services.Configure<List<string>>(Configuration.GetSection("BlackPhoneList"));
            services.Configure<Dictionary<string, string>>(Configuration.GetSection("BusinessMessages"));
        }
        //增長ConfigureContainer(ContainerBuilder builder) 方式,使用Autofac框架注入
        public void ConfigureContainer(ContainerBuilder builder)
        {
            builder.RegisterType<PhoneBlackListValidator>().Named<IPhoneValidator>("PHONE_BLACKLIST").SingleInstance();
            builder.RegisterType<PhonePerDayCountValidator>().Named<IPhoneValidator>("PHONE_PERDAYCOUNT").SingleInstance();
            builder.RegisterType<UniqueIdPerDayCountValidator>().Named<IUniqueIdValidator>("UNIQUEID_PERDAYCOUNT").SingleInstance();

            //可遍歷類型注入,注意 只支持IEnumerable\IList\ICollection 類型
            builder.RegisterType<MessageSendValidator>().As<IMessageSendValidator>().SingleInstance();
        }

3.x 主要是在IServiceCollection和IServiceProvider之間增長了一個 ContainerBuilder 容器適配,使得第三方IOC框架引入更加合理了。具體實現原理能夠網上源碼查找。async

特別關注-線程安全

  • 建立線程安全的單一實例服務。 若是單例服務依賴於一個Transient服務,那麼Transient服務可能也須要線程安全,具體取決於單例使用它的方式。
  • 工廠注入方式的Func<IServiceProvider,TService>不須要是線程安全的,框架保證它由單個線程調用一次。
相關文章
相關標籤/搜索