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