DI在.NET Core里面被提到了一个非常重要的位置, 这篇文章主要再给大家普及一下关于依赖注入的概念,身边有工作六七年的同事还个东西搞不清楚。另外再介绍一下.NET  Core的DI实现以及对实例生命周期的管理(这个是经常面试会问到的问题)。最后再给大家简单介绍一下在控制台以及Mvc下如何使用DI,以及如何把默认的Service Container 替换成Autofac。
  • 一、什么是依赖注入

  • 1.1 依赖
  • 1.2 什么注入
  • 为什么反转
  • 何为容器
  • 二、.NET Core DI

  • 2.1 实例的注册
  • 2.2 实例生命周期之单例
  • 2.3 实例生命周期之Tranisent
  • 2.4 实例生命周期之Scoped
  • 三、DI在ASP.NET Core中的应用

  • 3.1 在Startup类中初始化
  • 3.2 Controller中使用
  • 3.3 View中使用
  • 3.4 通过HttpContext来获取
  • 四、如何替换其它的Ioc容器

一、什么是依赖注入(Denpendency Injection)

这也是个老身常谈的问题,到底依赖注入是什么? 为什么要用它? 初学者特别容易对控制反转IOC(Iversion of Control),DI等概念搞晕。

1.1依赖

当一个类需要另一个类协作来完成工作的时候就产生了依赖。比如我们在AccountController这个控制器需要完成和用户相关的注册、登录 等事情。其中的登录我们由EF结合Idnetity来完成,所以我们封装了一个EFLoginService。这里AccountController就有一个ILoginService的依赖。
这里有一个设计原则:依赖于抽象,而不是具体的实现。所以我们给EFLoginService定义了一个接口,抽象了LoginService的行为。

1.2 什么是注入

注入体现的是一个IOC(控制反转的的思想)。在反转之前 ,我们先看看正转。
AccountController自己来实例化需要的依赖。
  1. private ILoginService<ApplicationUser> _loginService;
  2. public AccountController()
  3. {
  4. _loginService = new EFLoginService()
  5. }

大师说,这样不好。你不应该自己创建它,而是应该由你的调用者给你。于是你通过构造函数让外界把这两个依赖传给你。

  1. public
  2. AccountController(ILoginService<ApplicationUser> loginService)
  3. {
  4. _loginService = loginService;
  5. }

把依赖的创建丢给其它人,自己只负责使用,其它人丢给你依赖的这个过程理解为注入。

1.3 为什么要反转?

为了在业务变化的时候尽少改动代码可能造成的问题。
比如我们现在要把从EF中去验证登录改为从Redis去读,于是我们加了一个 RedisLoginService。这个时候我们只需要在原来注入的地方改一下就可以了。
  1. var controller = new AccountController(new EFLoginService());
  2. controller.Login(userName, password);
  3. // 用Redis来替换原来的EF登录
  4. var controller = new AccountController(new RedisLoginService());
  5. controller.Login(userName, password);

1.4 何为容器

上面我们在使用AccountController的时候,我们自己通过代码创建了一个ILoggingServce的实例。想象一下,一个系统中如果有100个这样的地方,我们是不是要在100个地方做这样的事情? 控制是反转了,依赖的创建也移交到了外部。现在的问题是依赖太多,我们需要一个地方统一管理系统中所有的依赖,容器诞生了。
容器负责两件事情:
  • 绑定服务与实例之间的关系
  • 获取实例,并对实例进行管理(创建与销毁)

二、.NET Core DI

2.1 实例的注册

前面讲清楚DI和Ioc的关键概念之后,我们先来看看在控制台中对.NET Core DI的应用。在.NET Core中DI的核心分为两个组件:IServiceCollection和 IServiceProvider。
  • IServiceCollection 负责注册
  • IServiceProvider 负责提供实例
通过默认的 ServiceCollection(在Microsoft.Extensions.DependencyInjection命名空间下)有三个方法:
  1. var serviceCollection = new ServiceCollection()
  2. .AddTransient<ILoginService, EFLoginService>()
  3. .AddSingleton<ILoginService, EFLoginService>()
  4. .AddScoped<ILoginService, EFLoginService>();
这三个方法都是将我们的实例注册进去,只不过实例的生命周期不一样。什么时候生命周期我们下一节接着讲。
ServiceCollection的默认实现是提供一个ServiceDescriptor的List
  1. public interface IServiceCollection : IList<ServiceDescriptor>
  2. {
  3. }

我们上面的AddTransient、AddSignletone和Scoped方法是IServiceCollection的扩展方法, 都是往这个List里面添加ServiceDescriptor。

  1. private static IServiceCollection Add(
  2. IServiceCollection collection,
  3. Type serviceType,
  4. Type implementationType,
  5. ServiceLifetime lifetime)
  6. {
  7. var descriptor =
  8. new ServiceDescriptor(serviceType, implementationType, lifetime);
  9. collection.Add(descriptor);
  10. return collection;
  11. }

