依赖注入在 dotnet core 中实现与使用:2 使用 Extensions DependencyInjection
既然是依赖注入容器,必然会涉及到服务的注册,获取服务实例,管理作用域,服务注入这四个方面。
- 服务注册涉及如何将我们的定义的服务注册到容器中。这通常是实际开发中使用容器的第一步,而容器本身通常是由框架来实例化的,大多数时候,并不需要自己初始化容器。
- 获取服务实例这一步,在实际开发中通常也不涉及,服务示例一般是通过注入来实现的。这里只是为了让我们对容器的使用了解的更全面一些。
- 管理作用域一般在开发中也不涉及,框架,例如 .NET 的 MVC 框架已经帮我们把这个问题处理了。
- 服务注入是我们需要关注的,不同的依赖注入容器支持不同的注入方式。在使用中,我们会通过注入来获取服务对象的实例。而不是自己 new 出来。
看起来很复杂,使用的时候其实很简单。
1. 服务注册
1.1 支持不同的作用域 Scope
services.AddSingleton<IUserService, UserService>();
services.AddScoped<IUserService, UserService>();
services.AddTransient<IUserService, UserService>();
Singleton 就是单例,以后通过该容器获取出来的,都是同一个服务对象实例。
Scoped 就是限定了作用域,在每个特定的作用域中,只会有一个服务对象实例。作用域需要你自己来创建,后面在使用服务的时候,我们再介绍。
而 Transient 则在每次从容器中获取的时候,都对创建新的服务对象实例。
三种作用域简单明了,后面我们介绍服务注册的时候,就不再关注服务的作用域,都使用单例来介绍,其它两种方式是相同的。
1.2 基于接口注册
这是最为常见的注册方式,在实际开发中,服务一般都有对应的接口。为了方便注册,在 .NET Core 中一般使用泛型方式进行注册,这样比较简洁。是最推荐的方式。
services.AddSingleton<IUserService, UserService>();
当然,也可以使用基于类型的方式注册,不过代码没有使用泛型方式优雅。你需要先获取服务的类型,再通过类型进行注册。
services.AddSingleton(typeof(IUserService), typeof(UserService));
1.3 直接注册实例
如果服务并没有对应的接口,可以直接使用对象实例注册,ServiceCollection 会直接使用该实例类型作为服务接口类型来注册,这种方式比较简单粗暴。
services.AddSingleton(typeof(UserService));
1.4 注册泛型服务
泛型是很强大的特性,例如,泛型的仓储,我们只需要一个泛型仓储,就可以通过它访问针对不同类型的数据。
容器必须要支持泛型的服务,例如,针对下面的简化仓储示例,注意这里的 Get() 简化为只返回默认值。
public interface IRepository<T>
{
T Get(int id);
} public class Repository<T>: IRepository<T> {
public Repository() {
Console.WriteLine(typeof(T).Name);
} public T Get(int id){
return default(T);
}
}
只需要注册一次,就可以获得不同实现的支持。但是,泛型比较特殊,不能这样写:
services.AddSingleton<IRepository<>, Repository<>>();
你需要通过类型的方式来进行服务注册。
services.AddSingleton(typeof(IRepository<>), typeof(Repository<>));
如果没有这个仓储接口的话,就可以这样注册。
services.AddSingleton(typeof(Repository<>));
如果涉及到多个类型的泛型,例如仓储的定义变成下面这样,id 的类型使用 K 来指定。
public interface IRepository<T, K>
{
T Get(K id);
}
而仓储的实现也变成下面这样:
public class Repository<T, K>: IRepository<T, K> {
public Repository() {
Console.WriteLine(typeof(T).Name);
} public T Get(K id){
return default(T);
}
}
注册的时候,就需要写成下面的样子:
services.AddSingleton (typeof (IRepository<,>), typeof (Repository<,>));
1.5 工厂模式注册
除了让容器帮助构造服务对象实例,我们也可以自己定义构建服务对象实例的工厂供容器使用。
如果服务实现没有参数,例如 UserRepository,可以这样写:
services.AddSingleton<IUserRepository>( (serviceProvider) => {
return new UserReposotory();
} );
如果构造函数是有参数的,比如:
public class UserReposotory: IUserRepository {
public UserReposotory(string name){
Console.WriteLine( name);
}
}
注册就变成了下面的样子,需要注意的是,工厂会得到一个 ServiceProvider 的实例供你使用。
services.AddSingleton<IUserRepository> ((serviceProvider) => {
Console.WriteLine ("construction IUserReposority");
return new UserReposotory ("Tom");
});
1.6 直接使用 ServiceDescriptor 注册
实际上,上面各种注册方式最终都会通过创建一个服务描述对象来完成注册,它的定义如下,你可以看到上面各种注册方式所涉及的痕迹。
public class ServiceDescriptor
{
public ServiceLifetime Lifetime { get; }
public Type ServiceType { get; }
public Type ImplementationType { get; }
public object ImplementationInstance { get; }
public Func<IServiceProvider, object> ImplementationFactory { get; }
}
所以,实际上,你可以通过自己创建这个服务描述对象来进行注册。
services.Add(ServiceDescriptor.Singleton<IUserService, UserService>());
殊途同归,其实与使用上面的方式效果是一样的。
2 管理 Scope
如果注册服务的时候,指定了服务是单例的,无论从哪个 Scope 中获得的服务实例,都会是同一个。所以,单例的服务与 Scope 就没有什么关系了。
如果注册服务的时候,指定了服务是瞬态的,无论从哪个 Scope 中获取服务实例,都会是新创建的,每次获取就新创建一个。所以,瞬态的服务与 Scope 也没有什么关系了。
只有在注册时声明是 Scoped 的服务,才会涉及到 Scope。所以,只有 Scoped 的服务才会涉及到 Scope。
在容器初始化之后,会创建一个根的 Scope 作用域,它是默认的。
IServiceProvider provider = services.BuildServiceProvider ();
在需要作用域的时候,可以通过已经创建的 provider 来创建作用域,然后从这个新创建的作用域再获取当前的服务提供器。
IServiceProvider provider = services.BuildServiceProvider ();
IServiceScope scope = provider.CreateScope(); IServiceProvider scopedServiceProvider = scope.ServiceProvider;
通过作用域的服务提供器获取服务对象实例的时候,就会影响到通过 Scoped 注册的服务了。
这种类型的服务在每一个 Scope 中只会创建一个。
在微软的实现中,不支持嵌套的 Scope。
3 注入服务
现在又回到了涉及编程中的使用,在需要服务对象实例的时候,我们只需要注入。
微软的实现实际上只支持了构造函数注入。例如:
public class HomeController : Controller
{
private readonly IDateTime _dateTime; public HomeController(IDateTime dateTime)
{
_dateTime = dateTime;
}
4 常见问题
多次注册问题
你可以对同一个服务类型多次注册,这并不会遇到异常。在获取服务对象实例的时候,是通过最后一次注册来支持的。
需要的话,也可以获取到所有注册的服务对象实例。
var instances = provider.GetServices(typeof(IUserService));
不是直接注入服务,而是注入容器
容器本身也可以注入,它的类型是 IServiceProvider,这样,你可以自己来通过容器完成特殊的处理。
var sp = provider.GetService<IServiceProvider> ();
var userService = sp.GetService<IUserService> ();
Console.WriteLine (userService);
5 简化注册过程
https://www.cnblogs.com/catcher1994/p/handle-multi-implementations-with-same-interface-in-dotnet-core.html
参考资料:
依赖注入在 dotnet core 中实现与使用:2 使用 Extensions DependencyInjection的更多相关文章
- 依赖注入在 dotnet core 中实现与使用:1 基本概念
关于 Microsoft Extension: DependencyInjection 的介绍已经很多,但是多数偏重于实现原理和一些特定的实现场景.作为 dotnet core 的核心基石,这里准备全 ...
- 依赖注入在 dotnet core 中实现与使用:4. 集成 Autofac
本示例使用 .net core 5 rc-1 实现. 1. 添加 Nuget 包引用 使用 Autofac 当然要添加 Autofac 的 Nuget 包,主要涉及到两个: Autofac.Exten ...
- 依赖注入在 dotnet core 中实现与使用:3 使用 Lazy<T> 延迟实例化
有些对象我们并不想一开始就实例化,由于性能或者功能的考虑,希望等到使用的时候再实例化.考虑存在一个类 A, 它使用了依赖的类 B,在 A 中,只有某些不常用到的方法会涉及调用 B 中的方法,多数情况下 ...
- Dotnet Core中使用AutoMapper
官网:http://automapper.org/ 文档:https://automapper.readthedocs.io/en/latest/index.html GitHub:https://g ...
- 依赖注入[8]: .NET Core DI框架[服务消费]
包含服务注册信息的IServiceCollection对象最终被用来创建作为DI容器的IServiceProvider对象.当需要消费某个服务实例的时候,我们只需要指定服务类型调用IServicePr ...
- 依赖注入[7]: .NET Core DI框架[服务注册]
包含服务注册信息的IServiceCollection对象最终被用来创建作为DI容器的IServiceProvider对象.服务注册就是创建出现相应的ServiceDescriptor对象并将其添加到 ...
- 依赖注入[6]: .NET Core DI框架[编程体验]
毫不夸张地说,整个ASP.NET Core框架是建立在一个依赖注入框架之上的,它在应用启动时构建请求处理管道过程中,以及利用该管道处理每个请求过程中使用到的服务对象均来源于DI容器.该DI容器不仅为A ...
- .NET CORE学习笔记系列(2)——依赖注入[7]: .NET Core DI框架[服务注册]
原文https://www.cnblogs.com/artech/p/net-core-di-07.html 包含服务注册信息的IServiceCollection对象最终被用来创建作为DI容器的IS ...
- .NET CORE学习笔记系列(2)——依赖注入[6]: .NET Core DI框架[编程体验]
原文https://www.cnblogs.com/artech/p/net-core-di-06.html 毫不夸张地说,整个ASP.NET Core框架是建立在一个依赖注入框架之上的,它在应用启动 ...
随机推荐
- yii2自定义操作按钮
[ 'class' => 'yii\grid\ActionColumn', 'header' => 'Html::a('操作')',//表单头 'template' => '{vie ...
- 【Luogu P3994】高速公路
Problem Background \(C\) 国拥有一张四通八达的高速公路网树,其中有 \(n\) 个城市,城市之间由一共 \(n-1\) 条高速公路连接.除了首都 \(1\) 号城市,每个城市都 ...
- 使用dom4j类操作xml文档
dom4j操作xml数据 1.Document对象相关 ①读取XML文件,获得document对象. SAXReader reader = new SAXReader(); Document docu ...
- CreateWindowW()函数
函数原型为: 该函数利用已经注册的窗口类 创建一个窗口,并返回该窗口的句柄 HWND CreateWindow( LPCTSTR lpClassName, //窗口类名称,也可以是控件名称 LPCTS ...
- Initialize a Property After Creating an Object 创建对象后初始化属性 (XPO)
In this lesson, you will learn how to set the default value for a particular property of a business ...
- GO基础之切片
一.什么是切片 Go语言切片是对数组的抽象. Go数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组"): 与数组相比切 ...
- Linux 操作系统的权限为什么是1,2,4 而不是 1,2,3?如何用二进制来做权限管理
1.二进制做权限的优点 大家都知道,在Linux操作系统中,x - 可执行权限,w - 可写权限 , r - 可读权限.其权限值分别是1,2,4,但是有没有想过为什么是1,2,4 而不是 1,2,3 ...
- 松软科技前端课堂:JavaScript 对象
真实生活中的对象.属性和方法 在真实生活中,汽车是一个对象. 汽车有诸如车重和颜色等属性,也有诸如启动和停止的方法: 对象 属性 方法 car.name = porsche car.model = ...
- JS基础语法----Math对象
推荐查看MDN----在线的帮助文档 实例对象: 通过构造函数创建出来,实例化的对象 静态对象: 不需要创建,直接就是一个对象,方法(静态方法)直接通过这个对象名字调用 实例方法必须通过实例对 ...
- MAC本地生成SSH KEY的方法
由于时间原因,直接转载,后期有空再来好好整理一下,大家先凑合着用哈: 参考链接:https://blog.csdn.net/wangjunling888/article/details/5111565 ...