Skip to content

Commit

Permalink
Optimized DI doc
Browse files Browse the repository at this point in the history
  • Loading branch information
gehongyan committed Aug 27, 2023
1 parent 87199d4 commit c18bf19
Show file tree
Hide file tree
Showing 29 changed files with 453 additions and 172 deletions.
70 changes: 70 additions & 0 deletions docs/guides/dependency_injection/basics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
uid: Guides.DI.Intro
title: 概述
---

# 依赖注入

依赖注入并不是 Discord.Net 中必要的功能,但它可以让框架的使用更为简便。与许多其他库相结合使用,可以更好地控制应用程序。

## 按照

依赖注入不是 .NET 的原生功能。您需要将扩展包安装到项目中才能使用它:

- [Microsoft.Extensions.DependencyInjection](https://www.nuget.org/packages/Microsoft.Extensions.DependencyInjection/)
- [Microsoft.Extensions.DependencyInjection.Abstractions](https://www.nuget.org/packages/Microsoft.Extensions.DependencyInjection.Abstractions/)

> [!WARNING]
> 仅下载 Abstractions 包不包含使用依赖注入所需的一些必要类,请确保同时安装两个包,或选择仅安装
> Microsoft.Extensions.DependencyInjection 包来隐式同时安装两个包。
### Visual Studio

![Installing](images/visualstudio.png)

- 程序包管理控制台

`PM> Install-Package Microsoft.Extensions.DependencyInjection`.

### Rider

![Installing](images/rider.png)

### 命令行

```sh
dotnet add package Microsoft.Extensions.DependencyInjection
```

> [!TIP]
> ASP.NET 已经在其框架中包含了所有必要的程序集,要在 ASP.NET 中使用依赖注入完整功能,不需要安装任何额外的 NuGet 包。
## 开始

首先,您需要创建一个基于依赖注入的应用程序,这样就可以在整个项目中访问并注入它们。

[!code-csharp[Building the Program](samples/program.cs)]

要在不同的类中自由传递依赖项,您需要将它们注册到新的 `ServiceCollection` 中,并将它们构建到 `IServiceProvider` 中,如上所示。
然后需要通过启动文件访问 IServiceProvider,以便您可以访问您的提供程序并对其进行管理。

[!code-csharp[Building the Collection](samples/collection.cs)]

如上所示,这里创建了一个 `KookSocketConfig` 的实例,并在添加客户端本身**之前**将其添加到了服务容器中。
服务容器会优先选择已经存在的服务来填充构造函数,且你已经将配置类注册到服务容器中,因此,带有配置参数的构造函数重载会被优先调用。

## 使用依赖项

在 Program 类的构造函数中构建服务容器提供程序后,现在可以在您正在使用的实例中使用它了。
通过提供程序,我们可以请求我们之前所注册的 KookSocketClient。

[!code-csharp[Applying DI in RunAsync](samples/runasync.cs)]

> [!WARNING]
> 服务实例的构造函数在被**首次请求**之前不会被调用。要想实例化服务容器里的服务,需要先从提供程序中请求服务。
> 如果所请求的服务有依赖项,那么在实例化服务本身之前,其未被实例化的依赖项会被实例化。
## 注入依赖项

您不仅可以直接从字段或属性中访问提供程序,还可以将实例传递给在提供程序中注册的类。有多种方法可以实现这一点。请参考
[注入实例](xref:Guides.DI.Injection) 了解更多信息。
Binary file added docs/guides/dependency_injection/images/rider.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 42 additions & 0 deletions docs/guides/dependency_injection/injection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
uid: Guides.DI.Injection
title: 注入实例
---

# 注入实例

`IServiceProvider` 中注册的服务后可以注入到任何注册的类中,这可以通过属性或构造函数来实现。

> [!NOTE]
> 如上所述,依赖项**目标类必须都进行注册,服务提供程序才可以解析它。
## 通过构造函数注入

服务可以从类的构造函数中注入。
这是首选的方法,因为这可以将只读字段与提供的服务关联在一起,且在类的外部不可访问。

[!code-csharp[Constructor Injection](samples/ctor-injecting.cs)]

## 通过属性注入

也可以通过属性进行注入。

[!code-csharp[Property Injection](samples/property-injecting.cs)]

> [!WARNING]
> 依赖项注入不会解析属性注入中缺少的服务,也不会选择构造函数作为替代。如果尝试注入公共可访问属性时其服务缺失,应用程序将抛出错误。

## 使用提供程序本身

也可以将提供程序本身注入到类中,可用于以下多种用例:

- 允许库(如 Kook.Net)在内部访问提供程序
- 注入可选依赖项
- 如有必要,可以直接在提供程序上调用方法,这通常用于创建作用域

[!code-csharp[Provider Injection](samples/provider.cs)]

> [!NOTE]
> 请记住,提供程序将选择“最大”的可用构造函数。
> 如果选择引入多个构造函数,请记住,如在某一个构造函数中缺失了某项服务,提供程序可能会选择另一个可用的构造函数,而不是抛出异常。
9 changes: 9 additions & 0 deletions docs/guides/dependency_injection/samples/access-activator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
async Task RunAsync()
{
//...

await _serviceProvider.GetRequiredService<ServiceActivator>()
.ActivateAsync();

//...
}
13 changes: 13 additions & 0 deletions docs/guides/dependency_injection/samples/collection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
static IServiceProvider CreateServices()
{
var config = new KookSocketConfig()
{
//...
};

var collection = new ServiceCollection()
.AddSingleton(config)
.AddSingleton<KookSocketClient>();

return collection.BuildServiceProvider();
}
14 changes: 14 additions & 0 deletions docs/guides/dependency_injection/samples/ctor-injecting.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
public class ClientHandler
{
private readonly KookSocketClient _client;

public ClientHandler(KookSocketClient client)
{
_client = client;
}

public async Task ConfigureAsync()
{
//...
}
}
18 changes: 18 additions & 0 deletions docs/guides/dependency_injection/samples/enumeration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
public class ServiceActivator
{
// 这包含了所有已注册的服务类型为 IService 的服务
private readonly IEnumerable<IService> _services;

public ServiceActivator(IEnumerable<IService> services)
{
_services = services;
}

public async Task ActivateAsync()
{
foreach(var service in _services)
{
await service.StartAsync();
}
}
}
12 changes: 12 additions & 0 deletions docs/guides/dependency_injection/samples/implicit-registration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
public static ServiceCollection RegisterImplicitServices(this ServiceCollection collection, Type interfaceType, Type activatorType)
{
// 获取当前程序集中的所有类型。有很多方法可以做到这一点,但这是最快的。
foreach (var type in typeof(Program).Assembly.GetTypes())
{
if (interfaceType.IsAssignableFrom(type) && !type.IsAbstract)
collection.AddSingleton(interfaceType, type);
}

// 注册可以激活这些实例的类,以便您可以激活这些实例。
collection.AddSingleton(activatorType);
}
16 changes: 16 additions & 0 deletions docs/guides/dependency_injection/samples/modules.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
public class MyModule : InteractionModuleBase
{
private readonly MyService _service;

public MyModule(MyService service)
{
_service = service;
}

[Command("things")]
public async Task ThingsAsync()
{
var str = string.Join("\n", _service.Things)
await ReplyTextAsync(str);
}
}
24 changes: 24 additions & 0 deletions docs/guides/dependency_injection/samples/program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
public class Program
{
private readonly IServiceProvider _serviceProvider;

public Program()
{
_serviceProvider = CreateProvider();
}

static void Main(string[] args)
=> new Program().RunAsync(args).GetAwaiter().GetResult();

static IServiceProvider CreateProvider()
{
var collection = new ServiceCollection();
//...
return collection.BuildServiceProvider();
}

async Task RunAsync(string[] args)
{
//...
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
public class ClientHandler
{
public KookSocketClient Client { get; set; }

public async Task ConfigureAsync()
{
//...
}
}
24 changes: 24 additions & 0 deletions docs/guides/dependency_injection/samples/provider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
public class UtilizingProvider
{
private readonly IServiceProvider _provider;
private readonly AnyService _service;

// 服务可为 null,因为它只有在提供程序中实际可用时才会填充
private readonly AnyOtherService? _otherService;

// 该构造函数仅注入服务提供程序,并使用它来填充其他依赖项
public UtilizingProvider(IServiceProvider provider)
{
_provider = provider;
_service = provider.GetRequiredService<AnyService>();
_otherService = provider.GetService<AnyOtherService>();
}

// 该构造函数注入服务提供程序和 AnyService,这样无需调用 GetRequiredService页可以确保 AnyService 不为 null
public UtilizingProvider(IServiceProvider provider, AnyService service)
{
_provider = provider;
_service = service;
_otherService = provider.GetService<AnyOtherService>();
}
}
17 changes: 17 additions & 0 deletions docs/guides/dependency_injection/samples/runasync.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
async Task RunAsync(string[] args)
{
// 从客户端请求实例。
// 我们在这里首先请求它,因此其目标构造函数将会调用,我们会得到一个客户端的实例。
var client = _services.GetRequiredService<KookSocketClient>();

client.Log += async (msg) =>
{
await Task.CompletedTask;
Console.WriteLine(msg);
}

await client.LoginAsync(TokenType.Bot, "");
await client.StartAsync();

await Task.Delay(Timeout.Infinite);
}
5 changes: 5 additions & 0 deletions docs/guides/dependency_injection/samples/scoped.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// 带有服务接口类型
collection.AddScoped<IScopedService, ScopedService>();

// 不带有服务接口类型
collection.AddScoped<ScopedService>();
20 changes: 20 additions & 0 deletions docs/guides/dependency_injection/samples/service-registration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
static IServiceProvider CreateServices()
{
var config = new KookSocketConfig()
{
//...
};

var servConfig = new CommandServiceConfig()
{
//...
}

var collection = new ServiceCollection()
.AddSingleton(config)
.AddSingleton<KookSocketClient>()
.AddSingleton(servConfig)
.AddSingleton<CommandService>();

return collection.BuildServiceProvider();
}
9 changes: 9 additions & 0 deletions docs/guides/dependency_injection/samples/services.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
public class MyService
{
public List<string> Things { get; }

public MyService()
{
Things = new();
}
}
5 changes: 5 additions & 0 deletions docs/guides/dependency_injection/samples/singleton.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// 带有服务接口类型
collection.AddSingleton<ISingletonService, SingletonService>();

// 不带有服务接口类型
collection.AddSingleton<SingletonService>();
5 changes: 5 additions & 0 deletions docs/guides/dependency_injection/samples/transient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// 带有服务接口类型
collection.AddTransient<ITransientService, TransientService>();

// 不带有服务接口类型
collection.AddTransient<TransientService>();
37 changes: 37 additions & 0 deletions docs/guides/dependency_injection/scaling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
uid: Guides.DI.Scaling
title: 隐式注入
---

# 隐式注入

依赖注入有很多用例,并且非常适合大规模应用程序。有几种方法可以使大量服务的注册和使用更加容易。

## 使用一系列服务

如果您有许多服务都具有相同的用途,例如处理事件或服务模块,您可以通过一些要求一次注册和注入它们:

- 所有类都需要继承单个接口或抽象类型
- 虽然不是必需的,但最好是接口和类型在调用时具有相同的方法签名
- 您需要注册一个所有类型都可以注入的类

### 隐式注册

通过获取程序集中的所有类型,并检查它们是否实现了指定接口,来进行服务的注册。

[!code-csharp[Registering](samples/implicit-registration.cs)]

> [!NOTE]
> 如上所示,interfaceType 和 activatorType 未定义。对于下面的用例,这些是 `IService``ServiceActivator`
### 使用隐式依赖

为了使用隐式依赖,您必须访问您之前注册的激活器类。

[!code-csharp[Accessing the activator](samples/access-activator.cs)]

当访问并调用激活器类的 `ActivateAsync()` 方法时,将执行以下代码:

[!code-csharp[Executing the activator](samples/enumeration.cs)]

至此,所有通过实现 `IService` 接口被注册的类上的自动代码都会被执行并启动。
Loading

0 comments on commit c18bf19

Please sign in to comment.