From 9a166ef1d0baecd21e4e5b965e2ac364feddbe2e Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Mon, 15 Jan 2018 19:09:10 +0100 Subject: [PATCH 01/13] Add callback method for when a module class has been added to the CommandService. --- .../Builders/ModuleBuilder.cs | 5 ++- .../Builders/ModuleClassBuilder.cs | 1 + src/Discord.Net.Commands/CommandService.cs | 32 ++++++++++++++----- src/Discord.Net.Commands/IModuleBase.cs | 4 ++- src/Discord.Net.Commands/Info/ModuleInfo.cs | 6 +++- src/Discord.Net.Commands/ModuleBase.cs | 8 ++++- 6 files changed, 44 insertions(+), 12 deletions(-) diff --git a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs index 0a33c9e26..67fb5904d 100644 --- a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Collections.Generic; +using System.Reflection; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; @@ -25,6 +26,8 @@ namespace Discord.Commands.Builders public IReadOnlyList Attributes => _attributes; public IReadOnlyList Aliases => _aliases; + internal Optional TypeInfo { get; set; } + //Automatic internal ModuleBuilder(CommandService service, ModuleBuilder parent) { diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index 5a3a1f25a..cf7b43555 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -98,6 +98,7 @@ namespace Discord.Commands private static void BuildModule(ModuleBuilder builder, TypeInfo typeInfo, CommandService service) { var attributes = typeInfo.GetCustomAttributes(); + builder.TypeInfo = typeInfo; foreach (var attribute in attributes) { diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 8e7dab898..cb328bb55 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -1,5 +1,3 @@ -using Discord.Commands.Builders; -using Discord.Logging; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -8,6 +6,9 @@ using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Discord.Commands.Builders; +using Discord.Logging; namespace Discord.Commands { @@ -93,8 +94,8 @@ namespace Discord.Commands _moduleLock.Release(); } } - public Task AddModuleAsync() => AddModuleAsync(typeof(T)); - public async Task AddModuleAsync(Type type) + public Task AddModuleAsync(IServiceProvider services = null) => AddModuleAsync(typeof(T), services); + public async Task AddModuleAsync(Type type, IServiceProvider services = null) { await _moduleLock.WaitAsync().ConfigureAwait(false); try @@ -111,14 +112,14 @@ namespace Discord.Commands _typedModuleDefs[module.Key] = module.Value; - return LoadModuleInternal(module.Value); + return LoadModuleInternal(module.Value, services); } finally { _moduleLock.Release(); } } - public async Task> AddModulesAsync(Assembly assembly) + public async Task> AddModulesAsync(Assembly assembly, IServiceProvider services = null) { await _moduleLock.WaitAsync().ConfigureAwait(false); try @@ -129,7 +130,7 @@ namespace Discord.Commands foreach (var info in moduleDefs) { _typedModuleDefs[info.Key] = info.Value; - LoadModuleInternal(info.Value); + LoadModuleInternal(info.Value, services); } return moduleDefs.Select(x => x.Value).ToImmutableArray(); @@ -139,10 +140,25 @@ namespace Discord.Commands _moduleLock.Release(); } } - private ModuleInfo LoadModuleInternal(ModuleInfo module) + private ModuleInfo LoadModuleInternal(ModuleInfo module, IServiceProvider services = null) { _moduleDefs.Add(module); + if (module.TypeInfo.IsSpecified) + { + services = services ?? EmptyServiceProvider.Instance; + try + { + var moduleInstance = ReflectionUtils.CreateObject(module.TypeInfo.Value, this, services); + moduleInstance.OnModuleAdded(this); + } + catch(Exception) + { + //unsure of what to do here + throw; + } + } + foreach (var command in module.Commands) _map.AddCommand(command); diff --git a/src/Discord.Net.Commands/IModuleBase.cs b/src/Discord.Net.Commands/IModuleBase.cs index 479724ae3..722c794c9 100644 --- a/src/Discord.Net.Commands/IModuleBase.cs +++ b/src/Discord.Net.Commands/IModuleBase.cs @@ -1,4 +1,4 @@ -namespace Discord.Commands +namespace Discord.Commands { internal interface IModuleBase { @@ -7,5 +7,7 @@ void BeforeExecute(CommandInfo command); void AfterExecute(CommandInfo command); + + void OnModuleAdded(CommandService commandService); } } diff --git a/src/Discord.Net.Commands/Info/ModuleInfo.cs b/src/Discord.Net.Commands/Info/ModuleInfo.cs index 97b90bf4e..e554391ea 100644 --- a/src/Discord.Net.Commands/Info/ModuleInfo.cs +++ b/src/Discord.Net.Commands/Info/ModuleInfo.cs @@ -2,7 +2,7 @@ using System; using System.Linq; using System.Collections.Generic; using System.Collections.Immutable; - +using System.Reflection; using Discord.Commands.Builders; namespace Discord.Commands @@ -22,6 +22,8 @@ namespace Discord.Commands public ModuleInfo Parent { get; } public bool IsSubmodule => Parent != null; + internal Optional TypeInfo { get; } + internal ModuleInfo(ModuleBuilder builder, CommandService service, ModuleInfo parent = null) { Service = service; @@ -31,6 +33,8 @@ namespace Discord.Commands Remarks = builder.Remarks; Parent = parent; + TypeInfo = builder.TypeInfo; + Aliases = BuildAliases(builder, service).ToImmutableArray(); Commands = builder.Commands.Select(x => x.Build(this, service)).ToImmutableArray(); Preconditions = BuildPreconditions(builder).ToImmutableArray(); diff --git a/src/Discord.Net.Commands/ModuleBase.cs b/src/Discord.Net.Commands/ModuleBase.cs index f51656e40..58cc3407c 100644 --- a/src/Discord.Net.Commands/ModuleBase.cs +++ b/src/Discord.Net.Commands/ModuleBase.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading.Tasks; namespace Discord.Commands @@ -23,6 +23,10 @@ namespace Discord.Commands { } + protected virtual void OnModuleAdded(CommandService commandService) + { + } + //IModuleBase void IModuleBase.SetContext(ICommandContext context) { @@ -33,5 +37,7 @@ namespace Discord.Commands void IModuleBase.BeforeExecute(CommandInfo command) => BeforeExecute(command); void IModuleBase.AfterExecute(CommandInfo command) => AfterExecute(command); + + void IModuleBase.OnModuleAdded(CommandService commandService) => OnModuleAdded(commandService); } } From 031b289d80604666dde62619e521af303203d48d Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Mon, 15 Jan 2018 20:02:20 +0100 Subject: [PATCH 02/13] Rename method to more intuitive 'OnModuleBuilding' --- src/Discord.Net.Commands/IModuleBase.cs | 2 +- src/Discord.Net.Commands/ModuleBase.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Discord.Net.Commands/IModuleBase.cs b/src/Discord.Net.Commands/IModuleBase.cs index 722c794c9..89559b9a8 100644 --- a/src/Discord.Net.Commands/IModuleBase.cs +++ b/src/Discord.Net.Commands/IModuleBase.cs @@ -8,6 +8,6 @@ namespace Discord.Commands void AfterExecute(CommandInfo command); - void OnModuleAdded(CommandService commandService); + void OnModuleBuilding(CommandService commandService); } } diff --git a/src/Discord.Net.Commands/ModuleBase.cs b/src/Discord.Net.Commands/ModuleBase.cs index 58cc3407c..ec3d35e5b 100644 --- a/src/Discord.Net.Commands/ModuleBase.cs +++ b/src/Discord.Net.Commands/ModuleBase.cs @@ -23,7 +23,7 @@ namespace Discord.Commands { } - protected virtual void OnModuleAdded(CommandService commandService) + protected virtual void OnModuleBuilding(CommandService commandService) { } @@ -38,6 +38,6 @@ namespace Discord.Commands void IModuleBase.AfterExecute(CommandInfo command) => AfterExecute(command); - void IModuleBase.OnModuleAdded(CommandService commandService) => OnModuleAdded(commandService); + void IModuleBase.OnModuleBuilding(CommandService commandService) => OnModuleBuilding(commandService); } } From 7f1b792946ac6b950922b06178aa5cc37d9f4144 Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Mon, 15 Jan 2018 20:04:37 +0100 Subject: [PATCH 03/13] I..... missed one. --- src/Discord.Net.Commands/CommandService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index cb328bb55..e99d019b6 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -150,7 +150,7 @@ namespace Discord.Commands try { var moduleInstance = ReflectionUtils.CreateObject(module.TypeInfo.Value, this, services); - moduleInstance.OnModuleAdded(this); + moduleInstance.OnModuleBuilding(this); } catch(Exception) { From 7b100e99bb119be190006d1cd8e403776930e401 Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Mon, 15 Jan 2018 23:34:06 +0100 Subject: [PATCH 04/13] * Make the service provider parameters required * Adjust quickstart guide to reflect changes --- .../samples/intro/structure.cs | 53 +++++++++++++------ src/Discord.Net.Commands/CommandService.cs | 15 +++--- 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/docs/guides/getting_started/samples/intro/structure.cs b/docs/guides/getting_started/samples/intro/structure.cs index bdfc12b67..c209b62df 100644 --- a/docs/guides/getting_started/samples/intro/structure.cs +++ b/docs/guides/getting_started/samples/intro/structure.cs @@ -19,10 +19,10 @@ class Program private readonly DiscordSocketClient _client; - // Keep the CommandService and IServiceCollection around for use with commands. + // Keep the CommandService and IServiceProvider around for use with commands. // These two types require you install the Discord.Net.Commands package. - private readonly IServiceCollection _map = new ServiceCollection(); - private readonly CommandService _commands = new CommandService(); + private readonly IServiceProvider _services; + private readonly CommandService _commands; private Program() { @@ -41,9 +41,39 @@ class Program // add the `using` at the top, and uncomment this line: //WebSocketProvider = WS4NetProvider.Instance }); + + _commands = new CommandService(new CommandServiceConfig + { + // Again, log level: + LogLevel = LogSeverity.Info, + + // There's a few more properties you can set, + // for example, case-insensitive commands. + CaseSensitiveCommands = false, + }); + // Subscribe the logging handler to both the client and the CommandService. _client.Log += Logger; _commands.Log += Logger; + + // Setup your DI container. + _services = ConfigureServices(); + } + + // If any services require the client, or the CommandService, or something else you keep on hand, + // pass them as parameters into this method as needed. + // If this method is getting pretty long, you can seperate it out into another file using partials. + private static IServiceProvider ConfigureServices() + { + map = new ServiceCollection() + // Repeat this for all the service classes + // and other dependencies that your commands might need. + .AddSingleton(new SomeServiceClass()); + + // When all your required services are in the collection, build the container. + // Tip: There's an overload taking in a 'validateScopes' bool to make sure + // you haven't made any mistakes in your dependency graph. + return map.BuildServiceProvider(); } // Example of a logging handler. This can be re-used by addons @@ -92,24 +122,15 @@ class Program await Task.Delay(Timeout.Infinite); } - private IServiceProvider _services; - private async Task InitCommands() { - // Repeat this for all the service classes - // and other dependencies that your commands might need. - _map.AddSingleton(new SomeServiceClass()); - - // When all your required services are in the collection, build the container. - // Tip: There's an overload taking in a 'validateScopes' bool to make sure - // you haven't made any mistakes in your dependency graph. - _services = _map.BuildServiceProvider(); - // Either search the program and add all Module classes that can be found. // Module classes MUST be marked 'public' or they will be ignored. - await _commands.AddModulesAsync(Assembly.GetEntryAssembly()); + // You also need to pass your 'IServiceProvider' instance now, + // so make sure that's done before you get here. + await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); // Or add Modules manually if you prefer to be a little more explicit: - await _commands.AddModuleAsync(); + await _commands.AddModuleAsync(_services); // Note that the first one is 'Modules' (plural) and the second is 'Module' (singular). // Subscribe a handler to see if a message invokes a command. diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index e99d019b6..6c9ceeacb 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -87,15 +87,17 @@ namespace Discord.Commands buildFunc(builder); var module = builder.Build(this); - return LoadModuleInternal(module); + + //should be fine to pass null here since it'll never get checked from this path anyway + return LoadModuleInternal(module, null); } finally { _moduleLock.Release(); } } - public Task AddModuleAsync(IServiceProvider services = null) => AddModuleAsync(typeof(T), services); - public async Task AddModuleAsync(Type type, IServiceProvider services = null) + public Task AddModuleAsync(IServiceProvider services) => AddModuleAsync(typeof(T), services); + public async Task AddModuleAsync(Type type, IServiceProvider services) { await _moduleLock.WaitAsync().ConfigureAwait(false); try @@ -119,7 +121,7 @@ namespace Discord.Commands _moduleLock.Release(); } } - public async Task> AddModulesAsync(Assembly assembly, IServiceProvider services = null) + public async Task> AddModulesAsync(Assembly assembly, IServiceProvider services) { await _moduleLock.WaitAsync().ConfigureAwait(false); try @@ -140,12 +142,13 @@ namespace Discord.Commands _moduleLock.Release(); } } - private ModuleInfo LoadModuleInternal(ModuleInfo module, IServiceProvider services = null) + private ModuleInfo LoadModuleInternal(ModuleInfo module, IServiceProvider services) { _moduleDefs.Add(module); if (module.TypeInfo.IsSpecified) { + //keep this for safety? services = services ?? EmptyServiceProvider.Instance; try { @@ -163,7 +166,7 @@ namespace Discord.Commands _map.AddCommand(command); foreach (var submodule in module.Submodules) - LoadModuleInternal(submodule); + LoadModuleInternal(submodule, services); return module; } From 74b17b0e04e2c413397a2e1b66ff814615326205 Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Tue, 16 Jan 2018 18:06:28 +0100 Subject: [PATCH 05/13] Experimental change for feedback --- .../samples/intro/structure.cs | 12 +- .../Builders/ModuleBuilder.cs | 18 +- .../Builders/ModuleClassBuilder.cs | 2 +- src/Discord.Net.Commands/CommandService.cs | 197 +++++++++--------- .../CommandServiceConfig.cs | 6 +- src/Discord.Net.Commands/Info/ModuleInfo.cs | 4 +- 6 files changed, 132 insertions(+), 107 deletions(-) diff --git a/docs/guides/getting_started/samples/intro/structure.cs b/docs/guides/getting_started/samples/intro/structure.cs index c209b62df..e61016d33 100644 --- a/docs/guides/getting_started/samples/intro/structure.cs +++ b/docs/guides/getting_started/samples/intro/structure.cs @@ -19,9 +19,8 @@ class Program private readonly DiscordSocketClient _client; - // Keep the CommandService and IServiceProvider around for use with commands. - // These two types require you install the Discord.Net.Commands package. - private readonly IServiceProvider _services; + // Keep the CommandService around for use with commands. + // This type requires you install the Discord.Net.Commands package. private readonly CommandService _commands; private Program() @@ -47,6 +46,9 @@ class Program // Again, log level: LogLevel = LogSeverity.Info, + // Setup your DI container. + ServiceProvider = ConfigureServices(), + // There's a few more properties you can set, // for example, case-insensitive commands. CaseSensitiveCommands = false, @@ -56,8 +58,6 @@ class Program _client.Log += Logger; _commands.Log += Logger; - // Setup your DI container. - _services = ConfigureServices(); } // If any services require the client, or the CommandService, or something else you keep on hand, @@ -161,7 +161,7 @@ class Program // 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, pos, _services); + var result = await _commands.ExecuteAsync(context, pos); // Uncomment the following lines if you want the bot // to send a message if it failed (not advised for most situations). diff --git a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs index 67fb5904d..5a3f3a8ca 100644 --- a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs @@ -26,7 +26,7 @@ namespace Discord.Commands.Builders public IReadOnlyList Attributes => _attributes; public IReadOnlyList Aliases => _aliases; - internal Optional TypeInfo { get; set; } + internal TypeInfo TypeInfo { get; set; } //Automatic internal ModuleBuilder(CommandService service, ModuleBuilder parent) @@ -120,6 +120,22 @@ namespace Discord.Commands.Builders if (Name == null) Name = _aliases[0]; + if (TypeInfo != null) + { + //keep this for safety? + //services = services ?? EmptyServiceProvider.Instance; + try + { + var moduleInstance = ReflectionUtils.CreateObject(TypeInfo, service, service._serviceProvider); + moduleInstance.OnModuleBuilding(service); + } + catch (Exception) + { + //unsure of what to do here + throw; + } + } + return new ModuleInfo(this, service, parent); } diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index cf7b43555..e56495632 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -297,7 +297,7 @@ namespace Discord.Commands } //We dont have a cached type reader, create one - reader = ReflectionUtils.CreateObject(typeReaderType.GetTypeInfo(), service, EmptyServiceProvider.Instance); + reader = ReflectionUtils.CreateObject(typeReaderType.GetTypeInfo(), service, service._serviceProvider); service.AddTypeReader(paramType, reader); return reader; diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 6c9ceeacb..b213971cd 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -28,6 +28,8 @@ namespace Discord.Commands private readonly HashSet _moduleDefs; private readonly CommandMap _map; + internal readonly IServiceProvider _serviceProvider; + internal readonly bool _caseSensitive, _throwOnError, _ignoreExtraArgs; internal readonly char _separatorChar; internal readonly RunMode _defaultRunMode; @@ -41,6 +43,7 @@ namespace Discord.Commands public CommandService() : this(new CommandServiceConfig()) { } public CommandService(CommandServiceConfig config) { + _serviceProvider = config.ServiceProvider ?? EmptyServiceProvider.Instance; _caseSensitive = config.CaseSensitiveCommands; _throwOnError = config.ThrowOnError; _ignoreExtraArgs = config.IgnoreExtraArgs; @@ -86,18 +89,18 @@ namespace Discord.Commands var builder = new ModuleBuilder(this, null, primaryAlias); buildFunc(builder); - var module = builder.Build(this); + var module = builder.Build(this, null); //should be fine to pass null here since it'll never get checked from this path anyway - return LoadModuleInternal(module, null); + return LoadModuleInternal(module); } finally { _moduleLock.Release(); } } - public Task AddModuleAsync(IServiceProvider services) => AddModuleAsync(typeof(T), services); - public async Task AddModuleAsync(Type type, IServiceProvider services) + public Task AddModuleAsync() => AddModuleAsync(typeof(T)); + public async Task AddModuleAsync(Type type) { await _moduleLock.WaitAsync().ConfigureAwait(false); try @@ -114,14 +117,14 @@ namespace Discord.Commands _typedModuleDefs[module.Key] = module.Value; - return LoadModuleInternal(module.Value, services); + return LoadModuleInternal(module.Value); } finally { _moduleLock.Release(); } } - public async Task> AddModulesAsync(Assembly assembly, IServiceProvider services) + public async Task> AddModulesAsync(Assembly assembly) { await _moduleLock.WaitAsync().ConfigureAwait(false); try @@ -132,7 +135,7 @@ namespace Discord.Commands foreach (var info in moduleDefs) { _typedModuleDefs[info.Key] = info.Value; - LoadModuleInternal(info.Value, services); + LoadModuleInternal(info.Value); } return moduleDefs.Select(x => x.Value).ToImmutableArray(); @@ -142,31 +145,31 @@ namespace Discord.Commands _moduleLock.Release(); } } - private ModuleInfo LoadModuleInternal(ModuleInfo module, IServiceProvider services) + private ModuleInfo LoadModuleInternal(ModuleInfo module) { _moduleDefs.Add(module); - if (module.TypeInfo.IsSpecified) - { - //keep this for safety? - services = services ?? EmptyServiceProvider.Instance; - try - { - var moduleInstance = ReflectionUtils.CreateObject(module.TypeInfo.Value, this, services); - moduleInstance.OnModuleBuilding(this); - } - catch(Exception) - { - //unsure of what to do here - throw; - } - } + //if (module.TypeInfo.IsSpecified) + //{ + // //keep this for safety? + // services = services ?? EmptyServiceProvider.Instance; + // try + // { + // var moduleInstance = ReflectionUtils.CreateObject(module.TypeInfo.Value, this, services); + // moduleInstance.OnModuleBuilding(this); + // } + // catch(Exception) + // { + // //unsure of what to do here + // throw; + // } + //} foreach (var command in module.Commands) _map.AddCommand(command); foreach (var submodule in module.Submodules) - LoadModuleInternal(submodule, services); + LoadModuleInternal(submodule); return module; } @@ -243,7 +246,7 @@ namespace Discord.Commands var readers = _typeReaders.GetOrAdd(typeof(Nullable<>).MakeGenericType(valueType), x => new ConcurrentDictionary()); var nullableReader = NullableTypeReader.Create(valueType, valueTypeReader); readers[nullableReader.GetType()] = nullableReader; - } + } internal IDictionary GetTypeReaders(Type type) { if (_typeReaders.TryGetValue(type, out var definedTypeReaders)) @@ -291,97 +294,99 @@ namespace Discord.Commands return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command."); } - public Task ExecuteAsync(ICommandContext context, int argPos, IServiceProvider services = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) - => ExecuteAsync(context, context.Message.Content.Substring(argPos), services, multiMatchHandling); - public async Task ExecuteAsync(ICommandContext context, string input, IServiceProvider services = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) + public Task ExecuteAsync(ICommandContext context, int argPos, /*IServiceProvider services = null,*/ MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) + => ExecuteAsync(context, context.Message.Content.Substring(argPos), /*services,*/ multiMatchHandling); + public async Task ExecuteAsync(ICommandContext context, string input, /*IServiceProvider services = null,*/ MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) { - services = services ?? EmptyServiceProvider.Instance; - - var searchResult = Search(context, input); - if (!searchResult.IsSuccess) - return searchResult; - - var commands = searchResult.Commands; - var preconditionResults = new Dictionary(); - - foreach (var match in commands) + //services = services ?? EmptyServiceProvider.Instance; + using (var scope = _serviceProvider.CreateScope()) { - preconditionResults[match] = await match.Command.CheckPreconditionsAsync(context, services).ConfigureAwait(false); - } + var searchResult = Search(context, input); + if (!searchResult.IsSuccess) + return searchResult; - var successfulPreconditions = preconditionResults - .Where(x => x.Value.IsSuccess) - .ToArray(); + var commands = searchResult.Commands; + var preconditionResults = new Dictionary(); - if (successfulPreconditions.Length == 0) - { - //All preconditions failed, return the one from the highest priority command - var bestCandidate = preconditionResults - .OrderByDescending(x => x.Key.Command.Priority) - .FirstOrDefault(x => !x.Value.IsSuccess); - return bestCandidate.Value; - } + foreach (var match in commands) + { + preconditionResults[match] = await match.Command.CheckPreconditionsAsync(context, scope.ServiceProvider).ConfigureAwait(false); + } - //If we get this far, at least one precondition was successful. + var successfulPreconditions = preconditionResults + .Where(x => x.Value.IsSuccess) + .ToArray(); - var parseResultsDict = new Dictionary(); - foreach (var pair in successfulPreconditions) - { - var parseResult = await pair.Key.ParseAsync(context, searchResult, pair.Value, services).ConfigureAwait(false); + if (successfulPreconditions.Length == 0) + { + //All preconditions failed, return the one from the highest priority command + var bestCandidate = preconditionResults + .OrderByDescending(x => x.Key.Command.Priority) + .FirstOrDefault(x => !x.Value.IsSuccess); + return bestCandidate.Value; + } + + //If we get this far, at least one precondition was successful. - if (parseResult.Error == CommandError.MultipleMatches) + var parseResultsDict = new Dictionary(); + foreach (var pair in successfulPreconditions) { - IReadOnlyList argList, paramList; - switch (multiMatchHandling) + var parseResult = await pair.Key.ParseAsync(context, searchResult, pair.Value, scope.ServiceProvider).ConfigureAwait(false); + + if (parseResult.Error == CommandError.MultipleMatches) { - case MultiMatchHandling.Best: - argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); - paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); - parseResult = ParseResult.FromSuccess(argList, paramList); - break; + IReadOnlyList argList, paramList; + switch (multiMatchHandling) + { + case MultiMatchHandling.Best: + argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); + paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); + parseResult = ParseResult.FromSuccess(argList, paramList); + break; + } } - } - parseResultsDict[pair.Key] = parseResult; - } + parseResultsDict[pair.Key] = parseResult; + } - // Calculates the 'score' of a command given a parse result - float CalculateScore(CommandMatch match, ParseResult parseResult) - { - float argValuesScore = 0, paramValuesScore = 0; - - if (match.Command.Parameters.Count > 0) + // Calculates the 'score' of a command given a parse result + float CalculateScore(CommandMatch match, ParseResult parseResult) { - var argValuesSum = parseResult.ArgValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0; - var paramValuesSum = parseResult.ParamValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0; + float argValuesScore = 0, paramValuesScore = 0; - argValuesScore = argValuesSum / match.Command.Parameters.Count; - paramValuesScore = paramValuesSum / match.Command.Parameters.Count; + if (match.Command.Parameters.Count > 0) + { + var argValuesSum = parseResult.ArgValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0; + var paramValuesSum = parseResult.ParamValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0; + + argValuesScore = argValuesSum / match.Command.Parameters.Count; + paramValuesScore = paramValuesSum / match.Command.Parameters.Count; + } + + var totalArgsScore = (argValuesScore + paramValuesScore) / 2; + return match.Command.Priority + totalArgsScore * 0.99f; } - var totalArgsScore = (argValuesScore + paramValuesScore) / 2; - return match.Command.Priority + totalArgsScore * 0.99f; - } + //Order the parse results by their score so that we choose the most likely result to execute + var parseResults = parseResultsDict + .OrderByDescending(x => CalculateScore(x.Key, x.Value)); - //Order the parse results by their score so that we choose the most likely result to execute - var parseResults = parseResultsDict - .OrderByDescending(x => CalculateScore(x.Key, x.Value)); + var successfulParses = parseResults + .Where(x => x.Value.IsSuccess) + .ToArray(); - var successfulParses = parseResults - .Where(x => x.Value.IsSuccess) - .ToArray(); + if (successfulParses.Length == 0) + { + //All parses failed, return the one from the highest priority command, using score as a tie breaker + var bestMatch = parseResults + .FirstOrDefault(x => !x.Value.IsSuccess); + return bestMatch.Value; + } - if (successfulParses.Length == 0) - { - //All parses failed, return the one from the highest priority command, using score as a tie breaker - var bestMatch = parseResults - .FirstOrDefault(x => !x.Value.IsSuccess); - return bestMatch.Value; + //If we get this far, at least one parse was successful. Execute the most likely overload. + var chosenOverload = successfulParses[0]; + return await chosenOverload.Key.ExecuteAsync(context, chosenOverload.Value, scope.ServiceProvider).ConfigureAwait(false); } - - //If we get this far, at least one parse was successful. Execute the most likely overload. - var chosenOverload = successfulParses[0]; - return await chosenOverload.Key.ExecuteAsync(context, chosenOverload.Value, services).ConfigureAwait(false); } } } diff --git a/src/Discord.Net.Commands/CommandServiceConfig.cs b/src/Discord.Net.Commands/CommandServiceConfig.cs index 7fdbe368b..c7157cf51 100644 --- a/src/Discord.Net.Commands/CommandServiceConfig.cs +++ b/src/Discord.Net.Commands/CommandServiceConfig.cs @@ -1,4 +1,6 @@ -namespace Discord.Commands +using System; + +namespace Discord.Commands { public class CommandServiceConfig { @@ -18,5 +20,7 @@ /// Determines whether extra parameters should be ignored. public bool IgnoreExtraArgs { get; set; } = false; + + public IServiceProvider ServiceProvider { get; set; } = EmptyServiceProvider.Instance; } } diff --git a/src/Discord.Net.Commands/Info/ModuleInfo.cs b/src/Discord.Net.Commands/Info/ModuleInfo.cs index e554391ea..6ebd901c7 100644 --- a/src/Discord.Net.Commands/Info/ModuleInfo.cs +++ b/src/Discord.Net.Commands/Info/ModuleInfo.cs @@ -22,7 +22,7 @@ namespace Discord.Commands public ModuleInfo Parent { get; } public bool IsSubmodule => Parent != null; - internal Optional TypeInfo { get; } + //public TypeInfo TypeInfo { get; } internal ModuleInfo(ModuleBuilder builder, CommandService service, ModuleInfo parent = null) { @@ -33,7 +33,7 @@ namespace Discord.Commands Remarks = builder.Remarks; Parent = parent; - TypeInfo = builder.TypeInfo; + //TypeInfo = builder.TypeInfo; Aliases = BuildAliases(builder, service).ToImmutableArray(); Commands = builder.Commands.Select(x => x.Build(this, service)).ToImmutableArray(); From 72b5e6c8a149d8e989b46351965daa14f8ca318c Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Fri, 19 Jan 2018 03:10:40 +0100 Subject: [PATCH 06/13] Remove superfluous comments, provide simpler alternative for setting the ServiceProvider. --- .../samples/intro/structure.cs | 4 +++- .../Builders/ModuleBuilder.cs | 2 -- src/Discord.Net.Commands/CommandService.cs | 22 ++++--------------- .../CommandServiceConfig.cs | 4 +++- 4 files changed, 10 insertions(+), 22 deletions(-) diff --git a/docs/guides/getting_started/samples/intro/structure.cs b/docs/guides/getting_started/samples/intro/structure.cs index e61016d33..9ba728133 100644 --- a/docs/guides/getting_started/samples/intro/structure.cs +++ b/docs/guides/getting_started/samples/intro/structure.cs @@ -164,7 +164,9 @@ class Program var result = await _commands.ExecuteAsync(context, pos); // Uncomment the following lines if you want the bot - // to send a message if it failed (not advised for most situations). + // to send a message if it failed. + // This does not catch errors from commands with 'RunMode.Async', + // subscribe a handler for '_commands.CommandExecuted' to see those. //if (!result.IsSuccess && result.Error != CommandError.UnknownCommand) // await msg.Channel.SendMessageAsync(result.ErrorReason); } diff --git a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs index 5a3f3a8ca..24a6477ee 100644 --- a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs @@ -122,8 +122,6 @@ namespace Discord.Commands.Builders if (TypeInfo != null) { - //keep this for safety? - //services = services ?? EmptyServiceProvider.Instance; try { var moduleInstance = ReflectionUtils.CreateObject(TypeInfo, service, service._serviceProvider); diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index b213971cd..c7d3e4011 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -43,7 +43,10 @@ namespace Discord.Commands public CommandService() : this(new CommandServiceConfig()) { } public CommandService(CommandServiceConfig config) { - _serviceProvider = config.ServiceProvider ?? EmptyServiceProvider.Instance; + _serviceProvider = config.ServiceProvider + ?? config.ServiceProviderFactory?.Invoke(this) + ?? EmptyServiceProvider.Instance; + _caseSensitive = config.CaseSensitiveCommands; _throwOnError = config.ThrowOnError; _ignoreExtraArgs = config.IgnoreExtraArgs; @@ -91,7 +94,6 @@ namespace Discord.Commands var module = builder.Build(this, null); - //should be fine to pass null here since it'll never get checked from this path anyway return LoadModuleInternal(module); } finally @@ -149,22 +151,6 @@ namespace Discord.Commands { _moduleDefs.Add(module); - //if (module.TypeInfo.IsSpecified) - //{ - // //keep this for safety? - // services = services ?? EmptyServiceProvider.Instance; - // try - // { - // var moduleInstance = ReflectionUtils.CreateObject(module.TypeInfo.Value, this, services); - // moduleInstance.OnModuleBuilding(this); - // } - // catch(Exception) - // { - // //unsure of what to do here - // throw; - // } - //} - foreach (var command in module.Commands) _map.AddCommand(command); diff --git a/src/Discord.Net.Commands/CommandServiceConfig.cs b/src/Discord.Net.Commands/CommandServiceConfig.cs index c7157cf51..c33fbd180 100644 --- a/src/Discord.Net.Commands/CommandServiceConfig.cs +++ b/src/Discord.Net.Commands/CommandServiceConfig.cs @@ -21,6 +21,8 @@ namespace Discord.Commands /// Determines whether extra parameters should be ignored. public bool IgnoreExtraArgs { get; set; } = false; - public IServiceProvider ServiceProvider { get; set; } = EmptyServiceProvider.Instance; + public IServiceProvider ServiceProvider { get; set; } + + public Func ServiceProviderFactory { get; set; } } } From 81bd9111faaf98a52679daae863ab04dce96e63e Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Fri, 19 Jan 2018 03:16:44 +0100 Subject: [PATCH 07/13] Add comment about the ServiceProviderFactory in the quickstart --- docs/guides/getting_started/samples/intro/structure.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/guides/getting_started/samples/intro/structure.cs b/docs/guides/getting_started/samples/intro/structure.cs index 9ba728133..2520e494c 100644 --- a/docs/guides/getting_started/samples/intro/structure.cs +++ b/docs/guides/getting_started/samples/intro/structure.cs @@ -48,6 +48,9 @@ class Program // Setup your DI container. ServiceProvider = ConfigureServices(), + // If you have a service that's dependant on the CommandService instance, + // use ServiceProviderFactory instead. + //ServiceProviderFactory = (cs => ConfigureServices(cs)), // There's a few more properties you can set, // for example, case-insensitive commands. From e3349ef3d400bb3ad8cb28dd4234d5316a80bcc4 Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Fri, 19 Jan 2018 03:33:46 +0100 Subject: [PATCH 08/13] Doc comment on items --- src/Discord.Net.Commands/CommandServiceConfig.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Discord.Net.Commands/CommandServiceConfig.cs b/src/Discord.Net.Commands/CommandServiceConfig.cs index c33fbd180..1432f4a3e 100644 --- a/src/Discord.Net.Commands/CommandServiceConfig.cs +++ b/src/Discord.Net.Commands/CommandServiceConfig.cs @@ -21,8 +21,10 @@ namespace Discord.Commands /// Determines whether extra parameters should be ignored. public bool IgnoreExtraArgs { get; set; } = false; - public IServiceProvider ServiceProvider { get; set; } + /// Gets or sets the to use. + public IServiceProvider ServiceProvider { get; set; } = null; - public Func ServiceProviderFactory { get; set; } + /// Gets or sets a factory function for the to use. + public Func ServiceProviderFactory { get; set; } = null; } } From ad7e0a46c8709e845dfacdc298a893e22dc11567 Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Fri, 19 Jan 2018 03:40:27 +0100 Subject: [PATCH 09/13] Fix quickstart leftover from previous draft --- docs/guides/getting_started/samples/intro/structure.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/getting_started/samples/intro/structure.cs b/docs/guides/getting_started/samples/intro/structure.cs index 2520e494c..be8d9a8be 100644 --- a/docs/guides/getting_started/samples/intro/structure.cs +++ b/docs/guides/getting_started/samples/intro/structure.cs @@ -131,9 +131,9 @@ class Program // Module classes MUST be marked 'public' or they will be ignored. // You also need to pass your 'IServiceProvider' instance now, // so make sure that's done before you get here. - await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); + await _commands.AddModulesAsync(Assembly.GetEntryAssembly()); // Or add Modules manually if you prefer to be a little more explicit: - await _commands.AddModuleAsync(_services); + await _commands.AddModuleAsync(); // Note that the first one is 'Modules' (plural) and the second is 'Module' (singular). // Subscribe a handler to see if a message invokes a command. From e30b9071351b69baa30a93a4851516dca9ea43cf Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Mon, 22 Jan 2018 11:35:08 +0100 Subject: [PATCH 10/13] Undo experimental changes, request IServiceProvider instance everywhere instead --- .../Builders/ModuleBuilder.cs | 10 +++--- .../Builders/ModuleClassBuilder.cs | 32 +++++++++---------- src/Discord.Net.Commands/CommandService.cs | 31 +++++++++--------- .../CommandServiceConfig.cs | 8 ++--- src/Discord.Net.Commands/Info/ModuleInfo.cs | 8 ++--- 5 files changed, 45 insertions(+), 44 deletions(-) diff --git a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs index 24a6477ee..2100069b6 100644 --- a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs @@ -114,7 +114,7 @@ namespace Discord.Commands.Builders return this; } - private ModuleInfo BuildImpl(CommandService service, ModuleInfo parent = null) + private ModuleInfo BuildImpl(CommandService service, IServiceProvider services, ModuleInfo parent = null) { //Default name to first alias if (Name == null) @@ -124,7 +124,7 @@ namespace Discord.Commands.Builders { try { - var moduleInstance = ReflectionUtils.CreateObject(TypeInfo, service, service._serviceProvider); + var moduleInstance = ReflectionUtils.CreateObject(TypeInfo, service, services); moduleInstance.OnModuleBuilding(service); } catch (Exception) @@ -134,11 +134,11 @@ namespace Discord.Commands.Builders } } - return new ModuleInfo(this, service, parent); + return new ModuleInfo(this, service, services, parent); } - public ModuleInfo Build(CommandService service) => BuildImpl(service); + public ModuleInfo Build(CommandService service, IServiceProvider services) => BuildImpl(service, services); - internal ModuleInfo Build(CommandService service, ModuleInfo parent) => BuildImpl(service, parent); + internal ModuleInfo Build(CommandService service, IServiceProvider services, ModuleInfo parent) => BuildImpl(service, services, parent); } } diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index e56495632..50fdad16f 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -42,8 +42,8 @@ namespace Discord.Commands } - public static Task> BuildAsync(CommandService service, params TypeInfo[] validTypes) => BuildAsync(validTypes, service); - public static async Task> BuildAsync(IEnumerable validTypes, CommandService service) + public static Task> BuildAsync(CommandService service, IServiceProvider services, params TypeInfo[] validTypes) => BuildAsync(validTypes, service, services); + public static async Task> BuildAsync(IEnumerable validTypes, CommandService service, IServiceProvider services) { /*if (!validTypes.Any()) throw new InvalidOperationException("Could not find any valid modules from the given selection");*/ @@ -63,11 +63,11 @@ namespace Discord.Commands var module = new ModuleBuilder(service, null); - BuildModule(module, typeInfo, service); - BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service); + BuildModule(module, typeInfo, service, services); + BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service, services); builtTypes.Add(typeInfo); - result[typeInfo.AsType()] = module.Build(service); + result[typeInfo.AsType()] = module.Build(service, services); } await service._cmdLogger.DebugAsync($"Successfully built {builtTypes.Count} modules.").ConfigureAwait(false); @@ -75,7 +75,7 @@ namespace Discord.Commands return result; } - private static void BuildSubTypes(ModuleBuilder builder, IEnumerable subTypes, List builtTypes, CommandService service) + private static void BuildSubTypes(ModuleBuilder builder, IEnumerable subTypes, List builtTypes, CommandService service, IServiceProvider services) { foreach (var typeInfo in subTypes) { @@ -87,15 +87,15 @@ namespace Discord.Commands builder.AddModule((module) => { - BuildModule(module, typeInfo, service); - BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service); + BuildModule(module, typeInfo, service, services); + BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service, services); }); builtTypes.Add(typeInfo); } } - private static void BuildModule(ModuleBuilder builder, TypeInfo typeInfo, CommandService service) + private static void BuildModule(ModuleBuilder builder, TypeInfo typeInfo, CommandService service, IServiceProvider services) { var attributes = typeInfo.GetCustomAttributes(); builder.TypeInfo = typeInfo; @@ -141,12 +141,12 @@ namespace Discord.Commands { builder.AddCommand((command) => { - BuildCommand(command, typeInfo, method, service); + BuildCommand(command, typeInfo, method, service, services); }); } } - private static void BuildCommand(CommandBuilder builder, TypeInfo typeInfo, MethodInfo method, CommandService service) + private static void BuildCommand(CommandBuilder builder, TypeInfo typeInfo, MethodInfo method, CommandService service, IServiceProvider serviceprovider) { var attributes = method.GetCustomAttributes(); @@ -192,7 +192,7 @@ namespace Discord.Commands { builder.AddParameter((parameter) => { - BuildParameter(parameter, paramInfo, pos++, count, service); + BuildParameter(parameter, paramInfo, pos++, count, service, serviceprovider); }); } @@ -228,7 +228,7 @@ namespace Discord.Commands builder.Callback = ExecuteCallback; } - private static void BuildParameter(ParameterBuilder builder, System.Reflection.ParameterInfo paramInfo, int position, int count, CommandService service) + private static void BuildParameter(ParameterBuilder builder, System.Reflection.ParameterInfo paramInfo, int position, int count, CommandService service, IServiceProvider services) { var attributes = paramInfo.GetCustomAttributes(); var paramType = paramInfo.ParameterType; @@ -246,7 +246,7 @@ namespace Discord.Commands builder.Summary = summary.Text; break; case OverrideTypeReaderAttribute typeReader: - builder.TypeReader = GetTypeReader(service, paramType, typeReader.TypeReader); + builder.TypeReader = GetTypeReader(service, paramType, typeReader.TypeReader, services); break; case ParamArrayAttribute _: builder.IsMultiple = true; @@ -286,7 +286,7 @@ namespace Discord.Commands } } - private static TypeReader GetTypeReader(CommandService service, Type paramType, Type typeReaderType) + private static TypeReader GetTypeReader(CommandService service, Type paramType, Type typeReaderType, IServiceProvider services) { var readers = service.GetTypeReaders(paramType); TypeReader reader = null; @@ -297,7 +297,7 @@ namespace Discord.Commands } //We dont have a cached type reader, create one - reader = ReflectionUtils.CreateObject(typeReaderType.GetTypeInfo(), service, service._serviceProvider); + reader = ReflectionUtils.CreateObject(typeReaderType.GetTypeInfo(), service, services); service.AddTypeReader(paramType, reader); return reader; diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index c7d3e4011..8b7d69c31 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -28,7 +28,7 @@ namespace Discord.Commands private readonly HashSet _moduleDefs; private readonly CommandMap _map; - internal readonly IServiceProvider _serviceProvider; + //internal readonly IServiceProvider _serviceProvider; internal readonly bool _caseSensitive, _throwOnError, _ignoreExtraArgs; internal readonly char _separatorChar; @@ -43,10 +43,6 @@ namespace Discord.Commands public CommandService() : this(new CommandServiceConfig()) { } public CommandService(CommandServiceConfig config) { - _serviceProvider = config.ServiceProvider - ?? config.ServiceProviderFactory?.Invoke(this) - ?? EmptyServiceProvider.Instance; - _caseSensitive = config.CaseSensitiveCommands; _throwOnError = config.ThrowOnError; _ignoreExtraArgs = config.IgnoreExtraArgs; @@ -81,6 +77,10 @@ namespace Discord.Commands entityTypeReaders.Add(new Tuple(typeof(IRole), typeof(RoleTypeReader<>))); entityTypeReaders.Add(new Tuple(typeof(IUser), typeof(UserTypeReader<>))); _entityTypeReaders = entityTypeReaders.ToImmutable(); + + //_serviceProvider = config.ServiceProvider + // ?? config.ServiceProviderFactory?.Invoke(this) + // ?? EmptyServiceProvider.Instance; } //Modules @@ -101,8 +101,8 @@ namespace Discord.Commands _moduleLock.Release(); } } - public Task AddModuleAsync() => AddModuleAsync(typeof(T)); - public async Task AddModuleAsync(Type type) + public Task AddModuleAsync(IServiceProvider services) => AddModuleAsync(typeof(T), services); + public async Task AddModuleAsync(Type type, IServiceProvider services) { await _moduleLock.WaitAsync().ConfigureAwait(false); try @@ -112,7 +112,7 @@ namespace Discord.Commands if (_typedModuleDefs.ContainsKey(type)) throw new ArgumentException($"This module has already been added."); - var module = (await ModuleClassBuilder.BuildAsync(this, typeInfo).ConfigureAwait(false)).FirstOrDefault(); + var module = (await ModuleClassBuilder.BuildAsync(this, services, typeInfo).ConfigureAwait(false)).FirstOrDefault(); if (module.Value == default(ModuleInfo)) throw new InvalidOperationException($"Could not build the module {type.FullName}, did you pass an invalid type?"); @@ -126,13 +126,13 @@ namespace Discord.Commands _moduleLock.Release(); } } - public async Task> AddModulesAsync(Assembly assembly) + public async Task> AddModulesAsync(Assembly assembly, IServiceProvider services) { await _moduleLock.WaitAsync().ConfigureAwait(false); try { var types = await ModuleClassBuilder.SearchAsync(assembly, this).ConfigureAwait(false); - var moduleDefs = await ModuleClassBuilder.BuildAsync(types, this).ConfigureAwait(false); + var moduleDefs = await ModuleClassBuilder.BuildAsync(types, this, services).ConfigureAwait(false); foreach (var info in moduleDefs) { @@ -280,12 +280,13 @@ namespace Discord.Commands return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command."); } - public Task ExecuteAsync(ICommandContext context, int argPos, /*IServiceProvider services = null,*/ MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) - => ExecuteAsync(context, context.Message.Content.Substring(argPos), /*services,*/ multiMatchHandling); - public async Task ExecuteAsync(ICommandContext context, string input, /*IServiceProvider services = null,*/ MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) + public Task ExecuteAsync(ICommandContext context, int argPos, IServiceProvider services = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) + => ExecuteAsync(context, context.Message.Content.Substring(argPos), services, multiMatchHandling); + public async Task ExecuteAsync(ICommandContext context, string input, IServiceProvider services = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) { - //services = services ?? EmptyServiceProvider.Instance; - using (var scope = _serviceProvider.CreateScope()) + services = services ?? EmptyServiceProvider.Instance; + //using (var scope = _serviceProvider.CreateScope()) + using (var scope = services.CreateScope()) { var searchResult = Search(context, input); if (!searchResult.IsSuccess) diff --git a/src/Discord.Net.Commands/CommandServiceConfig.cs b/src/Discord.Net.Commands/CommandServiceConfig.cs index 1432f4a3e..77c5b2262 100644 --- a/src/Discord.Net.Commands/CommandServiceConfig.cs +++ b/src/Discord.Net.Commands/CommandServiceConfig.cs @@ -21,10 +21,10 @@ namespace Discord.Commands /// Determines whether extra parameters should be ignored. public bool IgnoreExtraArgs { get; set; } = false; - /// Gets or sets the to use. - public IServiceProvider ServiceProvider { get; set; } = null; + ///// Gets or sets the to use. + //public IServiceProvider ServiceProvider { get; set; } = null; - /// Gets or sets a factory function for the to use. - public Func ServiceProviderFactory { get; set; } = null; + ///// Gets or sets a factory function for the to use. + //public Func ServiceProviderFactory { get; set; } = null; } } diff --git a/src/Discord.Net.Commands/Info/ModuleInfo.cs b/src/Discord.Net.Commands/Info/ModuleInfo.cs index 6ebd901c7..f8215ffc7 100644 --- a/src/Discord.Net.Commands/Info/ModuleInfo.cs +++ b/src/Discord.Net.Commands/Info/ModuleInfo.cs @@ -24,7 +24,7 @@ namespace Discord.Commands //public TypeInfo TypeInfo { get; } - internal ModuleInfo(ModuleBuilder builder, CommandService service, ModuleInfo parent = null) + internal ModuleInfo(ModuleBuilder builder, CommandService service, IServiceProvider services, ModuleInfo parent = null) { Service = service; @@ -40,7 +40,7 @@ namespace Discord.Commands Preconditions = BuildPreconditions(builder).ToImmutableArray(); Attributes = BuildAttributes(builder).ToImmutableArray(); - Submodules = BuildSubmodules(builder, service).ToImmutableArray(); + Submodules = BuildSubmodules(builder, service, services).ToImmutableArray(); } private static IEnumerable BuildAliases(ModuleBuilder builder, CommandService service) @@ -70,12 +70,12 @@ namespace Discord.Commands return result; } - private List BuildSubmodules(ModuleBuilder parent, CommandService service) + private List BuildSubmodules(ModuleBuilder parent, CommandService service, IServiceProvider services) { var result = new List(); foreach (var submodule in parent.Modules) - result.Add(submodule.Build(service, this)); + result.Add(submodule.Build(service, services, this)); return result; } From 8272c9675b0d63b4100aaf57f5067d635b68f5e6 Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Mon, 22 Jan 2018 11:39:28 +0100 Subject: [PATCH 11/13] Re-adjust quickstart --- .../getting_started/samples/intro/structure.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/docs/guides/getting_started/samples/intro/structure.cs b/docs/guides/getting_started/samples/intro/structure.cs index be8d9a8be..43e0f33cd 100644 --- a/docs/guides/getting_started/samples/intro/structure.cs +++ b/docs/guides/getting_started/samples/intro/structure.cs @@ -19,9 +19,10 @@ class Program private readonly DiscordSocketClient _client; - // Keep the CommandService around for use with commands. - // This type requires you install the Discord.Net.Commands package. + // Keep the CommandService and DI container around for use with commands. + // These two types require you install the Discord.Net.Commands package. private readonly CommandService _commands; + private readonly IServiceProvider _services; private Program() { @@ -46,12 +47,6 @@ class Program // Again, log level: LogLevel = LogSeverity.Info, - // Setup your DI container. - ServiceProvider = ConfigureServices(), - // If you have a service that's dependant on the CommandService instance, - // use ServiceProviderFactory instead. - //ServiceProviderFactory = (cs => ConfigureServices(cs)), - // There's a few more properties you can set, // for example, case-insensitive commands. CaseSensitiveCommands = false, @@ -61,6 +56,9 @@ class Program _client.Log += Logger; _commands.Log += Logger; + // Setup your DI container. + _services = ConfigureServices(), + } // If any services require the client, or the CommandService, or something else you keep on hand, @@ -131,9 +129,9 @@ class Program // Module classes MUST be marked 'public' or they will be ignored. // You also need to pass your 'IServiceProvider' instance now, // so make sure that's done before you get here. - await _commands.AddModulesAsync(Assembly.GetEntryAssembly()); + await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); // Or add Modules manually if you prefer to be a little more explicit: - await _commands.AddModuleAsync(); + await _commands.AddModuleAsync(_services); // Note that the first one is 'Modules' (plural) and the second is 'Module' (singular). // Subscribe a handler to see if a message invokes a command. From f623d19c68c5642a44898a561f77ed82d53fd103 Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Mon, 22 Jan 2018 13:15:31 +0100 Subject: [PATCH 12/13] Resolution for #937 because it's literally 4 lines of code --- src/Discord.Net.Commands/Builders/ModuleBuilder.cs | 1 + src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs | 1 + src/Discord.Net.Commands/Info/ModuleInfo.cs | 2 ++ 3 files changed, 4 insertions(+) diff --git a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs index 2100069b6..c6349712b 100644 --- a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs @@ -19,6 +19,7 @@ namespace Discord.Commands.Builders public string Name { get; set; } public string Summary { get; set; } public string Remarks { get; set; } + public string Group { get; set; } public IReadOnlyList Commands => _commands; public IReadOnlyList Modules => _submodules; diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index 50fdad16f..c0a7e9aca 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -118,6 +118,7 @@ namespace Discord.Commands break; case GroupAttribute group: builder.Name = builder.Name ?? group.Prefix; + builder.Group = group.Prefix; builder.AddAliases(group.Prefix); break; case PreconditionAttribute precondition: diff --git a/src/Discord.Net.Commands/Info/ModuleInfo.cs b/src/Discord.Net.Commands/Info/ModuleInfo.cs index f8215ffc7..135452cbf 100644 --- a/src/Discord.Net.Commands/Info/ModuleInfo.cs +++ b/src/Discord.Net.Commands/Info/ModuleInfo.cs @@ -13,6 +13,7 @@ namespace Discord.Commands public string Name { get; } public string Summary { get; } public string Remarks { get; } + public string Group { get; set; } public IReadOnlyList Aliases { get; } public IReadOnlyList Commands { get; } @@ -31,6 +32,7 @@ namespace Discord.Commands Name = builder.Name; Summary = builder.Summary; Remarks = builder.Remarks; + Group = builder.Group; Parent = parent; //TypeInfo = builder.TypeInfo; From b6a9ff57860ff3bddbad7ca850fd331529cb8e6e Mon Sep 17 00:00:00 2001 From: Joe4evr Date: Mon, 22 Jan 2018 13:17:14 +0100 Subject: [PATCH 13/13] #DERP --- src/Discord.Net.Commands/Info/ModuleInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Commands/Info/ModuleInfo.cs b/src/Discord.Net.Commands/Info/ModuleInfo.cs index 135452cbf..5a7f9208e 100644 --- a/src/Discord.Net.Commands/Info/ModuleInfo.cs +++ b/src/Discord.Net.Commands/Info/ModuleInfo.cs @@ -13,7 +13,7 @@ namespace Discord.Commands public string Name { get; } public string Summary { get; } public string Remarks { get; } - public string Group { get; set; } + public string Group { get; } public IReadOnlyList Aliases { get; } public IReadOnlyList Commands { get; }