這篇文章主要講解asp.net core 依賴注入的一些內容。html
ASP.NET Core支持依賴注入。這是一種在類和其依賴之間實現控制反轉的一種技術(IOC).web
1.原始的代碼數據庫
依賴就是一個對象的建立須要另外一個對象。下面的MyDependency是應用中其餘類須要的依賴:設計模式
public class MyDependency { public MyDependency() { } public Task WriteMessage(string message) { Console.WriteLine( $"MyDependency.WriteMessage called. Message: {message}"); return Task.FromResult(0); } }
一個MyDependency類被建立使WriteMessage方法對另外一個類可用。MyDependency類是IndexModel類的依賴(即IndexModel類的建立須要用到MyDependency類):api
public class IndexModel : PageModel { MyDependency _dependency = new MyDependency(); public async Task OnGetAsync() { await _dependency.WriteMessage( "IndexModel.OnGetAsync created this message."); } }
2.原始代碼分析安全
IndexModel類建立了MyDependency類,而且直接依賴MyDependency實例。上面的代碼依賴是有問題的,而且應該被避免(避免直接建立依賴的實例對象),服務器
緣由以下:框架
依賴注入解決那些問題:asp.net
3.下面是改良後的代碼less
這示例應用中,IMyDependency接口定義了一個方法:
public interface IMyDependency { Task WriteMessage(string message); }
接口被一個具體的類型,MyDependency實現:
public class MyDependency : IMyDependency { private readonly ILogger<MyDependency> _logger; public MyDependency(ILogger<MyDependency> logger) { _logger = logger; } public Task WriteMessage(string message) { _logger.LogInformation( "MyDependency.WriteMessage called. Message: {MESSAGE}", message); return Task.FromResult(0); } }
在示例中,IMydependency實例被請求和用於調用服務的WriteMessage方法:
public class IndexModel : PageModel { private readonly IMyDependency _myDependency; public IndexModel( IMyDependency myDependency, OperationService operationService, IOperationTransient transientOperation, IOperationScoped scopedOperation, IOperationSingleton singletonOperation, IOperationSingletonInstance singletonInstanceOperation) { _myDependency = myDependency; OperationService = operationService; TransientOperation = transientOperation; ScopedOperation = scopedOperation; SingletonOperation = singletonOperation; SingletonInstanceOperation = singletonInstanceOperation; } public OperationService OperationService { get; } public IOperationTransient TransientOperation { get; } public IOperationScoped ScopedOperation { get; } public IOperationSingleton SingletonOperation { get; } public IOperationSingletonInstance SingletonInstanceOperation { get; } public async Task OnGetAsync() { await _myDependency.WriteMessage( "IndexModel.OnGetAsync created this message."); } }
4.改良代碼分析及擴展講解(使用DI)
MyDependency在構造函數中,要求有一個ILogger<TCategoryName>。用一種鏈式的方法使用依賴注入是很常見的。每一個依賴依次再請求它本身須要的依賴。(即:MyDependency是一個依賴,同時,建立MyDependency又須要其餘依賴:ILogger<TCategoryName>。)
IMyDependency和ILogger<TCategoryName>必須在service container中註冊。IMyDependency是在Startup.ConfigureServices中註冊。ILogger<TCategoryName>是被logging abstractions infrastructure註冊,因此它是一種默認已經註冊的框架提供的服務。(即框架自帶的已經註冊的服務,不須要再另外註冊)
容器解析ILogger<TCategoryName>,經過利用泛型. 消除註冊每一種具體的構造類型的須要。(由於在上面的例子中,ILogger中的泛型類型爲MyDependency,可是若是在其餘類中使用ILogger<>, 類型則是其餘類型,這裏使用泛型比較方便)
services.AddSingleton(typeof(ILogger<T>), typeof(Logger<T>));
這是它的註冊的語句(框架實現的),其中的用到泛型,而不是一種具體的類型。
在示例應用中,IMyDependency service是用具體的類型MyDependency來註冊的。這個註冊包括服務的生命週期(service lifetime)。Service lifetimes隨後會講。
若是服務的構造函數要求一個內置類型,像string,這個類型能夠被使用configuration 或者options pattern來注入:
public class MyDependency : IMyDependency
{
public MyDependency(IConfiguration config) { var myStringValue = config["MyStringKey"]; // Use myStringValue } ... }
或者 options pattern(注意:不止這些,這裏簡單舉例)
Startup.ConfigureServices方法有責任定義應用使用的服務,包括平臺功能,例如Entity Framework Core和ASP.NET Core MVC。最初,IServiceColletion提供給ConfigureServices下面已經定義的服務(依賴於怎樣配置host):
當一個service colletion 擴展方法能夠用來註冊一個服務,習慣是用一個單獨的Add{SERVICE_NAME} 擴展方法來註冊服務所須要的全部服務。下面的代碼是一個怎麼使用擴展方法AddDbContext, AddIdentity,和AddMvc, 添加額外的服務到container:
public void ConfigureServices(IServiceCollection services) { services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); services.AddIdentity<ApplicationUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); services.AddMvc(); }
更多的信息:ServiceCollection Class
爲每一個註冊的服務選擇一個合適的生命週期。ASP.NET Core服務能夠用下面的聲明週期配置:
Transient、Scoped、Singleton
Transient(臨時的)
臨時的生命週期服務是在每次從服務容器中被請求時被建立。這個生命週期對於lightweight(輕量的),stateless(無狀態的)服務比較合適。
Scoped(範圍)
範圍生命週期被建立,一旦每一個客戶端請求時(connection)
警告:當在中間件中使用範圍服務時,注入服務到Invoke或者InvokeAsync方法。不要經過構造函數注入,由於那回強制服務表現的像是singleton(單例)。
Singleton(單獨)
單獨生命週期在第一次請求時被建立(或者說當ConfigureService運行而且被service registration指定時)。以後每個請求都使用同一個實例。若是應用要求一個單獨行爲(singleton behavior),容許service container來管理服務生命週期是被推薦的。不要實現一個單例設計模式而且在類中提供用戶代碼來管理這個對象的生命週期。
警告:從一個singleton來解析一個範圍服務(scoped service)是危險的。它可能會形成服務有不正確的狀態,當處理隨後的請求時。
服務能夠被經過兩種機制解析:
構造函數能夠接受參數,不經過依賴注入提供,可是這些參數必須指定默認值。
當服務被經過IServiceProvider或者ActivatorUtilities解析時,構造函數注入要求一個公共的構造函數。
當服務被ActivatorUtilities解析時,構造函數注入要求一個合適的構造函數存在。構造函數的重載是被支持的,可是隻有一個重載能夠存在,它的參數能夠被依賴注入執行(即:能夠被依賴注入執行的,只有一個構造函數的重載)。
Entity Framework contexts 一般使用scoped lifetime ,添加到服務容器中(service container).由於web 應用數據庫操做的範圍適用於client request(客戶端請求)。默認的生命週期是scoped,若是一個生命週期沒有被AddDbContext<TContext>重載指定,當註冊database context時。給出生命週期的服務不該該使用一個生命週期比服務的生命週期短的database context.
爲了說明lifetime和registration options之間的不一樣,考慮下面的接口:這些接口表示的任務都是帶有惟一標識的操做。取決於這些接口的操做服務的生命週期怎麼配置,container提供了要麼是同一個要麼是不一樣的服務當被一個類請求時:
public interface IOperation { Guid OperationId { get; } } public interface IOperationTransient : IOperation { } public interface IOperationScoped : IOperation { } public interface IOperationSingleton : IOperation { } public interface IOperationSingletonInstance : IOperation { }
這些接口在一個Operation類中被實現。Operation 構造函數生成了一個GUID,若是GUID沒被提供:
public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton, IOperationSingletonInstance { public Operation() : this(Guid.NewGuid()) { } public Operation(Guid id) { OperationId = id; } public Guid OperationId { get; private set; } }
OperationService依賴於其餘的Operation 類型被註冊。當OperationService被經過依賴注入請求,它要麼接收每一個服務的一個新實例要麼接收一個已經存在的實例(在依賴服務的生命週期的基礎上)。
public class OperationService { public OperationService( IOperationTransient transientOperation, IOperationScoped scopedOperation, IOperationSingleton singletonOperation, IOperationSingletonInstance instanceOperation) { TransientOperation = transientOperation; ScopedOperation = scopedOperation; SingletonOperation = singletonOperation; SingletonInstanceOperation = instanceOperation; } public IOperationTransient TransientOperation { get; } public IOperationScoped ScopedOperation { get; } public IOperationSingleton SingletonOperation { get; } public IOperationSingletonInstance SingletonInstanceOperation { get; } }
在Startup.ConfigureServices中,每一個類型根據命名的生命週期被添加到容器中:
public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddScoped<IMyDependency, MyDependency>(); services.AddTransient<IOperationTransient, Operation>(); services.AddScoped<IOperationScoped, Operation>(); services.AddSingleton<IOperationSingleton, Operation>(); services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty)); // OperationService depends on each of the other Operation types. services.AddTransient<OperationService, OperationService>(); }
IOperationSingletonInstance服務是一個特殊的實例,它的ID是Guid.Empty. 它是清楚的,當這個類型被使用(它的GUID都是0組成的)
示例應用說明了requests內的對象生命週期和兩個requests之間的對象生命週期。示例應用的IndexModel請求IOperation的每一個類型和OperationService。這個頁面展現了全部的這個page model類的和服務的OperationId值,經過屬性指定。
public class IndexModel : PageModel { private readonly IMyDependency _myDependency; public IndexModel( IMyDependency myDependency, OperationService operationService, IOperationTransient transientOperation, IOperationScoped scopedOperation, IOperationSingleton singletonOperation, IOperationSingletonInstance singletonInstanceOperation) { _myDependency = myDependency; OperationService = operationService; TransientOperation = transientOperation; ScopedOperation = scopedOperation; SingletonOperation = singletonOperation; SingletonInstanceOperation = singletonInstanceOperation; } public OperationService OperationService { get; } public IOperationTransient TransientOperation { get; } public IOperationScoped ScopedOperation { get; } public IOperationSingleton SingletonOperation { get; } public IOperationSingletonInstance SingletonInstanceOperation { get; } public async Task OnGetAsync() { await _myDependency.WriteMessage( "IndexModel.OnGetAsync created this message."); } }
下面的輸出展現了兩個請求的結果:
從結果看出:
能夠看出,Transient一直在變;Scoped 同一個client request請求內不變;Singleton一直不變;
用IServiceScopeFactory.CreateScope建立一個IServiceScope 來解析一個scoped service在應用的範圍內。這個方式是有用的對於在Startup中獲得一個scoped service 來運行初始化任務。下面的例子展現了MyScopedServcie怎樣包含一個context,在Program.Main中:
public static void Main(string[] args) { var host = CreateWebHostBuilder(args).Build(); using (var serviceScope = host.Services.CreateScope()) { var services = serviceScope.ServiceProvider; try { var serviceContext = services.GetRequiredService<MyScopedService>(); // Use the context here } catch (Exception ex) { var logger = services.GetRequiredService<ILogger<Program>>(); logger.LogError(ex, "An error occurred."); } } host.Run(); }
當應用在開發環境運行時,默認的service provider 執行檢查來驗證:
root service provider 是當BuildServiceProvider被調用時被建立的。Root service provider的生命週期對應於應用/服務器 的生命週期,當provider隨着應用啓動而且當應用關閉時會被釋放。
Scoped服務被建立它們的容器釋放。若是scoped service在root container中被建立,服務的生命週期其實是被提高爲singleton,由於它只有當應用或者服務器關閉時纔會被root container釋放。驗證servcie scopes 注意這些場景,當BuildServiceProvider被調用時。
來自HttpContext的ASP.NET Core request中的可用的services經過HttpContext.RequestServices集合來暴露。
Request Services表明應用中被配置的services和被請求的部分。當對象指定依賴,會被RequestService中的類型知足,而不是ApplicationServices中的。
一般,應用不該該直接使用那些屬性。相反的,請求知足那個類型的的這些類,能夠經過構造函數而且容許框架注入這些依賴。這使類更容易測試。
注意:請求依賴,經過構造函數參數來獲得RequestServices集合更受歡迎。
最佳實踐:
若是一個相似乎有不少注入的依賴,這一般是它有太多職責的信號,而且違反了Single Responsibility Principle(SRP)單一職責原則。嘗試經過移動一些職責到一個新類來重構這個類。記住,Razor Pages page model classes和MVC controller classes應該專一於UI層面。Business rules和data access implementation細節應該在那些合適的分開的關係的類中。
容器爲它建立的類調用IDisposable的Dispose。若是一個實例被用戶代碼添加到容器中,它不會自動釋放。
// Services that implement IDisposable: public class Service1 : IDisposable {} public class Service2 : IDisposable {} public class Service3 : IDisposable {} public interface ISomeService {} public class SomeServiceImplementation : ISomeService, IDisposable {} public void ConfigureServices(IServiceCollection services) { // The container creates the following instances and disposes them automatically: services.AddScoped<Service1>(); services.AddSingleton<Service2>(); services.AddSingleton<ISomeService>(sp => new SomeServiceImplementation()); // The container doesn't create the following instances, so it doesn't dispose of // the instances automatically: services.AddSingleton<Service3>(new Service3()); services.AddSingleton(new Service3()); }
即,若是,類是被用戶代碼添加容器中的,不會自動釋放。像下面這種直接new類的。
內置的service container意味着提供服務來知足框架和大多消費應用的需求。咱們建議使用功能內置容器,除非你須要一個特殊的功能,內置容器不支持。有些功能在第三方容器支持,可是內置容器不支持:
下面的示例,使用Autofac替代內置容器:
public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddMvc(); // Add other framework services // Add Autofac var containerBuilder = new ContainerBuilder(); containerBuilder.RegisterModule<DefaultModule>(); containerBuilder.Populate(services); var container = containerBuilder.Build(); return new AutofacServiceProvider(container); }
要使用第三方容器,Startup.ConfigureServices必須返回IServiceProvider.
public class DefaultModule : Module { protected override void Load(ContainerBuilder builder) { builder.RegisterType<CharacterRepository>().As<ICharacterRepository>(); } }
在運行時,Autofac被用來解析類型和注入依賴。
建立線程安全的單例服務。若是一個單例服務對一個臨時的服務有依賴,這個臨時的服務可能須要要求線程安全根據它怎樣被單例服務使用。
單例服務的工廠方法,例如AddSingleton<TService>(IServiceColletion, Func<IServiceProvider, TService>)的第二個參數,不須要線程安全。像一個類型的構造函數,它一次只能被一個線程調用。
錯誤的:
public void MyMethod() { var options = _services.GetService<IOptionsMonitor<MyOptions>>(); var option = options.CurrentValue.Option; ... }
正確的:
private readonly MyOptions _options; public MyClass(IOptionsMonitor<MyOptions> options) { _options = options.CurrentValue; } public void MyMethod() { var option = _options.Option; ... }
有時候的場景,可能須要忽略其中的建議。
DI是static/global object access patterns的可替代方式。若是你把它和static object access 方式混合使用,可能不能認識到DI的好處。
參考網址:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2