2.2 实例的生命周期之单例

我们上面看到了,.NET Core DI 为我们提供的实例生命周其包括三种:
  • Transient: 每一次GetService都会创建一个新的实例
  • Scoped:  在同一个Scope内只初始化一个实例 ,可以理解为( 每一个request级别只创建一个实例,同一个http request会在一个 scope内)
  • Singleton :整个应用程序生命周期以内只创建一个实例
对应了Microsoft.Extensions.DependencyInjection.ServiceLifetime的三个枚举值
  1. public enum ServiceLifetime
  2. {
  3. Singleton,
  4. Scoped,
  5. Transient
  6. }
为了大家能够更好的理解这个生命周期的概念我们做一个测试:
定义一个最基本的IOperation里面有一个 OperationId的属性,IOperationSingleton也是一样,只不过是另外一个接口。
  1. public interface IOperation
  2. {
  3. Guid OperationId { get; }
  4. }
  5. public interface IOperationSingleton : IOperation { }
  6. public interface IOperationTransient : IOperation{}
  7. public interface IOperationScoped : IOperation{}

我们的 Operation实现很简单,可以在构造函数中传入一个Guid进行赋值,如果没有的话则自已New一个 Guid。

  1. public class Operation :
  2. IOperationSingleton,
  3. IOperationTransient,
  4. IOperationScoped
  5. {
  6. private Guid _guid;
  7. public Operation() {
  8. _guid = Guid.NewGuid();
  9. }
  10. public Operation(Guid guid)
  11. {
  12. _guid = guid;
  13. }
  14. public Guid OperationId => _guid;
  15. }

在程序内我们可以多次调用ServiceProvider的GetService方法,获取到的都是同一个实例。

  1. var services = new ServiceCollection();
  2. // 默认构造
  3. services.AddSingleton<IOperationSingleton, Operation>();
  4. // 自定义传入Guid空值
  5. services.AddSingleton<IOperationSingleton>(
  6. new Operation(Guid.Empty));
  7. // 自定义传入一个New的Guid
  8. services.AddSingleton <IOperationSingleton>(
  9. new Operation(Guid.NewGuid()));
  10. var provider = services.BuildServiceProvider();
  11. // 输出singletone1的Guid
  12. var singletone1 = provider.GetService<IOperationSingleton>();
  13. Console.WriteLine($"signletone1: {singletone1.OperationId}");
  14. // 输出singletone2的Guid
  15. var singletone2 = provider.GetService<IOperationSingleton>();
  16. Console.WriteLine($"signletone2: {singletone2.OperationId}");
  17. Console.WriteLine($"singletone1 == singletone2 ? : { singletone1 == singletone2 }");

我们对IOperationSingleton注册了三次,最后获取两次,大家要注意到我们获取到的始终都是我们最后一次注册的那个给了一个Guid的实例,前面的会被覆盖。

2.3 实例生命周期之Tranisent

