既然是依赖注入容器,必然会涉及到服务的注册,获取服务实例,管理作用域,服务注入这四个方面。

  • 服务注册涉及如何将我们的定义的服务注册到容器中。这通常是实际开发中使用容器的第一步,而容器本身通常是由框架来实例化的,大多数时候,并不需要自己初始化容器。
  • 获取服务实例这一步,在实际开发中通常也不涉及,服务示例一般是通过注入来实现的。这里只是为了让我们对容器的使用了解的更全面一些。
  • 管理作用域一般在开发中也不涉及,框架,例如 .NET 的 MVC 框架已经帮我们把这个问题处理了。
  • 服务注入是我们需要关注的,不同的依赖注入容器支持不同的注入方式。在使用中,我们会通过注入来获取服务对象的实例。而不是自己 new 出来。

看起来很复杂,使用的时候其实很简单。

1. 服务注册

1.1 支持不同的作用域 Scope

DependencyInjection 通过 Add 方法来进行服务注册,三种不同的作用域通过三种带有不同后缀的 Add 方法来支持。

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的更多相关文章

  1. 依赖注入在 dotnet core 中实现与使用:1 基本概念

    关于 Microsoft Extension: DependencyInjection 的介绍已经很多,但是多数偏重于实现原理和一些特定的实现场景.作为 dotnet core 的核心基石,这里准备全 ...

  2. 依赖注入在 dotnet core 中实现与使用:4. 集成 Autofac

    本示例使用 .net core 5 rc-1 实现. 1. 添加 Nuget 包引用 使用 Autofac 当然要添加 Autofac 的 Nuget 包,主要涉及到两个: Autofac.Exten ...

  3. 依赖注入在 dotnet core 中实现与使用:3 使用 Lazy<T> 延迟实例化

    有些对象我们并不想一开始就实例化,由于性能或者功能的考虑,希望等到使用的时候再实例化.考虑存在一个类 A, 它使用了依赖的类 B,在 A 中,只有某些不常用到的方法会涉及调用 B 中的方法,多数情况下 ...

  4. Dotnet Core中使用AutoMapper

    官网:http://automapper.org/ 文档:https://automapper.readthedocs.io/en/latest/index.html GitHub:https://g ...

  5. 依赖注入[8]: .NET Core DI框架[服务消费]

    包含服务注册信息的IServiceCollection对象最终被用来创建作为DI容器的IServiceProvider对象.当需要消费某个服务实例的时候,我们只需要指定服务类型调用IServicePr ...

  6. 依赖注入[7]: .NET Core DI框架[服务注册]

    包含服务注册信息的IServiceCollection对象最终被用来创建作为DI容器的IServiceProvider对象.服务注册就是创建出现相应的ServiceDescriptor对象并将其添加到 ...

  7. 依赖注入[6]: .NET Core DI框架[编程体验]

    毫不夸张地说,整个ASP.NET Core框架是建立在一个依赖注入框架之上的,它在应用启动时构建请求处理管道过程中,以及利用该管道处理每个请求过程中使用到的服务对象均来源于DI容器.该DI容器不仅为A ...

  8. .NET CORE学习笔记系列(2)——依赖注入[7]: .NET Core DI框架[服务注册]

    原文https://www.cnblogs.com/artech/p/net-core-di-07.html 包含服务注册信息的IServiceCollection对象最终被用来创建作为DI容器的IS ...

  9. .NET CORE学习笔记系列(2)——依赖注入[6]: .NET Core DI框架[编程体验]

    原文https://www.cnblogs.com/artech/p/net-core-di-06.html 毫不夸张地说,整个ASP.NET Core框架是建立在一个依赖注入框架之上的,它在应用启动 ...

随机推荐

  1. August 25th, 2019. Sunday, Week 35th.

    It's what you do next that counts, not what happens but what you decide to do about it. 重点不是发生了什么,而是 ...

  2. springboot模板

    1.thymeleaf模板 2.Freemarker模板 Thymeleaf模板 首先导入依赖 <dependency> <groupId>org.springframewor ...

  3. Codeforces 1238 D. AB-string

    思维题 这次cf思维题好多啊 定义了good string 指一个串,其中每一个字符都属于一个长度>=2 的回文串的一部分.叫你求一个串中有几个good substring. 显然ab串 goo ...

  4. python的安装以及前景

    1.检查软件是否安装:在cmd命令行上输oython 假如环境已经配置好环境变量 则会显示为python的inf: 2.下载并安装python 打开python官网:https://www.pytho ...

  5. 判断上传的Excel为.xlsx还是.xls

    这个问题其实蛮简单的,具体操作如下 判断获取到上传的文件id后下载文件,然后拿到文件名称,截取文件名称后缀,判断是.xlsx还是.xls进行不同的操作即可

  6. Microsemi Libero系列教程(一)——Libero开发环境介绍、下载、安装与注册

    前言 相比与Xilinx和Altera在国内的市场,Microsemi的FPGA在国内应用很少很少,网上几乎没有详细的教程,刚开始使用时,遇到了各种问题,自己也走了不少弯路.本系列教程以Libero ...

  7. HDU 1081 To the Max 最大子矩阵(动态规划求最大连续子序列和)

    Description Given a two-dimensional array of positive and negative integers, a sub-rectangle is any ...

  8. 在vue组件中设置定时器和清除定时器

    由于项目中难免会碰到需要实时刷新,无论是获取短信码,还是在支付完成后轮询获取当前最新支付状态,这时就需要用到定时器.但是,定时器如果不及时合理地清除,会造成业务逻辑混乱甚至应用卡死的情况,这个时就需要 ...

  9. Educational Codeforces Round 76 (Rated for Div. 2)

    传送门 A. Two Rival Students 签到. Code /* * Author: heyuhhh * Created Time: 2019/11/13 22:37:26 */ #incl ...

  10. 四、读取一系列dcm图片,然后重新写入

    一.程序功能 读取一系列的CT dcm图片,然后重新写入到一个文件夹 二.代码 #pragma warning(disable:4996) #include "itkGDCMImageIO. ...