+ 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. | ||||