这次我们获取到的IOperationTransient为两个不同的实例。

  1. var services = new ServiceCollection();
  2. services.AddTransient<IOperationTransient, Operation>();
  3. var provider = services.BuildServiceProvider();
  4. var transient1 = provider.GetService<IOperationTransient>();
  5. Console.WriteLine($"transient1: {transient1.OperationId}");
  6. var transient2 = provider.GetService<IOperationTransient>();
  7. Console.WriteLine($"transient2: {transient2.OperationId}");
  8. Console.WriteLine($"transient1 == transient2 ? :
  9. { transient1 == transient2 }");

2.4 实例生命周期之Scoped

.NET Core人IServiceProvider提供CreateScope产生一个新的ServiceProvider范围,在这个范围下的Scope标注的实例将只会是同一个实例。换句话来说:用Scope注册的对象,在同一个ServiceProvider的 Scope下相当于单例。
同样我们先分别注册IOperationScoped、IOperationTransient和IOperationSingletone 这三个实例,用对应的Scoped、Transient、和Singleton生命周期。
  1. var services = new ServiceCollection()
  2. .AddScoped<IOperationScoped, Operation>()
  3. .AddTransient<IOperationTransient, Operation>()
  4. .AddSingleton<IOperationSingleton, Operation>();

接下来我们用ServiceProvider.CreateScope方法创建一个Scope

  1. var provider = services.BuildServiceProvider();
  2. using (var scope1 = provider.CreateScope())
  3. {
  4. var p = scope1.ServiceProvider;
  5. var scopeobj1 = p.GetService<IOperationScoped>();
  6. var transient1 = p.GetService<IOperationTransient>();
  7. var singleton1 = p.GetService<IOperationSingleton>();
  8. var scopeobj2 = p.GetService<IOperationScoped>();
  9. var transient2 = p.GetService<IOperationTransient>();
  10. var singleton2 = p.GetService<IOperationSingleton>();
  11. Console.WriteLine(
  12. $"scope1: { scopeobj1.OperationId }," +
  13. $"transient1: {transient1.OperationId}, " +
  14. $"singleton1: {singleton1.OperationId}");
  15. Console.WriteLine($"scope2: { scopeobj2.OperationId }, " +
  16. $"transient2: {transient2.OperationId}, " +
  17. $"singleton2: {singleton2.OperationId}");
  18. }

接下来

  1. scope1: 200d1e63-d024-4cd3-88c9-35fdf5c00956,
  2. transient1: fb35f570-713e-43fc-854c-972eed2fae52,
  3. singleton1: da6cf60f-670a-4a86-8fd6-01b635f74225
  4. scope2: 200d1e63-d024-4cd3-88c9-35fdf5c00956,
  5. transient2: 2766a1ee-766f-4116-8a48-3e569de54259,
  6. singleton2: da6cf60f-670a-4a86-8fd6-01b635f74225

如果再创建一个新的Scope运行,

  1. scope1: 29f127a7-baf5-4ab0-b264-fcced11d0729,
  2. transient1: 035d8bfc-c516-44a7-94a5-3720bd39ce57,
  3. singleton1: da6cf60f-670a-4a86-8fd6-01b635f74225
  4. scope2: 29f127a7-baf5-4ab0-b264-fcced11d0729,
  5. transient2: 74c37151-6497-4223-b558-a4ffc1897d57,
  6. singleton2: da6cf60f-670a-4a86-8fd6-01b635f74225
大家注意到上面我们一共得到了 4个Transient实例,2个Scope实例,1个Singleton实例。
这有什么用?
如果在Mvc中用过Autofac的InstancePerRequest的同学就知道,有一些对象在一个请求跨越多个Action或者多个Service、Repository的时候,比如最常用的DBContext它可以是一个实例。即能减少实例初始化的消耗,还能实现跨Service事务的功能。(注:在ASP.NET Core中所有用到EF的Service 都需要注册成Scoped )
 
而实现这种功能的方法就是在整个reqeust请求的生命周期以内共用了一个Scope。

三、DI在ASP.NET Core中的应用

3.1在Startup类中初始化

ASP.NET Core可以在Startup.cs的  ConfigureService中配置DI,大家看到 IServiceCollection这个参数应该就比较熟悉了。
  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.AddTransient<ILoginService<ApplicationUser>,
  4. EFLoginService>();
  5. services.AddMvc();
  6. )

ASP.NET Core的一些组件已经提供了一些实例的绑定,像AddMvc就是Mvc Middleware在 IServiceCollection上添加的扩展方法。

  1. public static IMvcBuilder AddMvc(this IServiceCollection services)
  2. {
  3. if (services == null)
  4. {
  5. throw new ArgumentNullException(nameof(services));
  6. }
  7. var builder = services.AddMvcCore();
  8. builder.AddApiExplorer();
  9. builder.AddAuthorization();
  10. AddDefaultFrameworkParts(builder.PartManager);
  11. ...
  12. }

3.2 Controller中使用

一般可以通过构造函数或者属性来实现注入,但是官方推荐是通过构造函数。这也是所谓的显式依赖。
  1. private ILoginService<ApplicationUser> _loginService;
  2. public AccountController(
  3. ILoginService<ApplicationUser> loginService)
  4. {
  5. _loginService = loginService;
  6. }

我们只要在控制器的构造函数里面写了这个参数,ServiceProvider就会帮我们注入进来。这一步是在Mvc初始化控制器的时候完成的,我们后面再介绍到Mvc的时候会往细里讲。

3.3 View中使用

在View中需要用@inject 再声明一下,起一个别名。
  1. @using MilkStone.Services;
  2. @model MilkStone.Models.AccountViewModel.LoginViewModel
  3. @inject ILoginService<ApplicationUser> loginService
  4. <!DOCTYPE html>
  5. <html xmlns="http://www.w3.org/1999/xhtml">
  6. <head></head>
  7. <body>
  8. @loginService.GetUserName()
  9. </body>
  10. </html>

