溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

關于ASP.NET Core依賴注入的詳細介紹

發布時間:2021-09-08 07:42:24 來源:億速云 閱讀:191 作者:chen 欄目:開發技術

這篇文章主要講解了“關于ASP.NET Core依賴注入的詳細介紹”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“關于ASP.NET Core依賴注入的詳細介紹”吧!

目錄
  • 依賴注入

    • 什么是依賴注入

    • 依賴注入有什么好處

  • ASP.NET Core內置的依賴注入

    • 服務生存周期

    • 服務釋放

    • TryAdd{Lifetime}擴展方法

    • 解析同一服務的多個不同實現

    • Replace && Remove 擴展方法

  • Autofac

    • 服務解析和注入

      • 構造函數注入

      • 方法注入

      • 屬性注入

    • 一些注意事項

      • 框架默認提供的服務

        依賴注入

        什么是依賴注入

        簡單說,就是將對象的創建和銷毀工作交給DI容器來進行,調用方只需要接收注入的對象實例即可。

        • 微軟官方文檔-DI

        依賴注入有什么好處

        依賴注入在.NET中,可謂是“一等公民”,處處都離不開它,那么它有什么好處呢?

        假設有一個日志類 FileLogger,用于將日志記錄到本地文件。

        public class FileLogger
        {
            public void LogInfo(string message)
            {
        
            }
        }

        日志很常用,幾乎所有服務都需要記錄日志。如果不使用依賴注入,那么我們就必須在每個服務中手動 new FileLogger 來創建一個 FileLogger 實例。

        public class MyService
        {
            private readonly FileLogger _logger = new FileLogger();
        
            public void Get()
            {
                _logger.LogInfo("MyService.Get");
            }
        }

        如果某一天,想要替換掉 FileLogger,而是使用 ElkLogger,通過ELK來處理日志,那么我們就需要將所有服務中的代碼都要改成 new ElkLogger。

        public class MyService
        {
            private readonly ElkLogger _logger = new ElkLogger();
        
            public void Get()
            {
                _logger.LogInfo("MyService.Get");
            }
        }
        • 在一個大型項目中,這樣的代碼分散在項目各處,涉及到的服務均需要進行修改,顯然一個一個去修改不現實,且違反了“開閉原則”。

        • 如果Logger中還需要其他一些依賴項,那么用到Logger的服務也要為其提供依賴,如果依賴項修改了,其他服務也必須要進行更改,更加增大了維護難度。

        • 很難進行單元測試,因為它無法進行 mock

        正因如此,所以依賴注入解決了這些棘手的問題:

        • 通過接口或基類(包含抽象方法或虛方法等)將依賴關系進行抽象化

        • 將依賴關系存放到服務容器中

        • 由框架負責創建和釋放依賴關系的實例,并將實例注入到構造函數、屬性或方法中

        ASP.NET Core內置的依賴注入

        服務生存周期

        Transient
        瞬時,即每次獲取,都是一個全新的服務實例

        Scoped
        范圍(或稱為作用域),即在某個范圍(或作用域內)內,獲取的始終是同一個服務實例,而不同范圍(或作用域)間獲取的是不同的服務實例。對于Web應用,每個請求為一個范圍(或作用域)。

        Singleton
        單例,即在單個應用中,獲取的始終是同一個服務實例。另外,為了保證程序正常運行,要求單例服務必須是線程安全的。

        服務釋放

        若服務實現了IDisposable接口,并且該服務是由DI容器創建的,那么你不應該去Dispose,DI容器會對服務自動進行釋放。

        如,有Service1、Service2、Service3、Service4四個服務,并且都實現了IDisposable接口,如:

        public class Service1 : IDisposable
        {
            public void Dispose()
            {
                Console.WriteLine("Service1.Dispose");
            }
        }
        
        public class Service2 : IDisposable
        {
            public void Dispose()
            {
                Console.WriteLine("Service2.Dispose");
            }
        }
        
        public class Service3 : IDisposable
        {
            public void Dispose()
            {
                Console.WriteLine("Service3.Dispose");
            }
        }
        
        public class Service4 : IDisposable
        {
            public void Dispose()
            {
                Console.WriteLine("Service4.Dispose");
            }
        }

        并注冊為:

        public void ConfigureServices(IServiceCollection services)
        {
            // 每次使用完(請求結束時)即釋放
            services.AddTransient<Service1>();
            // 超出范圍(請求結束時)則釋放
            services.AddScoped<Service2>();
            // 程序停止時釋放
            services.AddSingleton<Service3>();
            // 程序停止時釋放
            services.AddSingleton(sp => new Service4());
        }

        構造函數注入一下

        public ValuesController(
            Service1 service1, 
            Service2 service2, 
            Service3 service3, 
            Service4 service4)
        { }

        請求一下,獲取輸出:

        Service2.Dispose
        Service1.Dispose

        這些服務實例都是由DI容器創建的,所以DI容器也會負責服務實例的釋放和銷毀。注意,單例此時還沒到釋放的時候。

        但如果注冊為:

        public void ConfigureServices(IServiceCollection services)
        {
            // 注意與上面的區別,這個是直接 new 的,而上面是通過 sp => new 的
            services.AddSingleton(new Service1());
            services.AddSingleton(new Service2());
            services.AddSingleton(new Service3());
            services.AddSingleton(new Service4());
        }

        此時,實例都是咱們自己創建的,DI容器就不會負責去釋放和銷毀了,這些工作都需要我們開發人員自己去做。

        更多注冊方式,請參考官方文檔-Service registration methods

        TryAdd{Lifetime}擴展方法

        當你將同樣的服務注冊了多次時,如:

        services.AddSingleton<IMyService, MyService>();
        services.AddSingleton<IMyService, MyService>();

        那么當使用IEnumerable<{Service}>(下面會講到)解析服務時,就會產生多個MyService實例的副本。

        為此,框架提供了TryAdd{Lifetime}擴展方法,位于命名空間Microsoft.Extensions.DependencyInjection.Extensions下。當DI容器中已存在指定類型的服務時,則不進行任何操作;反之,則將該服務注入到DI容器中。

        services.AddTransient<IMyService, MyService1>();
        // 由于上面已經注冊了服務類型 IMyService,所以下面的代碼不不會執行任何操作(與生命周期無關)
        services.TryAddTransient<IMyService, MyService1>();
        services.TryAddTransient<IMyService, MyService2>();
        • TryAdd:通過參數ServiceDescriptor將服務類型、實現類型、生命周期等信息傳入進去

        • TryAddTransient:對應AddTransient

        • TryAddScoped:對應AddScoped

        • TryAddSingleton:對應AddSingleton

        • TryAddEnumerable:這個和TryAdd的區別是,TryAdd僅根據服務類型來判斷是否要進行注冊,而TryAddEnumerable則是根據服務類型和實現類型一同進行判斷是否要進行注冊,常常用于注冊同一服務類型的多個不同實現。舉個例子吧:

        // 注冊了 IMyService - MyService1
        services.TryAddEnumerable(ServiceDescriptor.Singleton<IMyService, MyService1>());
        // 注冊了 IMyService - MyService2
        services.TryAddEnumerable(ServiceDescriptor.Singleton<IMyService, MyService2>());
        // 未進行任何操作,因為 IMyService - MyService1 在上面已經注冊了
        services.TryAddEnumerable(ServiceDescriptor.Singleton<IMyService, MyService1>());

        解析同一服務的多個不同實現

        默認情況下,如果注入了同一個服務的多個不同實現,那么當進行服務解析時,會以最后一個注入的為準。

        如果想要解析出同一服務類型的所有服務實例,那么可以通過IEnumerable<{Service}>來解析(順序同注冊順序一致):

        public interface IAnimalService { }
        public class DogService : IAnimalService { }
        public class PigService : IAnimalService { }
        public class CatService : IAnimalService { }
        
        public void ConfigureServices(IServiceCollection services)
        {
            // 生命周期沒有限制
            services.AddTransient<IAnimalService, DogService>();
            services.AddScoped<IAnimalService, PigService>();
            services.AddSingleton<IAnimalService, CatService>();
        }
        
        public ValuesController(
            // CatService
            IAnimalService animalService,   
            // DogService、PigService、CatService
            IEnumerable<IAnimalService> animalServices)
        {
        }

        Replace && Remove 擴展方法

        上面我們所提到的,都是注冊新的服務到DI容器中,但是有時我們想要替換或是移除某些服務,這時就需要使用ReplaceRemove

        // 將 IMyService 的實現替換為 MyService1
        services.Replace(ServiceDescriptor.Singleton<IMyService, MyService>());
        // 移除 IMyService 注冊的實現 MyService
        services.Remove(ServiceDescriptor.Singleton<IMyService, MyService>());
        // 移除 IMyService 的所有注冊
        services.RemoveAll<IMyService>();
        // 清除所有服務注冊
        services.Clear();

        Autofac

        Autofac 是一個老牌DI組件了,接下來我們使用Autofac替換ASP.NET Core自帶的DI容器。

        1.安裝nuget包:

        Install-Package Autofac
        Install-Package Autofac.Extensions.DependencyInjection

        2.替換服務提供器工廠

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                })
                // 通過此處將默認服務提供器工廠替換為 autofac
                .UseServiceProviderFactory(new AutofacServiceProviderFactory());

        3.在 Startup 類中添加 ConfigureContainer 方法

        public class Startup
        {
            public Startup(IConfiguration configuration)
            {
                Configuration = configuration;
            }
        
            public IConfiguration Configuration { get; }
        
            public ILifetimeScope AutofacContainer { get; private set; }
        
            public void ConfigureServices(IServiceCollection services)
            {
                // 1. 不要 build 或返回任何 IServiceProvider,否則會導致 ConfigureContainer 方法不被調用。
                // 2. 不要創建 ContainerBuilder,也不要調用 builder.Populate(),AutofacServiceProviderFactory 已經做了這些工作了
                // 3. 你仍然可以在此處通過微軟默認的方式進行服務注冊
                
                services.AddOptions();
                services.AddControllers();
                services.AddSwaggerGen(c =>
                {
                    c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApplication.Ex", Version = "v1" });
                });
            }
        
            // 1. ConfigureContainer 用于使用 Autofac 進行服務注冊
            // 2. 該方法在 ConfigureServices 之后運行,所以這里的注冊會覆蓋之前的注冊
            // 3. 不要 build 容器,不要調用 builder.Populate(),AutofacServiceProviderFactory 已經做了這些工作了
            public void ConfigureContainer(ContainerBuilder builder)
            {
                // 將服務注冊劃分為模塊,進行注冊
                builder.RegisterModule(new AutofacModule());
            }
            
            public class AutofacModule : Autofac.Module
            {
                protected override void Load(ContainerBuilder builder)
                {
                    // 在此處進行服務注冊
                    builder.RegisterType<UserService>().As<IUserService>();
                }
            }
        
            public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
            {
                // 通過此方法獲取 autofac 的 DI容器
                AutofacContainer = app.ApplicationServices.GetAutofacRoot();
            }
        }

        服務解析和注入

        上面我們主要講了服務的注入方式,接下來看看服務的解析方式。解析方式有兩種:

        1.IServiceProvider

        2.ActivatorUtilities

        • 用于創建未在DI容器中注冊的服務實例

        • 用于某些框架級別的功能

        構造函數注入

        上面我們舉得很多例子都是使用了構造函數注入——通過構造函數接收參數。構造函數注入是非常常見的服務注入方式,也是首選方式,這要求:

        • 構造函數可以接收非依賴注入的參數,但必須提供默認值

        • 當服務通過IServiceProvider解析時,要求構造函數必須是public

        • 當服務通過ActivatorUtilities解析時,要求構造函數必須是public,雖然支持構造函數重載,但必須只能有一個是有效的,即參數能夠全部通過依賴注入得到值

        方法注入

        顧名思義,方法注入就是通過方法參數來接收服務實例。

        [HttpGet]
        public string Get([FromServices]IMyService myService)
        {
            return "Ok";
        }

        屬性注入

        ASP.NET Core內置的依賴注入是不支持屬性注入的。但是Autofac支持,用法如下:

        老規矩,先定義服務和實現

        public interface IUserService 
        {
            string Get();
        }
        
        public class UserService : IUserService
        {
            public string Get()
            {
                return "User";
            }
        }

        然后注冊服務

        • 默認情況下,控制器的構造函數參數由DI容器來管理嗎,而控制器實例本身卻是由ASP.NET Core框架來管理,所以這樣“屬性注入”是無法生效的

        • 通過AddControllersAsServices方法,將控制器交給 autofac 容器來處理,這樣就可以使“屬性注入”生效了

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers().AddControllersAsServices();
        }
        
        public void ConfigureContainer(ContainerBuilder builder)
        {
            builder.RegisterModule<AutofacModule>();
        }
        
        public class AutofacModule : Autofac.Module
        {
            protected override void Load(ContainerBuilder builder)
            {
                builder.RegisterType<UserService>().As<IUserService>();
        
                var controllerTypes = Assembly.GetExecutingAssembly().GetExportedTypes()
                    .Where(type => typeof(ControllerBase).IsAssignableFrom(type))
                    .ToArray();
        
                // 配置所有控制器均支持屬性注入
                builder.RegisterTypes(controllerTypes).PropertiesAutowired();
            }
        }

        最后,我們在控制器中通過屬性來接收服務實例

        public class ValuesController : ControllerBase
        {
            public IUserService UserService { get; set; }
        
            [HttpGet]
            public string Get()
            {
                return UserService.Get();
            }
        }

        通過調用Get接口,我們就可以得到IUserService的實例,從而得到響應

        User

        一些注意事項

        • 避免使用服務定位模式。盡量避免使用GetService來獲取服務實例,而應該使用DI。

        using Microsoft.Extensions.DependencyInjection;
        
        public class ValuesController : ControllerBase
        {
            private readonly IServiceProvider _serviceProvider;
        
            // 應通過依賴注入的方式獲取服務實例
            public ValuesController(IServiceProvider serviceProvider)
            {
                _serviceProvider = serviceProvider;
            }
        
            [HttpGet]
            public string Get()
            {
                // 盡量避免通過 GetService 方法獲取服務實例
                var myService = _serviceProvider.GetService<IMyService>();
        
                return "Ok";
            }
        }
        • 避免在ConfigureServices中調用BuildServiceProvider。因為這會導致創建第二個DI容器的副本,從而導致注冊的單例服務出現多個副本。

        public void ConfigureServices(IServiceCollection services)
        {
            // 不要在該方法中調用該方法
            var serviceProvider = services.BuildServiceProvider();
        }
        • 一定要注意服務解析范圍,不要在 Singleton 中解析 Transient 或 Scoped 服務,這可能導致服務狀態錯誤(如導致服務實例生命周期提升為單例)。允許的方式有:

        1.在 Scoped 或 Transient 服務中解析 Singleton 服務

        2.在 Scoped 或 Transient 服務中解析 Scoped 服務(不能和前面的Scoped服務相同)

        • 當在Development環境中運行、并通過 CreateDefaultBuilder生成主機時,默認的服務提供程序會進行如下檢查:

        1.不能在根服務提供程序解析 Scoped 服務,這會導致 Scoped 服務的生命周期提升為 Singleton,因為根容器在應用關閉時才會釋放。

        2.不能將 Scoped 服務注入到 Singleton 服務中

        • 隨著業務增長,需要依賴注入的服務也越來越多,建議使用擴展方法,封裝服務注入,命名為Add{Group_Name},如將所有 AppService 的服務注冊封裝起來

        namespace Microsoft.Extensions.DependencyInjection
        {
            public static class ApplicationServiceCollectionExtensions
            {
                public static IServiceCollection AddApplicationService(this IServiceCollection services)
                {
                    services.AddTransient<Service1>();
                    services.AddScoped<Service2>();
                    services.AddSingleton<Service3>();
                    services.AddSingleton(sp => new Service4());
        
                    return services;
                }
            }
        }

        然后在ConfigureServices中調用即可

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddApplicationService();
        }

        框架默認提供的服務

        以下列出一些常用的框架已經默認注冊的服務:

        服務類型生命周期
        Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactoryTransient
        IHostApplicationLifetimeSingleton
        IHostLifetimeSingleton
        IWebHostEnvironmentSingleton
        IHostEnvironmentSingleton
        Microsoft.AspNetCore.Hosting.IStartupSingleton
        Microsoft.AspNetCore.Hosting.IStartupFilterTransient
        Microsoft.AspNetCore.Hosting.Server.IServerSingleton
        Microsoft.AspNetCore.Http.IHttpContextFactoryTransient
        Microsoft.Extensions.Logging.ILoggerSingleton
        Microsoft.Extensions.Logging.ILoggerFactorySingleton
        Microsoft.Extensions.ObjectPool.ObjectPoolProviderSingleton
        Microsoft.Extensions.Options.IConfigureOptionsTransient
        Microsoft.Extensions.Options.IOptionsSingleton
        System.Diagnostics.DiagnosticSourceSingleton
        System.Diagnostics.DiagnosticListenerSingleton

        感謝各位的閱讀,以上就是“關于ASP.NET Core依賴注入的詳細介紹”的內容了,經過本文的學習后,相信大家對關于ASP.NET Core依賴注入的詳細介紹這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

        向AI問一下細節

        免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

        AI

        亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女