依赖服务注入(DI)
分类: Asp.net Core入门 ◆ 标签: #Asp.Net core基础 #基础 #Web ◆ 发布于: 2023-06-04 20:14:16

我们前两章学习了如何使用ASP.net Core的Startup
类,本章我们继续学习依赖服务注入的概念,这个也就是我们常说的DI, 还有另外一个词我们也经常提及,那就是IoC
- 控制反转。很多同学在学习到这两个概念的时候,可能经常会有些莫名其妙,IoC和DI到底有什么联系和区别?这两个概念有人说是一回事,实际上这个理解是大错特错,个人认为正确的理解应该是:IoC是目的,而且DI是实现IoC的手段。所谓的控制反转实际的目的是将流程委托给应用程序的框架,也就是将控制交给框架,然后由框架使用一定的技术,方便用户以这些技术或者接口扩展该框架的功能,实际上也就是将某些场景的流程控制功能模块化交由框架来实现,留出接口来方便用户控制和扩展,这就是IoC的设计目的,而DI就是实现这个目的的技术之一,也是目前用得最多的技术。
我们先来总结一下.Net
提供的DI功能:
- 必须使用接口或者是基类来抽象化类之间的依赖关系。
- 将依赖关系注册到由
.Net
提供的服务容器中,这个在.Net
中是以接口IServiceProfice
的实现类来表示的,依赖关系一般注册到实现了IServiceCollection
接口的容器类中,一般是在应用启动的时候完成注册的,一旦完成注册后,使用方法BuildServiceProvider
来创建服务容器。 - 在
.Net
中通过构造函数来完成依赖注入,由框架来负责创建应用需要的实例,并在不需要它的时候回收它。
需要注意的是在.Net
的DI属于中,服务(Service), 只要是指:
- 向其他对象提供服务的对象,而且被服务的对象都需要通过依赖而注入的这些类。
- 服务这个概念不是仅仅为了
Web
开发准备的,虽然大多数的时候它被用在Web
应用中。
另外对于.Net框架,它有提供默认的几个服务,后面的章节我们会来了解和学习它。
.Net
框架提供服务
.Net
框架提供了一些内置的服务,这些是由Host
在创建和配置的时候默认就已经初始化好了,在客户的应用方法ConfigureService
方法之前就已经注入到系统中了。如下是列表:
服务类型 | 生命周期范围 |
---|---|
Microsoft.Extensions.DependencyInjection.IServiceScopeFactory | Singleton |
IHostApplicationLifetime | Singleton |
Microsoft.Extensions.Logging.ILogger | Singleton |
Microsoft.Extensions.Logging.ILoggerFactory | Singleton |
Microsoft.Extensions.ObjectPool.ObjectPoolProvider | Singleton |
Microsoft.Extensions.Options.IConfigureOptions | Singleton |
Microsoft.Extensions.Options.IOptions | Singleton |
System.Diagnostics.DiagnosticListener | Singleton |
System.Diagnostics.DiagnosticSource | Singleton |
DI的生命周期
服务注册到DI系统中去之后,每个服务都是有生命周期的,生命周期代表框架什么时候创建这个服务,什么时候销毁它。
目前.Net
支持三种:
- Transient
- Scoped
- Singleton
Transient
在注册服务的时候选择Transient
范围,表示每次需要这个服务都会创建一个新的,例如在一个方法里多次向服务请求某个服务,那么就会多次创建这个服务的实例,使用方法AddTransient
对服务进行注册。
Scoped
在web应用中,Scoped
范围表示在一次客户端请求,都属于在一个scoped
的范围内置。在非Web
应用中,使用变量作用域来衡量scoped
生命周期比较合适。使用AddScoped
方法进行注册。
Singleton
代表从应用开始启动,到应用shutdown, 这个时间范围都只有一个实例服务,称为Singleton
范围。 使用AddSingleton
方法进行注册。
服务注册的方法:
我们前面学习过了通过什么方法来注册服务,同时我们还以用下表来表示注册的方法:
注册方法 | 是否自动释放 | 是否支持多种实现注册 | 是否支持参数传递 |
---|---|---|---|
Add{LifeTime}<{ServiceInterface}, {ServiceInterfaceImplement}> | 是 | 是 | 否 |
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION}) Example: services.AddSingleton(sp => new MyDep()); | 是 | 是 | 是 |
Add{LIFETIME}<{IMPLEMENTATION}>() | 是 | 否 | 否 |
AddSingleton<{SERVICE}>(new {IMPLEMENTATION}) | 否 | 是 | 是 |
AddSingleton(new {IMPLEMENTATION}) | 否 | 否 | 是 |
构造函数注入
构造函数注入需要注意:
- 用于注入的构造函数除了需要从容器中注入的参数之外,也可以有其他的参数,但是要求这个参数必须有默认值。
- 用于注入的构造函数必须是public的。
- 应用类中最好只有一个合适的构造函数。
注意
请注意如下的几点提示:
- 不要在Singleton范围内调用scoped或者transient范围的服务, 无论是直接调用还是间接调用。
- 可以从scoped或者tranisent范围里调用singleton。
- 可以从scoped或者tranisent范围里调用scoped。
服务清理
注册到服务容器中,并且由服务容器根据范围进行管理的,不用开发者来担心清理,由容器来负责清理的行为, 建议服务实现接口IDisposable
。
需要注意的是,在注册服务的时候,参考上述的表,如果服务不是由容器实例的,开发者需要自己清理,例如Singleton。
自动依赖服务最佳实践
async
、await
、以及基于Task的异步编程,在依赖注入的服务中不被支持,因为C#不支持异步构造函数,可以在同步方法中取得了注入的服务,然后在异步方法中使用该服务,这没有问题,只是服务本身不支持异步编程。- 避免直接在服务中存储数据或者更改配置。
- 避免在服务中使用静态方法。
- 保持创建服务的工厂方法快而且简单。
- 避免使用类似
GetService
来取得服务,而是通过DI - 启用范围验证。
ASP.net Core Startup类中依赖注入
需要注意的是在Startup
类中,只有构造函数和Configure
方法中可以注入需要依赖的服务, 同时构造函数只能注入如下几种类型:
- IWebHostEniroment
- IHostEnviroment
- IConfiguration
但是任何其他已经注册了的服务都可以注入到方法Configure
HttpContext.RequestService
在ASP.net core也可以通过HttpContext.RequestService
取得scoped 范围的服务,但是官方推荐还是尽量使用构造函数的参数来取得服务。
ASP.net Core MVC 中的Controller如何取得注册在容器里的服务
在ASP.net Core里由两种形式可以从DI容器中取得服务:
- 从构造函数中取得。
- 使用属性[FromService] 放入参数列表中,可以无需使用构造函数就可以取得服务了。
关于第二种,看一下这个例子:
public IActionResult About([FromServices] IDateTime dateTime) { return Content( $"Current server time: {dateTime.Now}"); }
从控制器中存取配置
不建议直接将IConfiguration
对象注入到Controller
中去,而是采取Options Pattern
模式。我们先看如下的例子,后面再用一节来学习什么是Options Pattern
先创建一个类表示配置选项:
public class SampleWebSettings { public string Title { get; set; } public int Updates { get; set; } }
添加这个配置类到服务里。
public void ConfigureServices(IServiceCollection services) { services.AddSingleton<IDateTime, SystemDateTime>(); services.Configure<SampleWebSettings>(Configuration); services.AddControllersWithViews(); }
配置应用从json格式里读取配置
public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureAppConfiguration((hostingContext, config) => { config.AddJsonFile("samplewebsettings.json", optional: false, reloadOnChange: true); }) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }); }
从控制器里存取配置
这里例子里我们通过IOptions<SampleWebSetting>
从依赖容器中取得配置。
public class SettingsController : Controller { private readonly SampleWebSettings _settings; public SettingsController(IOptions<SampleWebSettings> settingsOptions) { _settings = settingsOptions.Value; } public IActionResult Index() { ViewData["Title"] = _settings.Title; ViewData["Updates"] = _settings.Updates; return View(); } }
我们下一节来学习Options Pattern
, 依赖注入总结到这里就好了。