.NET Core ASP.NET Core Basic 1-2
本节内容为控制反转与依赖注入
简介
控制反转IOC
这个内容事实上在我们的C#高级篇就已经有所讲解,控制反转是一种设计模式,你可以这样理解控制反转,假设有一个人他有一部A品牌手机,他用手机进行听歌、打游戏,那么你可以创建一个手机类和一个人类
class APhone : IPhone { public string Owner{get;set;} public Phone(string own) { Owner = own; } void Play() { //省略 } void Music() { //省略 } } class Man { public string Name{get;set;} void Game() { var p = new APhone(Name); p.Play(); } }
事实上这段代码的耦合度是比较高的?它使用的是正转,也就是我需要什么东西的时候我就自己创建一个这个东西。为什么说他不好呢,如果有一天这个人决定再也不使用A品牌手机了,他决定以后只使用B品牌。那么也就意味着整个的Man类使用过APhone类的地方都需要更改。这是一个非常麻烦的事情,我们这个时候就需要运用我们的IOC控制反转了。我们将实例或者是需要使用的对象的创建交给你的调用者,自己只负责使用,其它人丢给你依赖的这个过程理解为注入。
控制反转的核心就是——原本我保存使用我自己的东西,现在我把控制权交给我的上级,我需要使用的时候再向他要。这个时候,接口的作用不言而喻,A继承了Phone接口,B也继承了,假定我们一开始就使用Phone接口去创建不同的A,B对象,那么是不是可以有效的切换AB对象呢?
依赖注入
依赖注入体现的是一个IOC(控制反转),它非常的简单,我们之前的Man类代码中使用的是正转的方式,也就是我要一个对象,那么我就创建一个。现在我们使用依赖注入就是将我们对这个对象的控制权交给上一级接口,也就成为了这种,我想要一个对象,我就向上级发出请求,上级就给我创建了一个对象。我们通常使用构造函数注入的方式进行依赖的注入。
上文的代码就会变成
class Man { private readonly IPhone _phone; public Man(IPhone phone) { _phone = phone; } }
假设这个时候你需要将手机换成B品牌,那么只需要再注入的地方传入B品牌的对象即可了。
容器
但是现在又出现了一个新的问题,假设说这个类有100个使用该接口的依赖,如果,我们是不是要在100个地方做这样的事情? 控制是反转了,依赖的创建也移交到了外部。现在的问题是依赖太多,我们需要一个地方统一管理系统中所有的依赖,这个时候,我们就使用容器进行集中的管理
容器负责两件事情:
- 绑定服务与实例之间的关系
- 获取实例,并对实例进行管理(创建与销毁)
使用
说了那么多,我们如何在.NET Core中使用我们的依赖注入呢?这里我们针对的是所有的.NET Core的应用,在.NET Core中依赖注入的核心分为两个组件:位于Microsoft.Extensions.DependencyInjection命名空间下的IServiceCollection和 IServiceProvider。
其中
- IServiceCollection 负责注册
- IServiceProvider 负责提供实例
在默认的容器ServiceCollection中有三个方法
- .AddTransient<I,C>()
- .AddSingleton<I,C>()
- .AddScoped<I,C>()
这里就不得不提一下我们依赖注入的三种生命周期了
- Singleton指的是单例模式,也就是说,在整个程序运转期间只会生成一次
- Transient指每一次GetService都会创建一个新的实例
- Scope指在同一个Scope内只初始化一个实例 ,可以理解为( 每一个request级别只创建一个实例,同一个http request会在一个 scope内)
我们可以尝试使用控制台项目来模拟依赖注入的原理,也就是说我们直接从容器获取我们对象实例,并且我们使用Guid进行唯一性的标记。
//创建三个代表不同生命周期的接口 interface IPhoneScope { Guid Guid { get; } } interface IPhoneSingleton { Guid Guid { get; } } interface IPhoneTransient { Guid Guid { get; } } //实现的类 class PhoneService:IPhoneScope,IPhoneSingleton,IPhoneTransient { public PhoneService() { this._guid = Guid.NewGuid(); } public PhoneService(Guid guid) { this._guid = guid; } private Guid _guid; public Guid Guid => this._guid; }
然后,在我们的主函数中
namespace DI_AND_IOC { class Program { static void Main(string[] args) { //注入服务 var services = new ServiceCollection() .AddScoped<IPhoneScope, PhoneService>() .AddTransient<IPhoneTransient, PhoneService>() .AddSingleton<IPhoneSingleton, PhoneService>(); //构造服务 var provider = services.BuildServiceProvider(); using (var scope = provider.CreateScope()) { var p = scope.ServiceProvider; var scopeobj1 = p.GetService<IPhoneScope>(); var transient1 = p.GetService<IPhoneTransient>(); var singleton1 = p.GetService<IPhoneSingleton>(); var scopeobj2 = p.GetService<IPhoneScope>(); var transient2 = p.GetService<IPhoneTransient>(); var singleton2 = p.GetService<IPhoneSingleton>(); Console.WriteLine( $"scope1: {scopeobj1.Guid},\n" + $"transient1: {transient1.Guid}, \n" + $"singleton1: {singleton1.Guid}\n"); Console.WriteLine($"scope2: {scopeobj2.Guid}, \n" + $"transient2: {transient2.Guid},\n" + $"singleton2: {singleton2.Guid}\n"); } //创建不同的scope using (var scope = provider.CreateScope()) { var p = scope.ServiceProvider; var scopeobj3 = p.GetService<IPhoneScope>(); var transient3 = p.GetService<IPhoneTransient>(); var singleton3 = p.GetService<IPhoneSingleton>(); Console.WriteLine($"scope3: {scopeobj3.Guid}, \n" + $"transient3: {transient3.Guid},\n" + $"singleton3: {singleton3.Guid}"); } } } }
你应该会得到类似以下的数据
scope1: 096d38e5-0c7b-4e50-9c79-241fb18a56ed, transient1: 289ebd11-8159-4f22-b53e-ed738a317313, singleton1: b453b7f5-3594-4b66-99c8-a72763abaa83 scope2: 096d38e5-0c7b-4e50-9c79-241fb18a56ed, transient2: 212ad420-e54c-4dd6-9214-abe91aacdd9c, singleton2: b453b7f5-3594-4b66-99c8-a72763abaa83 scope3: 688b6ffd-a8c1-47f4-a20a-872c2285d67c, transient3: 3d09997d-fffb-43d1-9e53-ccf9771c819d, singleton3: b453b7f5-3594-4b66-99c8-a72763abaa83
可以发现,singleton对象是不会发生改变的,而scope对象在创建新的scope之后就发生了改变,而transient对象每一次请求都在发生改变。
需要注意的是,在控制台项目使用容器服务需要引入 *** Microsoft.Extensions.DependencyInjection *** 程序集,你可以在引入中导入该dll
通过对注入服务的生命周期管控,在一些ASP.NET Core项目中,有些类(服务)有可能跨越了多个Action或者Controller,那么我们正确的使用生命周期,我们可以尽可能的节省内存,即能减少实例初始化的消耗。
在ASP.NET Core中的使用
在ASP.NET Core中,我们使用依赖注入非常的简单,在StartUp类中的ConfigureServices方法中已经为我们构建好了容器,我们只需要做类似于这样的操作
services.AddScoped<IPhoneScope, PhoneService>(); services.AddDbContext<DbContext>(); services.AddMVC();
如果你需要在控制器中注入服务,官方的推荐方案是使用构造函数注入
public IPhoneScope _ips; public Controller(IPhoneScope ips) { _ips = ips; }
特别的,你如果使用MVC的Razor页面进行注入的话,那么输入以下指令
@inject IPhoneScope ips
如果我的文章帮助了您,请您在github.NETCoreGuide项目帮我点一个star,在博客园中点一个关注和推荐。