浅谈ASP.NET Core中的DI

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

浅谈ASP.NET Core中的DI

Liuww06   2020-03-21 我要评论
# DI的一些事 传送门[马丁大叔的文章](https://martinfowler.com/articles/injection.html#InversionOfControl) ## 什么是依赖注入(DI: Dependency Injection)?     依赖注入(DI)是一种面向对象的软件设计模式,主要是帮助开发人员开发出松耦合的应用程序。同时呢,让应用更容易进行单元测试和维护。     DI其实就是用一个`注入器类`为一个对象提供其依赖的**一个过程**!如何更好的理解呢?下面就举个列子解释下!     比如 `class Client`,它要使用服务`class Service`的提供的功能,这个时候就说Service是Client的依赖,程序实现如下: ```csharp var s = new Service(); var c = new Client(s); ```     很明显我们还要承担创建Service的对象的职责,程序出现了强耦合问题,后面如果需求变化,我们要替换掉Service,那我们就要修改这边的代码,这样的程序很面明,扩展性,灵活性比较差了!     引入DI之后呢,我们应该还有一个`注入器类`,假设是 `class Injector` 。为了更好的解释DI的好处,上面的代码我们重新设定为 `class Client` 依赖 `接口IService` , `class Service ` 实现了`IService` ,这个时候我们的程序主流程不需要关注如何创建的Service,可以把这部分的职责委托给Injector,我们只要告诉Injector,我需要IService,请提供给我,程序实现如下: ```csharp var s = Injector.Get(typeof(IService)); var c = new Client(s); ```      这样的好处就很明显了,我们只关注自己的核心业务职责,对应依赖如何创建的,具体是什么类实现的,都不用自己管了,权力交给注入器就可以了!    ** 划重点**:其实上面这个过程大家应该发现了我们把本来自己的一部分控制权,转交给了注入器去做,这个就是我们经常说的`IOC`(Inversion of Control,控制反转)。DI其实就是IOC计原则的一种实现。还有我们平常说的观察者默认,其实也是IOC的一种实现,核心就是把部分职责(非核心职责)转交出去,从而去构建出一种松耦合的应用! ## 什么是依赖注入容器(DI Container)?     DI Container ,也可以叫 IOC Container,其实是一个框架(Framework),它提供了一整套的DI解决方案,它负责创建依赖,然后自动把依赖注入到需要它们的其他对象里面,同时还负责管理依赖的生命周期!一些强大的第三方容器还提供各种各样的功能,使我们更加愉快的撸代码!          常用的第三方DI Container: - Spring.NET -  Autofac - Unity - Ninject # ASP .NET Core中的DI     在ASP.NET Core中,把依赖统一称作服务(services),所以DI Container就也被称为Service Container,Asp.NET Core提供了一个简单的内置容器 `IServiceProvider ` ,它默认支持构造器注入 `constructor injection`,满足我们大多数的功能需求! 服务主要分为两类: 1. Framework Services:框架服务,由ASP.NET Core框架提供,例如 `IApplicationBuilder` 、`IHostingEnvironment` ... ,详情见[https:/https://img.qb5200.com/download-x/docs.microsoft.com/en-us/aspnet/core/fundamentalshttps://img.qb5200.com/download-x/dependency-injection?view=aspnetcore-3.1#framework-provided-services](https:/https://img.qb5200.com/download-x/docs.microsoft.com/en-us/aspnet/core/fundamentalshttps://img.qb5200.com/download-x/dependency-injection?view=aspnetcore-3.1#framework-provided-services) 。 2. Appliction Services:应用服务,由我们根据实际业务创建。 如果要通过DI容器自动实现注入我们的服务,我们必须要先在容器中登记服务(只需要注册应用服务,框架服务已经被ASP.NET Core框架注入了)。 ## 注册服务(Registering Services) 假设我们有一个ILog接口和它的一个实现类,我们要把它注入到DI容器里面,然后在应用中使用它。 ```csharp public interface ILog { void Info(string msg); void Error(string err); } public class ConsoleLogger:ILog { public void Info(string msg) { Console.WriteLine(msg); } public void Error(string err) { Console.WriteLine(err); } } ``` 然后在`Startup`类的`ConfigureServices()`方法中注册上面的服务,`ConfigureServices()`有一个`IServiceCollection`参数,就是用它来注册应用服务。 ```csharp public class Startup {     public void ConfigureServices(IServiceCollection services)     { services.Add(new ServiceDescriptor(typeof(ILog), typeof(ConsoleLogger), ServiceLifetime.Singleton)); } } ``` 类`ServiceDescriptor`用来描述服务的类型、服务的具体实现已经服务的一个生命周期(Service Lifetime),上面我们指定了服务ILog的实现是ConsoleLogger,且是一个单例(Singleton)。 通常情况我们都是使用`IServiceCollection`扩展方法来注册服务 ```csharp services.AddSingleton();//注册为单例 services.TryAddSingleton(); ``` ## 构造函数注入(Contractor Injection) 一旦我们注册了函数,当应用类的构造函数包含了需要依赖的服务,DI容器就自动帮我们注入依赖。 ```csharp public class HomeController : Controller { ILog _log; public HomeController(ILog log) { _log = log; } public IActionResult Index() { _log.Info("Executing /home/index"); return View(); } }     ``` 控制器需要ILog服务,只需要在构造函数的参数中包含ILog类型即可,我们不需做其他任何事,DI容器自动给我们创建ILog的实例,并根据注入时指定的生命周期,在切当是时机销毁(Dispose)这个实例。 ## Action方法注入(Method Injection) 有时候,我们只需要在某一个方法中需要这个服务,这时,我们可以给方案的参数标记上`[FromServices]`    这个特性,容器就能自动为我们注入这个依赖服务的实例了 ```csharp public IActionResult Index([FromServices] ILog log) { log.Info("Index method executing"); return View(); } ``` ## 属性注入(Property Injection) ASP.NET Core自带这个容器不支持,需要使用第三方容器,例如Autofac 。 ## 服务的生命周期(Service Lifetime) DI容器负责管理已注册服务的生命周期,它根据指定的生命周期自动销毁服务实例。ASP.NET Core 的服务可以配置以下三种生命周期形式: - **Transient**(瞬态) 每次从DI容器中解析(获取)服务时,DI容器都是返回一个新的服务实例。 ```csharp //原始方法 services.Add(new ServiceDescriptor(typeof(ILog), typeof(ConsoleLogger), ServiceLifetime.Transient)); //扩展方法 services.AddTransient(); ``` - **Scoped**(作用域) 每一个作用域范围内(例如每一个HTTP 请求)从DI容器中解析出来的实例都是同一个 ```csharp //原始方法 services.Add(new ServiceDescriptor(typeof(ILog), typeof(ConsoleLogger), ServiceLifetime.Scoped)); //扩展方法 services.AddScoped(); ``` - `Singleton`(单例) 只在第一次请求是创建,之后所有请求都共享同一个服务实例,直到应用程序的生命周结束。所以在ASP.NET Core应用中,没有必须手动去创建一个单例,通过注册一个单例服务到容器中即可。 ```csharp //原始方法 services.Add(new ServiceDescriptor(typeof(ILog), new ConsoleLogger())); //扩展方法 services.AddSingleton(); ``` ## DI使用经验的一些总结 ### **泛型如何注册?** 通过开放式泛型(Open Generics)注册服务。假设上面的类型修改成 `ILog` 和 `ConsoleLogger ` ,那么我们按照下面的方式注册即可 ```csharp services.AddScoped(typeof(ILog<>), typeof(ConsoleLogger<>)); ``` ### 相同的接口类型注入两个实现类型会怎样? 如果我们在注册服务时,相同类型注册多次并不会报错,但在解析时,返回的是最后一次注册的类型的实例。 ```csharp public interface ILog { void Info(string msg); } public class Logger1 : ILog { public void Info(string msg) { } } public class Logger2 : ILog { public void Info(string msg) { } } public class Startup {     public void ConfigureServices(IServiceCollection services)     {     services.AddTransient();      services.AddTransient();     } } public class HomeController : Controller {     ILog _log;     public HomeController(ILog log)     {     _log = log;//_log is Logger2      } } ```     为了避免我们多次注册,导致具体实现被覆盖的问题,所以我们一般都是使用    `TryAddTransient`,这个方法注册时,检测到相同类型已经被注册过,就不会在进行注册 ```csharp public class Startup {     public void ConfigureServices(IServiceCollection services)     {     services.TryAddTransient();      services.TryAddTransient();     } } public class HomeController : Controller {     ILog _log;     public HomeController(ILog log)     { _log = log;//_log is Logger1      } } ``` ### 自己想要手动从容器中获取服务对象,怎么做? ASP.NET Core中的DI Container是`IServiceProvider`,只要获取这个对象,然后调用 `GetService` 这个方法即可。 - 如果在Controller中 HttpContext的属性`RequestServices`就是`IServiceProvider`类型,所以我们可以按照下面的方法: ```csharp public IActionResult Index() { var services = this.HttpContext.RequestServices; var log = (ILog)services.GetService(typeof(ILog)); log.Info("Index method executing"); return View(); } ``` - 如果在应用中 在应用中时,我们可以通过构造函数注入`IServiceProvider` ```csharp public class MyAppService { private IServiceProvider _services; public MyAppService(IServiceProvider services) { _services=services; } public void Test() { //原始方法 var log = (ILog)services.GetService(typeof(ILog)); //通过扩展方法,需要nuget添加Microsoft.Extensions.DependencyInjection.Abstractions 这个引用 var log = services.GetService(); } } ``` # 结语 ASP.NET Core已经很强大了,提供了很多实用功能,希望.Net的战友们能在基本知识储备的前提下,多多发掘总结出ASP.NET Core开发的最佳实践,为推动.NET Core生态建设贡献一份自己的力量!:muscle:

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们