+ 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 string MyCoolString {get; set;} | |||
public string MyCoolString { get; set; } | |||
} | |||
public class Setup | |||
{ | |||
public IServiceProvider BuildProvider() => | |||
new ServiceCollection() | |||
.AddSingleton<MyService>() | |||
.BuildServiceProvider(); | |||
new ServiceCollection() | |||
.AddSingleton<MyService>() | |||
.BuildServiceProvider(); | |||
} | |||
public class MyModule : ModuleBase<SocketCommandContext> | |||
{ | |||
// 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")] | |||
public Task GetOrSetStringAsync(string input) | |||
{ | |||
if (_myService.MyCoolString == null) _myService.MyCoolString = input; | |||
if (string.IsNullOrEmpty(_myService.MyCoolString)) _myService.MyCoolString = input; | |||
return ReplyAsync(_myService.MyCoolString); | |||
} | |||
} |
@@ -1,9 +1,10 @@ | |||
public class Setup | |||
{ | |||
private readonly CommandService _command; | |||
public Setup() | |||
{ | |||
var config = new CommandServiceConfig{DefaultRunMode = RunMode.Async}; | |||
var config = new CommandServiceConfig{ DefaultRunMode = RunMode.Async }; | |||
_command = new CommandService(config); | |||
} | |||
} |
@@ -11,10 +11,11 @@ DI when writing your modules. | |||
## Setup | |||
1. Create an @System.IServiceProvider. | |||
1. Create a @Microsoft.Extensions.DependencyInjection.ServiceCollection. | |||
2. Add the dependencies to the service collection that you wish | |||
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 | |||
@@ -2,38 +2,62 @@ public class CommandHandler | |||
{ | |||
private readonly DiscordSocketClient _client; | |||
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() | |||
{ | |||
// Hook the MessageReceived Event into our Command Handler | |||
// Hook the MessageReceived event into our command handler | |||
_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) | |||
{ | |||
// 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; | |||
if (message == null) return; | |||
// Create a number to track where the prefix ends and the command begins | |||
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); | |||
// 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) | |||
{ | |||
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 | |||
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 | |||
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 (context.User.Id == ownerId) | |||
return PreconditionResult.FromSuccess(); | |||
@@ -23,7 +23,6 @@ Here are some examples: | |||
> [!NOTE] | |||
> Please note that you should *not* try to blindly copy paste | |||
> 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 samples]: https://github.com/RogueException/Discord.Net/tree/dev/samples | |||
@@ -33,7 +32,10 @@ Here are some examples: | |||
## 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 | |||
help you get started in the wonderful world of .NET. Here are some | |||
resources to get you started. | |||