+ Fix various formatting. + Provide a more detailed walkthrough for dependency injection. + Add C# note at intro.pull/1161/head
@@ -1,27 +1,28 @@ | |||||
public class MyService | public class MyService | ||||
{ | { | ||||
public string MyCoolString {get; set;} | |||||
public string MyCoolString { get; set; } | |||||
} | } | ||||
public class Setup | public class Setup | ||||
{ | { | ||||
public IServiceProvider BuildProvider() => | public IServiceProvider BuildProvider() => | ||||
new ServiceCollection() | |||||
.AddSingleton<MyService>() | |||||
.BuildServiceProvider(); | |||||
new ServiceCollection() | |||||
.AddSingleton<MyService>() | |||||
.BuildServiceProvider(); | |||||
} | } | ||||
public class MyModule : ModuleBase<SocketCommandContext> | public class MyModule : ModuleBase<SocketCommandContext> | ||||
{ | { | ||||
// Inject via public settable prop | // Inject via public settable prop | ||||
public MyService MyService {get; set;} | |||||
public MyService MyService { get; set; } | |||||
// or via ctor | |||||
private readonly MyService _myService; | |||||
public MyModule (MyService myService) => _myService = myService; | |||||
// ...or via the module's constructor | |||||
// private readonly MyService _myService; | |||||
// public MyModule (MyService myService) => _myService = myService; | |||||
[Command("string")] | [Command("string")] | ||||
public Task GetOrSetStringAsync(string input) | public Task GetOrSetStringAsync(string input) | ||||
{ | { | ||||
if (_myService.MyCoolString == null) _myService.MyCoolString = input; | |||||
if (string.IsNullOrEmpty(_myService.MyCoolString)) _myService.MyCoolString = input; | |||||
return ReplyAsync(_myService.MyCoolString); | return ReplyAsync(_myService.MyCoolString); | ||||
} | } | ||||
} | } |
@@ -1,9 +1,10 @@ | |||||
public class Setup | public class Setup | ||||
{ | { | ||||
private readonly CommandService _command; | private readonly CommandService _command; | ||||
public Setup() | public Setup() | ||||
{ | { | ||||
var config = new CommandServiceConfig{DefaultRunMode = RunMode.Async}; | |||||
var config = new CommandServiceConfig{ DefaultRunMode = RunMode.Async }; | |||||
_command = new CommandService(config); | _command = new CommandService(config); | ||||
} | } | ||||
} | } |
@@ -11,10 +11,11 @@ DI when writing your modules. | |||||
## Setup | ## Setup | ||||
1. Create an @System.IServiceProvider. | |||||
1. Create a @Microsoft.Extensions.DependencyInjection.ServiceCollection. | |||||
2. Add the dependencies to the service collection that you wish | 2. Add the dependencies to the service collection that you wish | ||||
to use in the modules. | to use in the modules. | ||||
3. Pass the service collection into `AddModulesAsync`. | |||||
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 | ### Example - Setting up Injection | ||||
@@ -2,38 +2,62 @@ public class CommandHandler | |||||
{ | { | ||||
private readonly DiscordSocketClient _client; | private readonly DiscordSocketClient _client; | ||||
private readonly CommandService _commands; | private readonly CommandService _commands; | ||||
private readonly IServiceProvider _services; | |||||
public CommandHandler(IServiceProvider services) | |||||
public CommandHandler(DiscordSocketClient client, CommandService commands) | |||||
{ | { | ||||
_services = services; | |||||
_commands = services.GetRequiredService<CommandService>(); | |||||
_client = services.GetRequiredService<DiscordSocketClient>(); | |||||
_commands = commands; | |||||
_client = client; | |||||
} | } | ||||
public async Task InstallCommandsAsync() | public async Task InstallCommandsAsync() | ||||
{ | { | ||||
// Hook the MessageReceived Event into our Command Handler | |||||
// Hook the MessageReceived event into our command handler | |||||
_client.MessageReceived += HandleCommandAsync; | _client.MessageReceived += HandleCommandAsync; | ||||
// Discover all of the commands in this assembly and load them. | |||||
await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); | |||||
// Here we discover all of the command modules in the entry | |||||
// assembly and load them. Starting from Discord.NET 2.0, a | |||||
// service provider is required to be passed into the | |||||
// module registration method to inject the | |||||
// required dependencies. | |||||
// | |||||
// If you do not use Dependency Injection, pass null. | |||||
// See Dependency Injection guide for more information. | |||||
await _commands.AddModulesAsync(assembly: Assembly.GetEntryAssembly(), | |||||
services: null); | |||||
} | } | ||||
private async Task HandleCommandAsync(SocketMessage messageParam) | private async Task HandleCommandAsync(SocketMessage messageParam) | ||||
{ | { | ||||
// Don't process the command if it was a System Message | |||||
// Don't process the command if it was a system message | |||||
var message = messageParam as SocketUserMessage; | var message = messageParam as SocketUserMessage; | ||||
if (message == null) return; | if (message == null) return; | ||||
// Create a number to track where the prefix ends and the command begins | // Create a number to track where the prefix ends and the command begins | ||||
int argPos = 0; | int argPos = 0; | ||||
// Determine if the message is a command, based on if it starts with '!' or a mention prefix | |||||
if (!(message.HasCharPrefix('!', ref argPos) || message.HasMentionPrefix(_client.CurrentUser, ref argPos))) return; | |||||
// Create a Command Context | |||||
// Determine if the message is a command based on the prefix | |||||
if (!(message.HasCharPrefix('!', ref argPos) || | |||||
message.HasMentionPrefix(_client.CurrentUser, ref argPos))) | |||||
return; | |||||
// Create a WebSocket-based command context based on the message | |||||
var context = new SocketCommandContext(_client, message); | var context = new SocketCommandContext(_client, message); | ||||
// Execute the command. (result does not indicate a return value, | |||||
// rather an object stating if the command executed successfully) | |||||
var result = await _commands.ExecuteAsync(context, argPos, _services); | |||||
if (!result.IsSuccess) | |||||
await context.Channel.SendMessageAsync(result.ErrorReason); | |||||
// Execute the command with the command context we just | |||||
// created, along with the service provider for precondition checks. | |||||
// Keep in mind that result does not indicate a return value | |||||
// rather an object stating if the command executed successfully. | |||||
var result = await _commands.ExecuteAsync( | |||||
context: context, | |||||
argPost: argPos, | |||||
services: null); | |||||
// Optionally, we may inform the user if the command fails | |||||
// to be executed; however, this may not always be desired, | |||||
// as it may clog up the request queue should a user spam a | |||||
// command. | |||||
// if (!result.IsSuccess) | |||||
// await context.Channel.SendMessageAsync(result.ErrorReason); | |||||
} | } | ||||
} | } |
@@ -4,7 +4,7 @@ public class MyModule : ModuleBase<SocketCommandContext> | |||||
public async Task<RuntimeResult> ChooseAsync(string food) | public async Task<RuntimeResult> ChooseAsync(string food) | ||||
{ | { | ||||
if (food == "salad") | if (food == "salad") | ||||
return MyCustomResult.FromError("No salad allowed!"); | |||||
return MyCustomResult.FromSuccess($"I'll take a {food}!"). | |||||
return MyCustomResult.FromError("No, I don't want that!"); | |||||
return MyCustomResult.FromSuccess($"Give me the {food}!"). | |||||
} | } | ||||
} | } |
@@ -1,18 +1,65 @@ | |||||
private IServiceProvider _services; | |||||
private CommandService _commands; | |||||
public class Initialize | |||||
{ | |||||
private IServiceProvider _services; | |||||
private CommandService _commands; | |||||
private DiscordSocketClient _client; | |||||
public Initialize(CommandService commands = null, DiscordSocketClient client = null) | |||||
{ | |||||
_commands = commands ?? new CommandService(); | |||||
_client = client ?? new DiscordSocketClient(); | |||||
} | |||||
public async Task InstallAsync(DiscordSocketClient client) | |||||
public IServiceProvider BuildServiceProvider() | |||||
{ | |||||
// Here, we will inject the ServiceProvider with | |||||
// all of the services our client will use. | |||||
return 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 | |||||
{ | { | ||||
// Here, we will inject the ServiceProvider with | |||||
// all of the services our client will use. | |||||
_services = 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. | |||||
.AddSingleton<DatabaseService>() | |||||
.BuildServiceProvider(); | |||||
// ... | |||||
await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); | |||||
} | |||||
private readonly CommandService _commands; | |||||
private readonly IServiceProvider _services; | |||||
private readonly DiscordSocketClient _client; | |||||
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); | |||||
// ... | |||||
} | |||||
} |
@@ -13,8 +13,11 @@ public class RequireOwnerAttribute : PreconditionAttribute | |||||
// Override the CheckPermissions method | // Override the CheckPermissions method | ||||
public async override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) | public async override Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) | ||||
{ | { | ||||
// Get the client via Depedency Injection | |||||
var client = services.GetRequiredService<DiscordSocketClient>(); | |||||
// Get the ID of the bot's owner | // Get the ID of the bot's owner | ||||
var ownerId = (await services.GetService<DiscordSocketClient>().GetApplicationInfoAsync()).Owner.Id; | |||||
var appInfo = await client.GetApplicationInfoAsync().ConfigureAwait(false); | |||||
var ownerId = appInfo.Owner.Id; | |||||
// If this command was executed by that user, return a success | // If this command was executed by that user, return a success | ||||
if (context.User.Id == ownerId) | if (context.User.Id == ownerId) | ||||
return PreconditionResult.FromSuccess(); | return PreconditionResult.FromSuccess(); | ||||
@@ -23,7 +23,6 @@ Here are some examples: | |||||
> [!NOTE] | > [!NOTE] | ||||
> Please note that you should *not* try to blindly copy paste | > Please note that you should *not* try to blindly copy paste | ||||
> the code. The examples are meant to be a template or a guide. | > the code. The examples are meant to be a template or a guide. | ||||
> It is not meant to be something that will work out of the box. | |||||
[Official template]: https://github.com/foxbot/DiscordBotBase/tree/csharp/src/DiscordBot | [Official template]: https://github.com/foxbot/DiscordBotBase/tree/csharp/src/DiscordBot | ||||
[Official samples]: https://github.com/RogueException/Discord.Net/tree/dev/samples | [Official samples]: https://github.com/RogueException/Discord.Net/tree/dev/samples | ||||
@@ -33,7 +32,10 @@ Here are some examples: | |||||
## New to .NET/C#? | ## New to .NET/C#? | ||||
If you are new to the language, using this lib may prove to be | |||||
All examples or snippets featured in this guide and all API | |||||
documentation will be written in C#. | |||||
If you are new to the language, using this wrapper may prove to be | |||||
difficult, but don't worry! There are many resources online that can | difficult, but don't worry! There are many resources online that can | ||||
help you get started in the wonderful world of .NET. Here are some | help you get started in the wonderful world of .NET. Here are some | ||||
resources to get you started. | resources to get you started. | ||||