为什么要使用依赖注入框架(IoC框架)
- 借助依赖注入框架,我们可以轻松管理类之间的依赖,帮助我们在构建应用是遵循设计规则,确保代码的可维护性和可拓展性
- ASP.NET Core的整个架构中,依赖注入框架提供了对象创建和生命周期管理的核心能力,各个组件相互写作,也是由依赖注入框架的能力来实现的
依赖注入框架组件包
1 | Microsoft.Extensions.DependencyInjection.Abstractions //抽象包 |
依赖注入框架核心类型
1 | IServiceCollection //负责服务的注册 |
生命周期
1 | Singleton //单例:在整个根容器的生命周期内获得的都是同一个单例对象 |
代码演示
项目
创建名为DependencyInjectionDemo
的ASP.NET Core
项目,类型为API
示例服务类
一共有5个示例服务类接口,分别为
1 | IGenericService<T> |
因为是示例服务类,所以所有类和服务均没有属性和方法,纯粹为了验证服务注册和服务对象
验证不同生命周期的实现
注册不同生命周期的服务
在Startup.ConfigureServices
方法中新增以下代码
1 | // 注册Singleton服务 |
在方法参数中获得服务进行验证
- 修改
WeatherForecastController
类的Route
标识为[Route("[controller]/[action]")]
,方便进行测试 - 在
WeatherForecastController
新增GetService
方法,代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17[ ]
public int GetService([FromServices] IMySingletonService singletonService1,
[FromServices] IMySingletonService singletonService2,
[FromServices] IMyScopeService scopeService1,
[FromServices] IMyScopeService scopeService2,
[FromServices] IMyTransientService transientService1,
[FromServices] IMyTransientService transientService2)
{
Console.WriteLine($"{nameof(singletonService1)}:{singletonService1.GetHashCode()}");
Console.WriteLine($"{nameof(singletonService2)}:{singletonService2.GetHashCode()}");
Console.WriteLine($"{nameof(scopeService1)}:{scopeService1.GetHashCode()}");
Console.WriteLine($"{nameof(scopeService2)}:{scopeService2.GetHashCode()}");
Console.WriteLine($"{nameof(transientService1)}:{transientService1.GetHashCode()}");
Console.WriteLine($"{nameof(transientService2)}:{transientService2.GetHashCode()}");
Console.WriteLine("=========请求结束========");
return 1;
} - 运行项目,访问
/WeatherForecast/GetService
,控制台会打印出类似以下信息可以看出1
2
3
4
5
6
7singletonService1:23488915
singletonService2:23488915
scopeService1:24854661
scopeService2:24854661
transientService1:38972574
transientService2:14645893
=========请求结束========IMySingletonService
的实现对象属于同一个对象,IMyTransientService
的实现对象有多个,目前看IMyScopeService
的实现对象为同一个,但是再次访问/WeatherForecast/GetService
接口,就可以发现IMyScopeService
的实现对象为新的对象,而IMySingletonService
的实现对象还是上次访问的那个,打印信息如下1
2
3
4
5
6
7singletonService1:23488915
singletonService2:23488915
scopeService1:6630602
scopeService2:6630602
transientService1:5024928
transientService2:38414640
=========请求结束========
其他方式注册服务(以单例模式为例)
直接new对象
在Startup.ConfigureServices
方法中新增以下代码
1 | services.AddSingleton<IOrderService>(new OrderService()); |
通过工厂模式注册对象
使用工厂模式注册对象,可以在委托中使用IServiceProvider参数,这也就意味着可以从容器里获取多个对象,然后进行组装,得到我们最终需要的实现实例,可以把工厂类设计的比较复杂,比如说我们的实现类依赖了容器里面的另外一个类的情况,或者我们期望用另外一个类来包装我们原有的实现的时候
在Startup.ConfigureServices
方法中新增以下代码
1 | services.AddSingleton<IOrderService>(serviceProvider => |
尝试注册服务
尝试注册服务有两种情况
- 当指定接口已有实现类,则不再注册服务,代码如下:
1
services.TryAddSingleton<IOrderService, OrderServiceEx>();
- 当指定接口已有实现类,但是已注册的实现类不包含当前指定的实现类,则注册进去,如果已经包含当前的实现类,则不再注册服务,代码如下:在控制器里验证是否有多个实现类注册,可通过下面的方法验证(需要自行注释或修改Startup里面的服务注册情况)
1
services.TryAddEnumerable(ServiceDescriptor.Singleton<IOrderService,OrderService>());
1
2
3
4
5
6
7
8
9[ ]
public int GetServiceList([FromServices] IEnumerable<IOrderService> orderServices)
{
foreach (var item in orderServices)
{
Console.WriteLine($"获取到服务实例:{item.ToString()}:{item.GetHashCode()}");
}
return 1;
}移除和替换服务
移除服务
指的是直接从容器中移除指定接口的所有实现类,代码如下:1
services.RemoveAll<IOrderService>();
替换服务
指的是替换指定接口的实现类,同时也会替换该服务的生命周期,代码如下:1
services.Replace(ServiceDescriptor.Singleton<IOrderService, OrderServiceEx>());
泛型服务注册
即对泛型服务注册,代码如下:
1 | services.AddSingleton(typeof(IGenericService<>), typeof(GenericService<>)); |
可以通过在控制器的构造函数中获取到服务对象,代码如下:
1 | public WeatherForecastController(ILogger<WeatherForecastController> logger,IOrderService orderService,IGenericService<IOrderService> genericService) |
可以通过断点查看最终IGenericService
的IOrderService
为哪个实现类
服务对象的获取
通过上面可以看出,服务对象有两种获取方式,一种是通过构造函数直接注入,一种是通过函数参数,使用[FromServices]
标签来注入
一般按照使用情况来确定用哪种方式,如果整个类使用地方比较多,则使用构造函数注入,如果只有某一个方法使用,则一般使用函数参数来注入