了解ASP.NET Core 依赖注入,看这篇就够了 于2017年11月6日由jesseliu发布的更多相关文章

  1. 任务21 :了解ASP.NET Core 依赖注入,看这篇就够了

    DI在.NET Core里面被提到了一个非常重要的位置, 这篇文章主要再给大家普及一下关于依赖注入的概念,身边有工作六七年的同事还个东西搞不清楚.另外再介绍一下.NET  Core的DI实现以及对实例 ...

  2. # ASP.NET Core依赖注入解读&使用Autofac替代实现

    标签: 依赖注入 Autofac ASPNETCore ASP.NET Core依赖注入解读&使用Autofac替代实现 1. 前言 2. ASP.NET Core 中的DI方式 3. Aut ...

  3. ASP.NET Core依赖注入——依赖注入最佳实践

    在这篇文章中,我们将深入研究.NET Core和ASP.NET Core MVC中的依赖注入,将介绍几乎所有可能的选项,依赖注入是ASP.Net Core的核心,我将分享在ASP.Net Core应用 ...

  4. ASP.NET Core 依赖注入最佳实践——提示与技巧

    在这篇文章,我将分享一些在ASP.NET Core程序中使用依赖注入的个人经验和建议.这些原则背后的动机如下: 高效地设计服务和它们的依赖. 预防多线程问题. 预防内存泄漏. 预防潜在的BUG. 这篇 ...

  5. ASP.NET Core依赖注入最佳实践,提示&技巧

    分享翻译一篇Abp框架作者(Halil İbrahim Kalkan)关于ASP.NET Core依赖注入的博文. 在本文中,我将分享我在ASP.NET Core应用程序中使用依赖注入的经验和建议. ...

  6. ASP.NET Core依赖注入解读&使用Autofac替代实现【转载】

    ASP.NET Core依赖注入解读&使用Autofac替代实现 1. 前言 2. ASP.NET Core 中的DI方式 3. Autofac实现和自定义实现扩展方法 3.1 安装Autof ...

  7. ASP.NET Core 依赖注入基本用法

    ASP.NET Core 依赖注入 ASP.NET Core从框架层对依赖注入提供支持.也就是说,如果你不了解依赖注入,将很难适应 ASP.NET Core的开发模式.本文将介绍依赖注入的基本概念,并 ...

  8. ASP.NET Core 依赖注入(构造函数注入,属性注入等)

    原文:ASP.NET Core 依赖注入(构造函数注入,属性注入等) 如果你不熟悉ASP.NET Core依赖注入,先阅读文章: 在ASP.NET Core中使用依赖注入   构造函数注入 构造函数注 ...

  9. ASP.NET Core 依赖注入最佳实践与技巧

    ASP.NET Core 依赖注入最佳实践与技巧 原文地址:https://medium.com/volosoft/asp-net-core-dependency-injection-best-pra ...

随机推荐

  1. 取出csv文件中的中文评论数据

    # -*- coding: utf-8 -*- import csv import re csvfile = 'weibo.csv' def columns_data(path, column): c ...

  2. 利用 fdisk进行分区

    ):fdisk命令参数 p:打印分区表. n:新建一个新分区. d:删除一个新分区. q:退出不保存. w:退出且保存. 例子: 先看下磁盘: root@archiso ~ # lsblk 在这里对磁 ...

  3. iOS概念之KVO(Key-Value Observing)

    在一个复杂的,有状态的系统中,当一个对象的状态发生改变,如何通知系统,并对状态改变做出相应的行为是必需考虑的一个问题,在iOS中为这类问题提供了4种解决方法: 1. NSNotifiactaion和N ...

  4. VC/Wince 实现仿Win8 Metro风格界面2——页面滑动切换(附效果图)

    前几天开始写仿Win8 Metro界面文章,部分网友觉得不错,感谢各位的意见.本来今天一直在折腾Android VLC播放器,没时间写.不过明天休息,所以今天就抽时间先写一下. 言归正传,我们都知道W ...

  5. hystrix服务降级和服务熔断的区别

    故事的背景是这样的:由于小强在工作中碰到一些问题,于是想请教一下业界大牛小壮.于是发生了下面的两个场景: 小强在拿起常用手机拨号时发现该手机没有能够拨通,所以就拿出了备用手机拨通了某A的电话,这个过程 ...

  6. NSDateFormater格式化参数汇总

    NSDateFormatterhtml, body {overflow-x: initial !important;}html { font-size: 14px; } body { margin: ...

  7. shell常用命令大全

    目录: 一.文件目录类命令 二.文件压缩和归档类命令 三.系统状态类命令 四.网络类命令 五.其他 一.文件目录类命令 1.查看联机帮助信息. man命令.#man ls info命令. #info ...

  8. UEFI Protocol

    MEM_INFO_PROTOCOL MEM_INFO_PROTOCOL; EFI_LOADED_IMAGE_PROTOCOL EFI_DEVICE_PATH_PROTOCOL EFI_DRIVER_B ...

  9. 利用python的KMeans和PCA包实现聚类算法

    题目: 通过给出的驾驶员行为数据(trip.csv),对驾驶员不同时段的驾驶类型进行聚类,聚成普通驾驶类型,激进类型和超冷静型3类 . 利用Python的scikit-learn包中的Kmeans算法 ...

  10. linux下nc的使用

    发送端:cat test.txt | nc -l -p 6666或者nc -l  -p 6666 < test.txt                    有些版本不要在 -p[监听6666端 ...