这篇文章主要讲解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