Dependency Injection in Commands System
Wolfringo is built entirely with Dependency Injection in mind, and Commands System is no exception. In fact, Commands System makes even more use of it than the rest of Wolfringo library.
Dependency Injection is a mechanism for sharing services and other dependencies with other classes by providing (injecting) these dependencies into them. Dependency Injection is the recommended way to share instances of classes (such as instance of current IWolfClient, your database class, or any other service your bot needs to work). Using static variables/properties would work as well, but it would greatly reduce maintainability, testability and quality of the project.
In Wolfringo Commands System, Handlers constructors, commands methods, internal commands instances, and even Commands Requirements all support injection of services via Dependency Injection.
Handler Constructor Dependency Injection
To inject dependencies into handler constructor, simply specify them as its parameters.
private readonly IWolfClient _client;
private readonly IMySuperDatabase _database;
public ExampleCommandsHandler(IWolfClient client, IMySuperDatabase database)
{
this._client = client;
this._database = database;
}
If Commands System cannot resolve required dependencies for any of the available constructors, it'll log error and commands in that handler won't work. Check Registering Services to check how to add services to DI - for example your custom IMySuperDatabase.
You can also mark services as optional by giving the parameter a default value. Commands Service will still try to resolve the dependency, but if it fails to do so, it'll simply use the default value instead of throwing errors.
Command Method Dependency Injection
Command methods support dependency injection in a similar manner as Handler Constructors - simply specify them as the parameters.
[Command("example")]
private async Task ExampleAsync(ICommandContext context, IWolfClient client, IMySuperDatabase database)
{
// do something with client and database here!
}
If Commands System cannot resolve a required dependency of any of the parameters, it'll log an error and abort executing the command. Check Registering Services to check how to add services to DI - for example your custom IMySuperDatabase.
You can also mark services as optional by giving the parameter a default value. Commands Service will still try to resolve the dependency, but if it fails to do so, it'll simply use the default value instead of throwing errors.
Command Requirement Dependency Injection
Command Requirement's CheckAsync method has IServiceProvider as one of its parameters. Whenever CommandsService runs checks, its services will be provided via this parameter. You can use it to gain access to your own services - for example your custom IMySuperDatabase.
See Creating Custom Command Requirements guide for more information.
Registering Services
Registration of Dependency Injection services varies slightly depending whether you use Wolfringo.Hosting or not.
Since Wolfringo v2.0, adding services is a matter of calling WithSingletonService
, WithScopedService
or WithTransientService
on CommandsServiceBuilder. See Services Lifetime to understand the difference between these methods.
// in your MainAsync method:
_client = new WolfClientBuilder()
.WithCommands(commands =>
{
commands.WithSingletonService<IMySuperDatabase, MySuperDatabase>();
})
.Build();
You can add as many custom services as you want, as long as their resolving types (in example above - IMySuperDatabase) are different.
Note
Note: services registered with CommandsServiceBuilder are NOT available to WolfClient, which might be important if you're customizing Wolfringo.
WolfClientBuilder also has a WithService
method. All services added with this method will be available to both WolfClient and Commands (unless they're created separately), however they will always be registered with Singleton lifetime.
Warning
Please note that both CommandsServiceBuilder and WolfClientBuilder build their IServiceProvider separately. That means both will get DIFFERENT copies of the same service.
Note: this is not applicable to Wolfringo.Hosting - in that scenario, both WolfClient and CommandsService use the same, host-provided IServiceProvider.
Services Lifetime
Services can have following lifetimes:
- Singleton (
AddSingletonService<T>()
) - this service is created once, and kept alive until application exits. - Scoped (
AddScopedService<T>()
) - this service is created once for a received message. Each new message will get a new copy of this service. - Scoped (
AddTransientService<T>()
) - this service is created once per injection. If the service is used in constructor, method, requirement, or any combination of them, each will receive a new copy.
It is likely that you'll mostly use singleton services.