docs: Improved DI documentation (#2407)
This commit is contained in:
69
docs/guides/dependency_injection/basics.md
Normal file
69
docs/guides/dependency_injection/basics.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
---
|
||||||
|
uid: Guides.DI.Intro
|
||||||
|
title: Introduction
|
||||||
|
---
|
||||||
|
|
||||||
|
# Dependency Injection
|
||||||
|
|
||||||
|
Dependency injection is a feature not required in Discord.Net, but makes it a lot easier to use.
|
||||||
|
It can be combined with a large number of other libraries, and gives you better control over your application.
|
||||||
|
|
||||||
|
> Further into the documentation, Dependency Injection will be referred to as 'DI'.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
DI is not native to .NET. You need to install the extension packages to your project in order to use it:
|
||||||
|
|
||||||
|
- [Meta](https://www.nuget.org/packages/Microsoft.Extensions.DependencyInjection/).
|
||||||
|
- [Abstractions](https://www.nuget.org/packages/Microsoft.Extensions.DependencyInjection.Abstractions/).
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> Downloading the abstractions package alone will not give you access to required classes to use DI properly.
|
||||||
|
> Please install both packages, or choose to only install the meta package to implicitly install both.
|
||||||
|
|
||||||
|
### Visual Package Manager:
|
||||||
|
|
||||||
|
[Installing](images/manager.png)
|
||||||
|
|
||||||
|
### Command Line:
|
||||||
|
|
||||||
|
`PM> Install-Package Microsoft.Extensions.DependencyInjection`.
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> ASP.NET already comes packed with all the necessary assemblies in its framework.
|
||||||
|
> You do not require to install any additional NuGet packages to make full use of all features of DI in ASP.NET projects.
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
First of all, you will need to create an application based around dependency injection,
|
||||||
|
which in order will be able to access and inject them across the project.
|
||||||
|
|
||||||
|
[!code-csharp[Building the Program](samples/program.cs)]
|
||||||
|
|
||||||
|
In order to freely pass around your dependencies in different classes,
|
||||||
|
you will need to register them to a new `ServiceCollection` and build them into an `IServiceProvider` as seen above.
|
||||||
|
The IServiceProvider then needs to be accessible by the startup file, so you can access your provider and manage them.
|
||||||
|
|
||||||
|
[!code-csharp[Building the Collection](samples/collection.cs)]
|
||||||
|
|
||||||
|
As shown above, an instance of `DiscordSocketConfig` is created, and added **before** the client itself is.
|
||||||
|
Because the collection will prefer to create the highest populated constructor available with the services already present,
|
||||||
|
it will prefer the constructor with the configuration, because you already added it.
|
||||||
|
|
||||||
|
## Using your dependencies
|
||||||
|
|
||||||
|
After building your provider in the Program class constructor, the provider is now available inside the instance you're actively using.
|
||||||
|
Through the provider, we can ask for the DiscordSocketClient we registered earlier.
|
||||||
|
|
||||||
|
[!code-csharp[Applying DI in RunAsync](samples/runasync.cs)]
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> Service constructors are not activated until the service is **first requested**.
|
||||||
|
> An 'endpoint' service will have to be requested from the provider before it is activated.
|
||||||
|
> If a service is requested with dependencies, its dependencies (if not already active) will be activated before the service itself is.
|
||||||
|
|
||||||
|
## Injecting dependencies
|
||||||
|
|
||||||
|
You can not only directly access the provider from a field or property, but you can also pass around instances to classes registered in the provider.
|
||||||
|
There are multiple ways to do this. Please refer to the
|
||||||
|
[Injection Documentation](Guides.DI.Injection) for further information.
|
||||||
BIN
docs/guides/dependency_injection/images/manager.png
Normal file
BIN
docs/guides/dependency_injection/images/manager.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
44
docs/guides/dependency_injection/injection.md
Normal file
44
docs/guides/dependency_injection/injection.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
uid: Guides.DI.Injection
|
||||||
|
title: Injection
|
||||||
|
---
|
||||||
|
|
||||||
|
# Injecting instances within the provider
|
||||||
|
|
||||||
|
You can inject registered services into any class that is registered to the `IServiceProvider`.
|
||||||
|
This can be done through property or constructor.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> As mentioned above, the dependency *and* the target class have to be registered in order for the serviceprovider to resolve it.
|
||||||
|
|
||||||
|
## Injecting through a constructor
|
||||||
|
|
||||||
|
Services can be injected from the constructor of the class.
|
||||||
|
This is the preferred approach, because it automatically locks the readonly field in place with the provided service and isn't accessible outside of the class.
|
||||||
|
|
||||||
|
[!code-csharp[Property Injection(samples/property-injecting.cs)]]
|
||||||
|
|
||||||
|
## Injecting through properties
|
||||||
|
|
||||||
|
Injecting through properties is also allowed as follows.
|
||||||
|
|
||||||
|
[!code-csharp[Property Injection](samples/property-injecting.cs)]
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> Dependency Injection will not resolve missing services in property injection, and it will not pick a constructor instead.
|
||||||
|
> If a publically accessible property is attempted to be injected and its service is missing, the application will throw an error.
|
||||||
|
|
||||||
|
## Using the provider itself
|
||||||
|
|
||||||
|
You can also access the provider reference itself from injecting it into a class. There are multiple use cases for this:
|
||||||
|
|
||||||
|
- Allowing libraries (Like Discord.Net) to access your provider internally.
|
||||||
|
- Injecting optional dependencies.
|
||||||
|
- Calling methods on the provider itself if necessary, this is often done for creating scopes.
|
||||||
|
|
||||||
|
[!code-csharp[Provider Injection](samples/provider.cs)]
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> It is important to keep in mind that the provider will pick the 'biggest' available constructor.
|
||||||
|
> If you choose to introduce multiple constructors,
|
||||||
|
> keep in mind that services missing from one constructor may have the provider pick another one that *is* available instead of throwing an exception.
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
async Task RunAsync()
|
||||||
|
{
|
||||||
|
//...
|
||||||
|
|
||||||
|
await _serviceProvider.GetRequiredService<ServiceActivator>()
|
||||||
|
.ActivateAsync();
|
||||||
|
|
||||||
|
//...
|
||||||
|
}
|
||||||
13
docs/guides/dependency_injection/samples/collection.cs
Normal file
13
docs/guides/dependency_injection/samples/collection.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
static IServiceProvider CreateServices()
|
||||||
|
{
|
||||||
|
var config = new DiscordSocketConfig()
|
||||||
|
{
|
||||||
|
//...
|
||||||
|
};
|
||||||
|
|
||||||
|
var collection = new ServiceCollection()
|
||||||
|
.AddSingleton(config)
|
||||||
|
.AddSingleton<DiscordSocketClient>();
|
||||||
|
|
||||||
|
return collection.BuildServiceProvider();
|
||||||
|
}
|
||||||
14
docs/guides/dependency_injection/samples/ctor-injecting.cs
Normal file
14
docs/guides/dependency_injection/samples/ctor-injecting.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
public class ClientHandler
|
||||||
|
{
|
||||||
|
private readonly DiscordSocketClient _client;
|
||||||
|
|
||||||
|
public ClientHandler(DiscordSocketClient client)
|
||||||
|
{
|
||||||
|
_client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ConfigureAsync()
|
||||||
|
{
|
||||||
|
//...
|
||||||
|
}
|
||||||
|
}
|
||||||
18
docs/guides/dependency_injection/samples/enumeration.cs
Normal file
18
docs/guides/dependency_injection/samples/enumeration.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
public class ServiceActivator
|
||||||
|
{
|
||||||
|
// This contains *all* registered services of serviceType 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
public static ServiceCollection RegisterImplicitServices(this ServiceCollection collection, Type interfaceType, Type activatorType)
|
||||||
|
{
|
||||||
|
// Get all types in the executing assembly. There are many ways to do this, but this is fastest.
|
||||||
|
foreach (var type in typeof(Program).Assembly.GetTypes())
|
||||||
|
{
|
||||||
|
if (interfaceType.IsAssignableFrom(type) && !type.IsAbstract)
|
||||||
|
collection.AddSingleton(interfaceType, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the activator so you can activate the instances.
|
||||||
|
collection.AddSingleton(activatorType);
|
||||||
|
}
|
||||||
16
docs/guides/dependency_injection/samples/modules.cs
Normal file
16
docs/guides/dependency_injection/samples/modules.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
public class MyModule : InteractionModuleBase
|
||||||
|
{
|
||||||
|
private readonly MyService _service;
|
||||||
|
|
||||||
|
public MyModule(MyService service)
|
||||||
|
{
|
||||||
|
_service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SlashCommand("things", "Shows things")]
|
||||||
|
public async Task ThingsAsync()
|
||||||
|
{
|
||||||
|
var str = string.Join("\n", _service.Things)
|
||||||
|
await RespondAsync(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
24
docs/guides/dependency_injection/samples/program.cs
Normal file
24
docs/guides/dependency_injection/samples/program.cs
Normal file
@@ -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)
|
||||||
|
{
|
||||||
|
//...
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
public class ClientHandler
|
||||||
|
{
|
||||||
|
public DiscordSocketClient Client { get; set; }
|
||||||
|
|
||||||
|
public async Task ConfigureAsync()
|
||||||
|
{
|
||||||
|
//...
|
||||||
|
}
|
||||||
|
}
|
||||||
26
docs/guides/dependency_injection/samples/provider.cs
Normal file
26
docs/guides/dependency_injection/samples/provider.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
public class UtilizingProvider
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _provider;
|
||||||
|
private readonly AnyService _service;
|
||||||
|
|
||||||
|
// This service is allowed to be null because it is only populated if the service is actually available in the provider.
|
||||||
|
private readonly AnyOtherService? _otherService;
|
||||||
|
|
||||||
|
// This constructor injects only the service provider,
|
||||||
|
// and uses it to populate the other dependencies.
|
||||||
|
public UtilizingProvider(IServiceProvider provider)
|
||||||
|
{
|
||||||
|
_provider = provider;
|
||||||
|
_service = provider.GetRequiredService<AnyService>();
|
||||||
|
_otherService = provider.GetService<AnyOtherService>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This constructor injects the service provider, and AnyService,
|
||||||
|
// making sure that AnyService is not null without having to call GetRequiredService
|
||||||
|
public UtilizingProvider(IServiceProvider provider, AnyService service)
|
||||||
|
{
|
||||||
|
_provider = provider;
|
||||||
|
_service = service;
|
||||||
|
_otherService = provider.GetService<AnyOtherService>();
|
||||||
|
}
|
||||||
|
}
|
||||||
17
docs/guides/dependency_injection/samples/runasync.cs
Normal file
17
docs/guides/dependency_injection/samples/runasync.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
async Task RunAsync(string[] args)
|
||||||
|
{
|
||||||
|
// Request the instance from the client.
|
||||||
|
// Because we're requesting it here first, its targetted constructor will be called and we will receive an active instance.
|
||||||
|
var client = _services.GetRequiredService<DiscordSocketClient>();
|
||||||
|
|
||||||
|
client.Log += async (msg) =>
|
||||||
|
{
|
||||||
|
await Task.CompletedTask;
|
||||||
|
Console.WriteLine(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.LoginAsync(TokenType.Bot, "");
|
||||||
|
await client.StartAsync();
|
||||||
|
|
||||||
|
await Task.Delay(Timeout.Infinite);
|
||||||
|
}
|
||||||
6
docs/guides/dependency_injection/samples/scoped.cs
Normal file
6
docs/guides/dependency_injection/samples/scoped.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
// With serviceType:
|
||||||
|
collection.AddScoped<IScopedService, ScopedService>();
|
||||||
|
|
||||||
|
// Without serviceType:
|
||||||
|
collection.AddScoped<ScopedService>();
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
static IServiceProvider CreateServices()
|
||||||
|
{
|
||||||
|
var config = new DiscordSocketConfig()
|
||||||
|
{
|
||||||
|
//...
|
||||||
|
};
|
||||||
|
|
||||||
|
// X represents either Interaction or Command, as it functions the exact same for both types.
|
||||||
|
var servConfig = new XServiceConfig()
|
||||||
|
{
|
||||||
|
//...
|
||||||
|
}
|
||||||
|
|
||||||
|
var collection = new ServiceCollection()
|
||||||
|
.AddSingleton(config)
|
||||||
|
.AddSingleton<DiscordSocketClient>()
|
||||||
|
.AddSingleton(servConfig)
|
||||||
|
.AddSingleton<XService>();
|
||||||
|
|
||||||
|
return collection.BuildServiceProvider();
|
||||||
|
}
|
||||||
9
docs/guides/dependency_injection/samples/services.cs
Normal file
9
docs/guides/dependency_injection/samples/services.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
public class MyService
|
||||||
|
{
|
||||||
|
public List<string> Things { get; }
|
||||||
|
|
||||||
|
public MyService()
|
||||||
|
{
|
||||||
|
Things = new();
|
||||||
|
}
|
||||||
|
}
|
||||||
6
docs/guides/dependency_injection/samples/singleton.cs
Normal file
6
docs/guides/dependency_injection/samples/singleton.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
// With serviceType:
|
||||||
|
collection.AddSingleton<ISingletonService, SingletonService>();
|
||||||
|
|
||||||
|
// Without serviceType:
|
||||||
|
collection.AddSingleton<SingletonService>();
|
||||||
6
docs/guides/dependency_injection/samples/transient.cs
Normal file
6
docs/guides/dependency_injection/samples/transient.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
// With serviceType:
|
||||||
|
collection.AddTransient<ITransientService, TransientService>();
|
||||||
|
|
||||||
|
// Without serviceType:
|
||||||
|
collection.AddTransient<TransientService>();
|
||||||
39
docs/guides/dependency_injection/scaling.md
Normal file
39
docs/guides/dependency_injection/scaling.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
uid: Guides.DI.Scaling
|
||||||
|
title: Scaling your DI
|
||||||
|
---
|
||||||
|
|
||||||
|
# Scaling your DI
|
||||||
|
|
||||||
|
Dependency injection has a lot of use cases, and is very suitable for scaled applications.
|
||||||
|
There are a few ways to make registering & using services easier in large amounts.
|
||||||
|
|
||||||
|
## Using a range of services.
|
||||||
|
|
||||||
|
If you have a lot of services that all have the same use such as handling an event or serving a module,
|
||||||
|
you can register and inject them all at once by some requirements:
|
||||||
|
|
||||||
|
- All classes need to inherit a single interface or abstract type.
|
||||||
|
- While not required, it is preferred if the interface and types share a method to call on request.
|
||||||
|
- You need to register a class that all the types can be injected into.
|
||||||
|
|
||||||
|
### Registering implicitly
|
||||||
|
|
||||||
|
Registering all the types is done through getting all types in the assembly and checking if they inherit the target interface.
|
||||||
|
|
||||||
|
[!code-csharp[Registering](samples/implicit-registration.cs)]
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> As seen above, the interfaceType and activatorType are undefined. For our usecase below, these are `IService` and `ServiceActivator` in order.
|
||||||
|
|
||||||
|
### Using implicit dependencies
|
||||||
|
|
||||||
|
In order to use the implicit dependencies, you have to get access to the activator you registered earlier.
|
||||||
|
|
||||||
|
[!code-csharp[Accessing the activator](samples/access-activator.cs)]
|
||||||
|
|
||||||
|
When the activator is accessed and the `ActivateAsync()` method is called, the following code will be executed:
|
||||||
|
|
||||||
|
[!code-csharp[Executing the activator](samples/enumeration.cs)]
|
||||||
|
|
||||||
|
As a result of this, all the services that were registered with `IService` as its implementation type will execute their starting code, and start up.
|
||||||
48
docs/guides/dependency_injection/services.md
Normal file
48
docs/guides/dependency_injection/services.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
uid: Guides.DI.Services
|
||||||
|
title: Using DI in Interaction & Command Frameworks
|
||||||
|
---
|
||||||
|
|
||||||
|
# DI in the Interaction- & Command Service
|
||||||
|
|
||||||
|
For both the Interaction- and Command Service modules, DI is quite straight-forward to use.
|
||||||
|
|
||||||
|
You can inject any service into modules without the modules having to be registered to the provider.
|
||||||
|
Discord.Net resolves your dependencies internally.
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> The way DI is used in the Interaction- & Command Service are nearly identical, except for one detail:
|
||||||
|
> [Resolving Module Dependencies](xref:Guides.IntFw.Intro#resolving-module-dependencies)
|
||||||
|
|
||||||
|
## Registering the Service
|
||||||
|
|
||||||
|
Thanks to earlier described behavior of allowing already registered members as parameters of the available ctors,
|
||||||
|
The socket client & configuration will automatically be acknowledged and the XService(client, config) overload will be used.
|
||||||
|
|
||||||
|
[!code-csharp[Service Registration](samples/service-registration.cs)]
|
||||||
|
|
||||||
|
## Usage in modules
|
||||||
|
|
||||||
|
In the constructor of your module, any parameters will be filled in by
|
||||||
|
the @System.IServiceProvider that you've passed.
|
||||||
|
|
||||||
|
Any publicly settable properties will also be filled in the same
|
||||||
|
manner.
|
||||||
|
|
||||||
|
[!code-csharp[Module Injection](samples/modules.cs)]
|
||||||
|
|
||||||
|
If you accept `Command/InteractionService` or `IServiceProvider` as a parameter in your constructor or as an injectable property,
|
||||||
|
these entries will be filled by the `Command/InteractionService` that the module is loaded from and the `IServiceProvider` that is passed into it respectively.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Annotating a property with a [DontInjectAttribute] attribute will
|
||||||
|
> prevent the property from being injected.
|
||||||
|
|
||||||
|
## Services
|
||||||
|
|
||||||
|
Because modules are transient of nature and will reinstantiate on every request,
|
||||||
|
it is suggested to create a singleton service behind it to hold values across multiple command executions.
|
||||||
|
|
||||||
|
[!code-csharp[Services](samples/services.cs)]
|
||||||
|
|
||||||
|
|
||||||
52
docs/guides/dependency_injection/types.md
Normal file
52
docs/guides/dependency_injection/types.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
uid: Guides.DI.Dependencies
|
||||||
|
title: Types of Dependencies
|
||||||
|
---
|
||||||
|
|
||||||
|
# Dependency Types
|
||||||
|
|
||||||
|
There are 3 types of dependencies to learn to use. Several different usecases apply for each.
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> When registering types with a serviceType & implementationType,
|
||||||
|
> only the serviceType will be available for injection, and the implementationType will be used for the underlying instance.
|
||||||
|
|
||||||
|
## Singleton
|
||||||
|
|
||||||
|
A singleton service creates a single instance when first requested, and maintains that instance across the lifetime of the application.
|
||||||
|
Any values that are changed within a singleton will be changed across all instances that depend on it, as they all have the same reference to it.
|
||||||
|
|
||||||
|
### Registration:
|
||||||
|
|
||||||
|
[!code-csharp[Singleton Example](samples/singleton.cs)]
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Types like the Discord client and Interaction/Command services are intended to be singleton,
|
||||||
|
> as they should last across the entire app and share their state with all references to the object.
|
||||||
|
|
||||||
|
## Scoped
|
||||||
|
|
||||||
|
A scoped service creates a new instance every time a new service is requested, but is kept across the 'scope'.
|
||||||
|
As long as the service is in view for the created scope, the same instance is used for all references to the type.
|
||||||
|
This means that you can reuse the same instance during execution, and keep the services' state for as long as the request is active.
|
||||||
|
|
||||||
|
### Registration:
|
||||||
|
|
||||||
|
[!code-csharp[Scoped Example](samples/scoped.cs)]
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Without using HTTP or libraries like EFCORE, scopes are often unused in Discord bots.
|
||||||
|
> They are most commonly used for handling HTTP and database requests.
|
||||||
|
|
||||||
|
## Transient
|
||||||
|
|
||||||
|
A transient service is created every time it is requested, and does not share its state between references within the target service.
|
||||||
|
It is intended for lightweight types that require little state, to be disposed quickly after execution.
|
||||||
|
|
||||||
|
### Registration:
|
||||||
|
|
||||||
|
[!code-csharp[Transient Example](samples/transient.cs)]
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Discord.Net modules behave exactly as transient types, and are intended to only last as long as the command execution takes.
|
||||||
|
> This is why it is suggested for apps to use singleton services to keep track of cross-execution data.
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
---
|
|
||||||
uid: Guides.IntFw.DI
|
|
||||||
title: Dependency Injection
|
|
||||||
---
|
|
||||||
|
|
||||||
# Dependency Injection
|
|
||||||
|
|
||||||
Dependency injection in the Interaction Service is mostly based on that of the Text-based command service,
|
|
||||||
for which further information is found [here](xref:Guides.TextCommands.DI).
|
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> The 2 are nearly identical, except for one detail:
|
|
||||||
> [Resolving Module Dependencies](xref:Guides.IntFw.Intro#resolving-module-dependencies)
|
|
||||||
@@ -374,8 +374,7 @@ delegate can be used to create HTTP responses from a deserialized json object st
|
|||||||
- Use the interaction endpoints of the module base instead of the interaction object (ie. `RespondAsync()`, `FollowupAsync()`...).
|
- Use the interaction endpoints of the module base instead of the interaction object (ie. `RespondAsync()`, `FollowupAsync()`...).
|
||||||
|
|
||||||
[AutocompleteHandlers]: xref:Guides.IntFw.AutoCompletion
|
[AutocompleteHandlers]: xref:Guides.IntFw.AutoCompletion
|
||||||
[DependencyInjection]: xref:Guides.TextCommands.DI
|
[DependencyInjection]: xref:Guides.DI.Intro
|
||||||
[Post Execution Docuemntation]: xref:Guides.IntFw.PostExecution
|
|
||||||
|
|
||||||
[GroupAttribute]: xref:Discord.Interactions.GroupAttribute
|
[GroupAttribute]: xref:Discord.Interactions.GroupAttribute
|
||||||
[InteractionService]: xref:Discord.Interactions.InteractionService
|
[InteractionService]: xref:Discord.Interactions.InteractionService
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
---
|
|
||||||
uid: Guides.TextCommands.DI
|
|
||||||
title: Dependency Injection
|
|
||||||
---
|
|
||||||
|
|
||||||
# Dependency Injection
|
|
||||||
|
|
||||||
The Text Command Service is bundled with a very barebone Dependency
|
|
||||||
Injection service for your convenience. It is recommended that you use
|
|
||||||
DI when writing your modules.
|
|
||||||
|
|
||||||
> [!WARNING]
|
|
||||||
> If you were brought here from the Interaction Service guides,
|
|
||||||
> make sure to replace all namespaces that imply `Discord.Commands` with `Discord.Interactions`
|
|
||||||
|
|
||||||
## Setup
|
|
||||||
|
|
||||||
1. Create a @Microsoft.Extensions.DependencyInjection.ServiceCollection.
|
|
||||||
2. Add the dependencies to the service collection that you wish
|
|
||||||
to use in the modules.
|
|
||||||
3. Build the service collection into a service provider.
|
|
||||||
4. Pass the service collection into @Discord.Commands.CommandService.AddModulesAsync* / @Discord.Commands.CommandService.AddModuleAsync* , @Discord.Commands.CommandService.ExecuteAsync* .
|
|
||||||
|
|
||||||
### Example - Setting up Injection
|
|
||||||
|
|
||||||
[!code-csharp[IServiceProvider Setup](samples/dependency-injection/dependency_map_setup.cs)]
|
|
||||||
|
|
||||||
## Usage in Modules
|
|
||||||
|
|
||||||
In the constructor of your module, any parameters will be filled in by
|
|
||||||
the @System.IServiceProvider that you've passed.
|
|
||||||
|
|
||||||
Any publicly settable properties will also be filled in the same
|
|
||||||
manner.
|
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> Annotating a property with a [DontInjectAttribute] attribute will
|
|
||||||
> prevent the property from being injected.
|
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> If you accept `CommandService` or `IServiceProvider` as a parameter
|
|
||||||
> in your constructor or as an injectable property, these entries will
|
|
||||||
> be filled by the `CommandService` that the module is loaded from and
|
|
||||||
> the `IServiceProvider` that is passed into it respectively.
|
|
||||||
|
|
||||||
### Example - Injection in Modules
|
|
||||||
|
|
||||||
[!code-csharp[Injection Modules](samples/dependency-injection/dependency_module.cs)]
|
|
||||||
[!code-csharp[Disallow Dependency Injection](samples/dependency-injection/dependency_module_noinject.cs)]
|
|
||||||
|
|
||||||
[DontInjectAttribute]: xref:Discord.Commands.DontInjectAttribute
|
|
||||||
@@ -187,7 +187,7 @@ service provider.
|
|||||||
|
|
||||||
### Module Constructors
|
### Module Constructors
|
||||||
|
|
||||||
Modules are constructed using [Dependency Injection](xref:Guides.TextCommands.DI). Any parameters
|
Modules are constructed using [Dependency Injection](xref:Guides.DI.Intro). Any parameters
|
||||||
that are placed in the Module's constructor must be injected into an
|
that are placed in the Module's constructor must be injected into an
|
||||||
@System.IServiceProvider first.
|
@System.IServiceProvider first.
|
||||||
|
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
public class Initialize
|
|
||||||
{
|
|
||||||
private readonly CommandService _commands;
|
|
||||||
private readonly DiscordSocketClient _client;
|
|
||||||
|
|
||||||
// Ask if there are existing CommandService and DiscordSocketClient
|
|
||||||
// instance. If there are, we retrieve them and add them to the
|
|
||||||
// DI container; if not, we create our own.
|
|
||||||
public Initialize(CommandService commands = null, DiscordSocketClient client = null)
|
|
||||||
{
|
|
||||||
_commands = commands ?? new CommandService();
|
|
||||||
_client = client ?? new DiscordSocketClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IServiceProvider BuildServiceProvider() => new ServiceCollection()
|
|
||||||
.AddSingleton(_client)
|
|
||||||
.AddSingleton(_commands)
|
|
||||||
// You can pass in an instance of the desired type
|
|
||||||
.AddSingleton(new NotificationService())
|
|
||||||
// ...or by using the generic method.
|
|
||||||
//
|
|
||||||
// The benefit of using the generic method is that
|
|
||||||
// ASP.NET DI will attempt to inject the required
|
|
||||||
// dependencies that are specified under the constructor
|
|
||||||
// for us.
|
|
||||||
.AddSingleton<DatabaseService>()
|
|
||||||
.AddSingleton<CommandHandler>()
|
|
||||||
.BuildServiceProvider();
|
|
||||||
}
|
|
||||||
public class CommandHandler
|
|
||||||
{
|
|
||||||
private readonly DiscordSocketClient _client;
|
|
||||||
private readonly CommandService _commands;
|
|
||||||
private readonly IServiceProvider _services;
|
|
||||||
|
|
||||||
public CommandHandler(IServiceProvider services, CommandService commands, DiscordSocketClient client)
|
|
||||||
{
|
|
||||||
_commands = commands;
|
|
||||||
_services = services;
|
|
||||||
_client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task InitializeAsync()
|
|
||||||
{
|
|
||||||
// Pass the service provider to the second parameter of
|
|
||||||
// AddModulesAsync to inject dependencies to all modules
|
|
||||||
// that may require them.
|
|
||||||
await _commands.AddModulesAsync(
|
|
||||||
assembly: Assembly.GetEntryAssembly(),
|
|
||||||
services: _services);
|
|
||||||
_client.MessageReceived += HandleCommandAsync;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task HandleCommandAsync(SocketMessage msg)
|
|
||||||
{
|
|
||||||
// ...
|
|
||||||
// Pass the service provider to the ExecuteAsync method for
|
|
||||||
// precondition checks.
|
|
||||||
await _commands.ExecuteAsync(
|
|
||||||
context: context,
|
|
||||||
argPos: argPos,
|
|
||||||
services: _services);
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
// After setting up dependency injection, modules will need to request
|
|
||||||
// the dependencies to let the library know to pass
|
|
||||||
// them along during execution.
|
|
||||||
|
|
||||||
// Dependency can be injected in two ways with Discord.Net.
|
|
||||||
// You may inject any required dependencies via...
|
|
||||||
// the module constructor
|
|
||||||
// -or-
|
|
||||||
// public settable properties
|
|
||||||
|
|
||||||
// Injection via constructor
|
|
||||||
public class DatabaseModule : ModuleBase<SocketCommandContext>
|
|
||||||
{
|
|
||||||
private readonly DatabaseService _database;
|
|
||||||
public DatabaseModule(DatabaseService database)
|
|
||||||
{
|
|
||||||
_database = database;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Command("read")]
|
|
||||||
public async Task ReadFromDbAsync()
|
|
||||||
{
|
|
||||||
await ReplyAsync(_database.GetData());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Injection via public settable properties
|
|
||||||
public class DatabaseModule : ModuleBase<SocketCommandContext>
|
|
||||||
{
|
|
||||||
public DatabaseService DbService { get; set; }
|
|
||||||
|
|
||||||
[Command("read")]
|
|
||||||
public async Task ReadFromDbAsync()
|
|
||||||
{
|
|
||||||
await ReplyAsync(DbService.GetData());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
// Sometimes injecting dependencies automatically with the provided
|
|
||||||
// methods in the prior example may not be desired.
|
|
||||||
|
|
||||||
// You may explicitly tell Discord.Net to **not** inject the properties
|
|
||||||
// by either...
|
|
||||||
// restricting the access modifier
|
|
||||||
// -or-
|
|
||||||
// applying DontInjectAttribute to the property
|
|
||||||
|
|
||||||
// Restricting the access modifier of the property
|
|
||||||
public class ImageModule : ModuleBase<SocketCommandContext>
|
|
||||||
{
|
|
||||||
public ImageService ImageService { get; }
|
|
||||||
public ImageModule()
|
|
||||||
{
|
|
||||||
ImageService = new ImageService();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Applying DontInjectAttribute
|
|
||||||
public class ImageModule : ModuleBase<SocketCommandContext>
|
|
||||||
{
|
|
||||||
[DontInject]
|
|
||||||
public ImageService ImageService { get; set; }
|
|
||||||
public ImageModule()
|
|
||||||
{
|
|
||||||
ImageService = new ImageService();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user