aspcore系列之Dependencyinjection(依赖注⼊)
这篇⽂章主要讲解asp core 依赖注⼊的⼀些内容。
ASP.NET Core⽀持依赖注⼊。这是⼀种在类和其依赖之间实现控制反转的⼀种技术(IOC).
⼀.依赖注⼊概述
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类):
public class IndexModel : PageModel
{
MyDependency _dependency = new MyDependency();
public async Task OnGetAsync()
{
await _dependency.WriteMessage(
"IndexModel.OnGetAsync created this message.");
}
}
2.原始代码分析
IndexModel类创建了MyDependency类,并且直接依赖MyDependency实例。上⾯的代码依赖是有问题的,并且应该被避免(避免直接创建依赖的实例对象),
原因如下:
需要⽤⼀个不同的实现来替换MyDependency,这个类必须被修改
如果MyDependency有依赖,他们必须被这个类配置。在⼀个有很多类依赖MyDependency的⼤的项⽬中,配置代码在应⽤中会很分散。
这种实现对于单元测试是困难的。对于MyDependency,应⽤应该使⽤mock或者stub,⽤这种⽅式是不可能的。
依赖注⼊解决那些问题:
接⼝的使⽤抽象了依赖的实现
在service container注册依赖。ASP.NET Core提供了⼀个内置的service container, IServiceProvider. Services是在应⽤
的Startup.ConfigureServices中被注册。
⼀个类是在构造函数中注⼊service。框架执⾏着创建⼀个带依赖的实例的责任,并且当不需要时,释放。
3.下⾯是改良后的代码
这⽰例应⽤中,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(注意:不⽌这些,这⾥简单举例)
框架提供的服务(Framework-provided services)⼆.框架提供的服务
⼆.
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();
}
更多的信息:
三. 服务⽣命周期(Service lifetimes)
为每个注册的服务选择⼀个合适的⽣命周期。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是使⽤user-facing abstractions,例如Tag Helpers , MVC controllers 和 model binders.
构造函数可以接受参数,不通过依赖注⼊提供,但是这些参数必须指定默认值。
当服务被通过IServiceProvider或者ActivatorUtilities解析时,构造函数注⼊要求⼀个公共的构造函数。
当服务被ActivatorUtilities解析时,构造函数注⼊要求⼀个合适的构造函数存在。构造函数的重载是被⽀持的,但是只有⼀个重载可以存在,它的参数可以被依赖注⼊执⾏(即:可以被依赖注⼊执⾏的,只有⼀个构造函数的重载)。
四. Entity Framework contexts
Entity Framework contexts 通常使⽤scoped lifetime ,添加到服务容器中(service container).因为w e b 应⽤数据库操作的范围适⽤于client request(客户端请求)。默认的⽣命周期是scoped,如果⼀个⽣命周期没有被AddDbContext<TContext>重载指定,当注册database context时。给出⽣命周期的服务不应该使⽤⼀个⽣命周期⽐服务的⽣命周期短的database context.
五.Lifetime and registration options
为了说明lifetime和registration options之间的不同,考虑下⾯的接⼝:这些接⼝表⽰的任务都是带有唯⼀标识的操作。取决于这些接⼝的操作服务的⽣命周期怎么配置,container提供了要么是同⼀个要么是不同的服务当被⼀个类请求时:
public interface IOperation
{
Guid OperationId { get; }
}
mvc实例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被通过依赖注⼊请求,它要么接收每个服务的⼀个新实例要么接收⼀个已经存在的实例(在依赖服务的⽣命周期的基础上)。
当临时服务(transient services)被创建时,当被从容器中请求时,IOperationTransient服务的OperationId是不同
的。OperationService接收到⼀个IOperationTransient类的实例。这个新实例产⽣⼀个不同的OperationId.
每个client请求时,scoped services被创建,IOperationScoped service的OperationId是⼀样的,在⼀个client request内。跨越client requests,两个service享⽤⼀个不同的OperationId的值。
当singleton和singleton-instance服务⼀旦被创建,并且被使⽤跨越所有的client requests和所有的服务,则OperationId跨越所有的service requests是⼀致的。
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,
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论