From 9a7e65cf6bd87d7a87652407d10fecbbeff222fb Mon Sep 17 00:00:00 2001 From: Still Hsu <341464@gmail.com> Date: Tue, 5 Jun 2018 09:34:31 +0800 Subject: [PATCH] Revise guide paragraphs/samples + Fix various formatting. + Provide a more detailed walkthrough for dependency injection. + Add C# note at intro. --- docs/faq/commands/samples/DI.cs | 19 ++--- .../faq/commands/samples/runmode-cmdconfig.cs | 3 +- docs/guides/commands/dependency-injection.md | 5 +- .../commands/samples/command_handler.cs | 58 ++++++++++---- .../commands/samples/customresult_usage.cs | 4 +- .../commands/samples/dependency_map_setup.cs | 79 +++++++++++++++---- docs/guides/commands/samples/require_owner.cs | 5 +- docs/guides/introduction/intro.md | 6 +- 8 files changed, 129 insertions(+), 50 deletions(-) diff --git a/docs/faq/commands/samples/DI.cs b/docs/faq/commands/samples/DI.cs index 59f098ff9..ce4454bc2 100644 --- a/docs/faq/commands/samples/DI.cs +++ b/docs/faq/commands/samples/DI.cs @@ -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() - .BuildServiceProvider(); + new ServiceCollection() + .AddSingleton() + .BuildServiceProvider(); } public class MyModule : ModuleBase { // 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); } } \ No newline at end of file diff --git a/docs/faq/commands/samples/runmode-cmdconfig.cs b/docs/faq/commands/samples/runmode-cmdconfig.cs index 22b356aa3..11d9cc295 100644 --- a/docs/faq/commands/samples/runmode-cmdconfig.cs +++ b/docs/faq/commands/samples/runmode-cmdconfig.cs @@ -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); } } \ No newline at end of file diff --git a/docs/guides/commands/dependency-injection.md b/docs/guides/commands/dependency-injection.md index db63e00c4..6c748b8dc 100644 --- a/docs/guides/commands/dependency-injection.md +++ b/docs/guides/commands/dependency-injection.md @@ -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 diff --git a/docs/guides/commands/samples/command_handler.cs b/docs/guides/commands/samples/command_handler.cs index d63d550ee..509c2d929 100644 --- a/docs/guides/commands/samples/command_handler.cs +++ b/docs/guides/commands/samples/command_handler.cs @@ -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(); - _client = services.GetRequiredService(); + _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); } } \ No newline at end of file diff --git a/docs/guides/commands/samples/customresult_usage.cs b/docs/guides/commands/samples/customresult_usage.cs index f7ec303a2..44266cfb4 100644 --- a/docs/guides/commands/samples/customresult_usage.cs +++ b/docs/guides/commands/samples/customresult_usage.cs @@ -4,7 +4,7 @@ public class MyModule : ModuleBase public async Task 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}!"). } } \ No newline at end of file diff --git a/docs/guides/commands/samples/dependency_map_setup.cs b/docs/guides/commands/samples/dependency_map_setup.cs index 34e4c994e..5790ceb4e 100644 --- a/docs/guides/commands/samples/dependency_map_setup.cs +++ b/docs/guides/commands/samples/dependency_map_setup.cs @@ -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() + .AddSingleton() + .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() - .BuildServiceProvider(); - // ... - await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); -} \ No newline at end of file + 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); + // ... + } +} diff --git a/docs/guides/commands/samples/require_owner.cs b/docs/guides/commands/samples/require_owner.cs index a4e95cfcb..f17b7d9a7 100644 --- a/docs/guides/commands/samples/require_owner.cs +++ b/docs/guides/commands/samples/require_owner.cs @@ -13,8 +13,11 @@ public class RequireOwnerAttribute : PreconditionAttribute // Override the CheckPermissions method public async override Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) { + // Get the client via Depedency Injection + var client = services.GetRequiredService(); // Get the ID of the bot's owner - var ownerId = (await services.GetService().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(); diff --git a/docs/guides/introduction/intro.md b/docs/guides/introduction/intro.md index c22edd1f7..4c002740a 100644 --- a/docs/guides/introduction/intro.md +++ b/docs/guides/introduction/intro.md @@ -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.