Skip to main content

依赖服务注入(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.IServiceScopeFactorySingleton
IHostApplicationLifetimeSingleton
Microsoft.Extensions.Logging.ILoggerSingleton
Microsoft.Extensions.Logging.ILoggerFactorySingleton
Microsoft.Extensions.ObjectPool.ObjectPoolProviderSingleton
Microsoft.Extensions.Options.IConfigureOptionsSingleton
Microsoft.Extensions.Options.IOptionsSingleton
System.Diagnostics.DiagnosticListenerSingleton
System.Diagnostics.DiagnosticSourceSingleton

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。

自动依赖服务最佳实践

  • asyncawait、以及基于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, 依赖注入总结到这里就好了。