From a551064eaf9712b3e7ba6eb2a6c6434d4ad25124 Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 10 Feb 2017 15:44:24 +0000 Subject: [PATCH 1/9] Add IDependencyMap injection for public properties --- .../Utilities/ReflectionUtils.cs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs index 27ea601bf..635f00fc3 100644 --- a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs +++ b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs @@ -19,6 +19,7 @@ namespace Discord.Commands var constructor = constructors[0]; System.Reflection.ParameterInfo[] parameters = constructor.GetParameters(); + System.Reflection.PropertyInfo[] properties = typeInfo.DeclaredProperties.Where(p => p.CanWrite).ToArray(); return (map) => { @@ -40,15 +41,33 @@ namespace Discord.Commands args[i] = arg; } + T obj; try { - return (T)constructor.Invoke(args); + obj = (T)constructor.Invoke(args); } catch (Exception ex) { throw new Exception($"Failed to create \"{typeInfo.FullName}\"", ex); } + + foreach(var property in properties) + { + object prop; + if (map == null || !map.TryGet(property.PropertyType, out prop)) + { + if (property.PropertyType == typeof(CommandService)) + prop = service; + else if (property.PropertyType == typeof(IDependencyMap)) + prop = map; + else + throw new InvalidOperationException($"Failed to create \"{typeInfo.FullName}\", dependency \"{property.PropertyType.Name}\" was not found."); + } + property.SetValue(prop, null); + } + return obj; }; } + } } From f0b4c24e8232be8cd478d4fd67ca90468815eb56 Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 10 Feb 2017 21:52:33 +0000 Subject: [PATCH 2/9] Add InjectAttribute for annotating injectable properties --- src/Discord.Net.Commands/Attributes/InjectAttribute.cs | 9 +++++++++ src/Discord.Net.Commands/Utilities/ReflectionUtils.cs | 4 +++- 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 src/Discord.Net.Commands/Attributes/InjectAttribute.cs diff --git a/src/Discord.Net.Commands/Attributes/InjectAttribute.cs b/src/Discord.Net.Commands/Attributes/InjectAttribute.cs new file mode 100644 index 000000000..b970fa8c8 --- /dev/null +++ b/src/Discord.Net.Commands/Attributes/InjectAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace Discord.Commands { + + [AttributeUsage(AttributeTargets.Property)] + public class InjectAttribute : Attribute { + } + +} diff --git a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs index 635f00fc3..5bed627df 100644 --- a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs +++ b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs @@ -19,7 +19,9 @@ namespace Discord.Commands var constructor = constructors[0]; System.Reflection.ParameterInfo[] parameters = constructor.GetParameters(); - System.Reflection.PropertyInfo[] properties = typeInfo.DeclaredProperties.Where(p => p.CanWrite).ToArray(); + System.Reflection.PropertyInfo[] properties = typeInfo.DeclaredProperties + .Where(p => p.CanWrite && p.GetCustomAttribute() != null) + .ToArray(); return (map) => { From f1df412341b926ddd17dffa506db717fd33c690a Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 10 Feb 2017 22:02:18 +0000 Subject: [PATCH 3/9] Change whitelist injection into blacklist injection --- .../Attributes/{InjectAttribute.cs => DontInjectAttribute.cs} | 2 +- src/Discord.Net.Commands/Utilities/ReflectionUtils.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/Discord.Net.Commands/Attributes/{InjectAttribute.cs => DontInjectAttribute.cs} (66%) diff --git a/src/Discord.Net.Commands/Attributes/InjectAttribute.cs b/src/Discord.Net.Commands/Attributes/DontInjectAttribute.cs similarity index 66% rename from src/Discord.Net.Commands/Attributes/InjectAttribute.cs rename to src/Discord.Net.Commands/Attributes/DontInjectAttribute.cs index b970fa8c8..bd966e129 100644 --- a/src/Discord.Net.Commands/Attributes/InjectAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/DontInjectAttribute.cs @@ -3,7 +3,7 @@ using System; namespace Discord.Commands { [AttributeUsage(AttributeTargets.Property)] - public class InjectAttribute : Attribute { + public class DontInjectAttribute : Attribute { } } diff --git a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs index 5bed627df..14192e0bd 100644 --- a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs +++ b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs @@ -20,7 +20,7 @@ namespace Discord.Commands var constructor = constructors[0]; System.Reflection.ParameterInfo[] parameters = constructor.GetParameters(); System.Reflection.PropertyInfo[] properties = typeInfo.DeclaredProperties - .Where(p => p.CanWrite && p.GetCustomAttribute() != null) + .Where(p => p.CanWrite && p.GetCustomAttribute() == null) .ToArray(); return (map) => From bb9568607839c89cb4f2c61215fb3c62416b8f4f Mon Sep 17 00:00:00 2001 From: james7132 Date: Fri, 10 Feb 2017 22:11:06 +0000 Subject: [PATCH 4/9] Update docs to reflect the change. --- docs/guides/commands.md | 199 ++++++++++++----------- docs/guides/samples/dependency_module.cs | 20 ++- 2 files changed, 120 insertions(+), 99 deletions(-) diff --git a/docs/guides/commands.md b/docs/guides/commands.md index 4c5100946..71fdc58e9 100644 --- a/docs/guides/commands.md +++ b/docs/guides/commands.md @@ -5,16 +5,16 @@ ## Setup -To use Commands, you must create a [Commands Service] and a +To use Commands, you must create a [Commands Service] and a Command Handler. -Included below is a very bare-bones Command Handler. You can extend -your Command Handler as much as you like, however the below is the +Included below is a very bare-bones Command Handler. You can extend +your Command Handler as much as you like, however the below is the bare minimum. -The CommandService optionally will accept a [CommandServiceConfig], -which _does_ set a few default values for you. It is recommended to -look over the properties in [CommandServiceConfig], and their default +The CommandService optionally will accept a [CommandServiceConfig], +which _does_ set a few default values for you. It is recommended to +look over the properties in [CommandServiceConfig], and their default values. [!code-csharp[Command Handler](samples/command_handler.cs)] @@ -22,38 +22,38 @@ values. [Command Service]: xref:Discord.Commands.CommandService [CommandServiceConfig]: xref:Discord.Commands.CommandServiceConfig -## With Attributes +## With Attributes -In 1.0, Commands can be defined ahead of time, with attributes, or +In 1.0, Commands can be defined ahead of time, with attributes, or at runtime, with builders. -For most bots, ahead-of-time commands should be all you need, and this +For most bots, ahead-of-time commands should be all you need, and this is the recommended method of defining commands. -### Modules +### Modules The first step to creating commands is to create a _module_. -Modules are an organizational pattern that allow you to write your -commands in different classes, and have them automatically loaded. +Modules are an organizational pattern that allow you to write your +commands in different classes, and have them automatically loaded. -Discord.Net's implementation of Modules is influenced heavily from -ASP.Net Core's Controller pattern. This means that the lifetime of a +Discord.Net's implementation of Modules is influenced heavily from +ASP.Net Core's Controller pattern. This means that the lifetime of a module instance is only as long as the command being invoked. -**Avoid using long-running code** in your modules whereever possible. -You should **not** be implementing very much logic into your modules; +**Avoid using long-running code** in your modules whereever possible. +You should **not** be implementing very much logic into your modules; outsource to a service for that. -If you are unfamiliar with Inversion of Control, it is recommended to -read the MSDN article on [IoC] and [Dependency Injection]. +If you are unfamiliar with Inversion of Control, it is recommended to +read the MSDN article on [IoC] and [Dependency Injection]. -To begin, create a new class somewhere in your project, and +To begin, create a new class somewhere in your project, and inherit the class from [ModuleBase]. This class **must** be `public`. >[!NOTE] ->[ModuleBase] is an _abstract_ class, meaning that you may extend it ->or override it as you see fit. Your module may inherit from any +>[ModuleBase] is an _abstract_ class, meaning that you may extend it +>or override it as you see fit. Your module may inherit from any >extension of ModuleBase. By now, your module should look like this: @@ -63,36 +63,36 @@ By now, your module should look like this: [Dependency Injection]: https://msdn.microsoft.com/en-us/library/ff921152.aspx [ModuleBase]: xref:Discord.Commands.ModuleBase`1 -### Adding Commands +### Adding Commands -The next step to creating commands, is actually creating commands. +The next step to creating commands, is actually creating commands. -To create a command, add a method to your module of type `Task`. -Typically, you will want to mark this method as `async`, although it is -not required. +To create a command, add a method to your module of type `Task`. +Typically, you will want to mark this method as `async`, although it is +not required. -Adding parameters to a command is done by adding parameters to the +Adding parameters to a command is done by adding parameters to the parent Task. -For example, to take an integer as an argument, add `int arg`. To take -a user as an argument, add `IUser user`. In 1.0, a command can accept -nearly any type of argument; a full list of types that are parsed by +For example, to take an integer as an argument, add `int arg`. To take +a user as an argument, add `IUser user`. In 1.0, a command can accept +nearly any type of argument; a full list of types that are parsed by default can be found in the below section on _Type Readers_. -Parameters, by default, are always required. To make a parameter -optional, give it a default value. To accept a comma-separated list, -set the parameter to `params Type[]`. +Parameters, by default, are always required. To make a parameter +optional, give it a default value. To accept a comma-separated list, +set the parameter to `params Type[]`. -Should a parameter include spaces, it **must** be wrapped in quotes. -For example, for a command with a parameter `string food`, you would -execute it with `!favoritefood "Key Lime Pie"`. +Should a parameter include spaces, it **must** be wrapped in quotes. +For example, for a command with a parameter `string food`, you would +execute it with `!favoritefood "Key Lime Pie"`. -If you would like a parameter to parse until the end of a command, -flag the parameter with the [RemainderAttribute]. This will allow a +If you would like a parameter to parse until the end of a command, +flag the parameter with the [RemainderAttribute]. This will allow a user to invoke a command without wrapping a parameter in quotes. -Finally, flag your command with the [CommandAttribute]. (You must -specify a name for this command, except for when it is part of a +Finally, flag your command with the [CommandAttribute]. (You must +specify a name for this command, except for when it is part of a module group - see below). [RemainderAttribute]: xref:Discord.Commands.RemainderAttribute @@ -100,52 +100,52 @@ module group - see below). ### Command Overloads -You may add overloads of your commands, and the command parser will +You may add overloads of your commands, and the command parser will automatically pick up on it. -If, for whatever reason, you have too commands which are ambiguous to -each other, you may use the @Discord.Commands.PriorityAttribute to -specify which should be tested before the other. +If, for whatever reason, you have too commands which are ambiguous to +each other, you may use the @Discord.Commands.PriorityAttribute to +specify which should be tested before the other. -Priority's are sorted in ascending order; the higher priority will be +Priority's are sorted in ascending order; the higher priority will be called first. -### CommandContext +### CommandContext -Every command can access the execution context through the [Context] -property on [ModuleBase]. CommandContext allows you to access the -message, channel, guild, and user that the command was invoked from, -as well as the underlying discord client the command was invoked from. +Every command can access the execution context through the [Context] +property on [ModuleBase]. CommandContext allows you to access the +message, channel, guild, and user that the command was invoked from, +as well as the underlying discord client the command was invoked from. -Different types of Contexts may be specified using the generic variant -of [ModuleBase]. When using a [SocketCommandContext], for example, -the properties on this context will already be Socket entities. You +Different types of Contexts may be specified using the generic variant +of [ModuleBase]. When using a [SocketCommandContext], for example, +the properties on this context will already be Socket entities. You will not need to cast them. -To reply to messages, you may also invoke [ReplyAsync], instead of +To reply to messages, you may also invoke [ReplyAsync], instead of accessing the channel through the [Context] and sending a message. [Context]: xref:Discord.Commands.ModuleBase`1#Discord_Commands_ModuleBase_1_Context [SocketCommandContext]: xref:Discord.Commands.SocketCommandContext >![WARNING] ->Contexts should **NOT** be mixed! You cannot have one module that +>Contexts should **NOT** be mixed! You cannot have one module that >uses CommandContext, and another that uses SocketCommandContext. -### Example Module +### Example Module At this point, your module should look comparable to this example: [!code-csharp[Example Module](samples/module.cs)] #### Loading Modules Automatically -The Command Service can automatically discover all classes in an +The Command Service can automatically discover all classes in an Assembly that inherit [ModuleBase], and load them. -To opt a module out of auto-loading, flag it with +To opt a module out of auto-loading, flag it with [DontAutoLoadAttribute] -Invoke [CommandService.AddModulesAsync] to discover modules and +Invoke [CommandService.AddModulesAsync] to discover modules and install them. [DontAutoLoadAttribute]: xref:Discord.Commands.DontAutoLoadAttribute @@ -153,33 +153,38 @@ install them. #### Loading Modules Manually -To manually load a module, invoke [CommandService.AddModuleAsync], -by passing in the generic type of your module, and optionally +To manually load a module, invoke [CommandService.AddModuleAsync], +by passing in the generic type of your module, and optionally a dependency map. [CommandService.AddModuleAsync]: xref:Discord.Commands.CommandService#Discord_Commands_CommandService_AddModuleAsync__1 ### Module Constructors -Modules are constructed using Dependency Injection. Any parameters -that are placed in the constructor must be injected into an -@Discord.Commands.IDependencyMap. Alternatively, you may accept an +Modules are constructed using Dependency Injection. Any parameters +that are placed in the constructor must be injected into an +@Discord.Commands.IDependencyMap. Alternatively, you may accept an IDependencyMap as an argument and extract services yourself. +### Module Properties + +Modules with public settable properties will be have them injected after +construction. + ### Module Groups -Module Groups allow you to create a module where commands are prefixed. -To create a group, flag a module with the +Module Groups allow you to create a module where commands are prefixed. +To create a group, flag a module with the @Discord.Commands.GroupAttribute -Module groups also allow you to create **nameless commands**, where the -[CommandAttribute] is configured with no name. In this case, the +Module groups also allow you to create **nameless commands**, where the +[CommandAttribute] is configured with no name. In this case, the command will inherit the name of the group it belongs to. ### Submodules -Submodules are modules that reside within another module. Typically, -submodules are used to create nested groups (although not required to +Submodules are modules that reside within another module. Typically, +submodules are used to create nested groups (although not required to create nested groups). [!code-csharp[Groups and Submodules](samples/groups.cs)] @@ -190,40 +195,46 @@ create nested groups). ## Dependency Injection -The commands service is bundled with a very barebones Dependency -Injection service for your convienence. It is recommended that +The commands service is bundled with a very barebones Dependency +Injection service for your convienence. It is recommended that you use DI when writing your modules. ### Setup -First, you need to create an @Discord.Commands.IDependencyMap. -The library includes @Discord.Commands.DependencyMap to help with -this, however you may create your own IDependencyMap if you wish. +First, you need to create an @Discord.Commands.IDependencyMap. +The library includes @Discord.Commands.DependencyMap to help with +this, however you may create your own IDependencyMap if you wish. -Next, add the dependencies your modules will use to the map. +Next, add the dependencies your modules will use to the map. -Finally, pass the map into the `LoadAssembly` method. +Finally, pass the map into the `LoadAssembly` method. Your modules will automatically be loaded with this dependency map. [!code-csharp[DependencyMap Setup](samples/dependency_map_setup.cs)] ### Usage in Modules -In the constructor of your module, any parameters will be filled in by +In the constructor of your module, any parameters will be filled in by the @Discord.Commands.IDependencyMap you pass into `LoadAssembly`. +Any publicly settable properties will also be filled in the same manner. + +>[!NOTE] +> Annotating a property with the [DontInject] attribute will prevent it from +being injected. + >[!NOTE] ->If you accept `CommandService` or `IDependencyMap` as a parameter in -your constructor, these parameters will be filled by the -CommandService the module was loaded from, and the DependencyMap passed -into it, respectively. +>If you accept `CommandService` or `IDependencyMap` as a parameter in +your constructor or as an injectable property, these entries will be filled +by the CommandService the module was loaded from, and the DependencyMap passed +into it, respectively. [!code-csharp[DependencyMap in Modules](samples/dependency_module.cs)] # Preconditions -Preconditions serve as a permissions system for your commands. Keep in -mind, however, that they are not limited to _just_ permissions, and +Preconditions serve as a permissions system for your commands. Keep in +mind, however, that they are not limited to _just_ permissions, and can be as complex as you want them to be. >[!NOTE] @@ -231,7 +242,7 @@ can be as complex as you want them to be. ## Bundled Preconditions -Commands ships with four bundled preconditions; you may view their +Commands ships with four bundled preconditions; you may view their usages on their API page. - @Discord.Commands.RequireContextAttribute @@ -244,13 +255,13 @@ usages on their API page. To write your own preconditions, create a new class that inherits from @Discord.Commands.PreconditionAttribute -In order for your precondition to function, you will need to override +In order for your precondition to function, you will need to override [CheckPermissions]. Your IDE should provide an option to fill this in for you. -Return [PreconditionResult.FromSuccess] if the context met the -required parameters, otherwise return [PreconditionResult.FromError], +Return [PreconditionResult.FromSuccess] if the context met the +required parameters, otherwise return [PreconditionResult.FromError], optionally including an error message. [!code-csharp[Custom Precondition](samples/require_owner.cs)] @@ -261,7 +272,7 @@ optionally including an error message. # Type Readers -Type Readers allow you to parse different types of arguments in +Type Readers allow you to parse different types of arguments in your commands. By default, the following Types are supported arguments: @@ -282,19 +293,19 @@ By default, the following Types are supported arguments: ### Creating a Type Readers -To create a TypeReader, create a new class that imports @Discord and +To create a TypeReader, create a new class that imports @Discord and @Discord.Commands. Ensure your class inherits from @Discord.Commands.TypeReader Next, satisfy the `TypeReader` class by overriding [Read]. >[!NOTE] ->In many cases, Visual Studio can fill this in for you, using the +>In many cases, Visual Studio can fill this in for you, using the >"Implement Abstract Class" IntelliSense hint. Inside this task, add whatever logic you need to parse the input string. -Finally, return a `TypeReaderResult`. If you were able to successfully -parse the input, return `TypeReaderResult.FromSuccess(parsedInput)`. +Finally, return a `TypeReaderResult`. If you were able to successfully +parse the input, return `TypeReaderResult.FromSuccess(parsedInput)`. Otherwise, return `TypeReaderResult.FromError`. [Read]: xref:Discord.Commands.TypeReader#Discord_Commands_TypeReader_Read_Discord_Commands_CommandContext_System_String_ @@ -305,5 +316,5 @@ Otherwise, return `TypeReaderResult.FromError`. ### Installing TypeReaders -TypeReaders are not automatically discovered by the Command Service, +TypeReaders are not automatically discovered by the Command Service, and must be explicitly added. To install a TypeReader, invoke [CommandService.AddTypeReader](xref:Discord.Commands.CommandService#Discord_Commands_CommandService_AddTypeReader__1_Discord_Commands_TypeReader_). diff --git a/docs/guides/samples/dependency_module.cs b/docs/guides/samples/dependency_module.cs index 2e1d662f8..0d65a1eea 100644 --- a/docs/guides/samples/dependency_module.cs +++ b/docs/guides/samples/dependency_module.cs @@ -6,6 +6,7 @@ public class ModuleA : ModuleBase { private readonly DatabaseService _database; + // Dependencies can be injected via the constructor public ModuleA(DatabaseService database) { _database = database; @@ -20,12 +21,21 @@ public class ModuleA : ModuleBase public class ModuleB { - private CommandService _commands; - private NotificationService _notification; - + + // Public settable properties will be injected + public AnnounceService { get; set; } + + // Public properties without setters will not + public CommandService Commands { get; } + + // Public properties annotated with [DontInject] will not + [DontInject] + public NotificationService { get; set; } + public ModuleB(CommandService commands, IDependencyMap map) { - _commands = commands; + Commands = commands; _notification = map.Get(); } -} \ No newline at end of file + +} From 145ae1518b9343ba37edad93b44e94739e193778 Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 11 Feb 2017 05:48:45 +0000 Subject: [PATCH 5/9] Fix properties not being set properly on injection --- src/Discord.Net.Commands/Utilities/ReflectionUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs index 14192e0bd..e569401bc 100644 --- a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs +++ b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs @@ -65,7 +65,7 @@ namespace Discord.Commands else throw new InvalidOperationException($"Failed to create \"{typeInfo.FullName}\", dependency \"{property.PropertyType.Name}\" was not found."); } - property.SetValue(prop, null); + property.SetValue(obj, prop); } return obj; }; From 73f00eb0d7ad8da5d4ae80549388b6477f350fd6 Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 11 Feb 2017 20:12:12 +0000 Subject: [PATCH 6/9] Ensure injected properties have public setters --- src/Discord.Net.Commands/Utilities/ReflectionUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs index e569401bc..230b30312 100644 --- a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs +++ b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs @@ -20,7 +20,7 @@ namespace Discord.Commands var constructor = constructors[0]; System.Reflection.ParameterInfo[] parameters = constructor.GetParameters(); System.Reflection.PropertyInfo[] properties = typeInfo.DeclaredProperties - .Where(p => p.CanWrite && p.GetCustomAttribute() == null) + .Where(p => p.SetMethod?.IsPublic == true && p.GetCustomAttribute() == null) .ToArray(); return (map) => From 9609520a53b3f91d722b68bbe189cc419f2491b9 Mon Sep 17 00:00:00 2001 From: james7132 Date: Sat, 11 Feb 2017 20:12:53 +0000 Subject: [PATCH 7/9] Remove "Get" statement from example docs --- docs/guides/samples/dependency_module.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/guides/samples/dependency_module.cs b/docs/guides/samples/dependency_module.cs index 0d65a1eea..561b0f6ac 100644 --- a/docs/guides/samples/dependency_module.cs +++ b/docs/guides/samples/dependency_module.cs @@ -32,10 +32,9 @@ public class ModuleB [DontInject] public NotificationService { get; set; } - public ModuleB(CommandService commands, IDependencyMap map) + public ModuleB(CommandService commands) { Commands = commands; - _notification = map.Get(); } } From 29261a943a02ab0f5730866feac3fe5acc67983a Mon Sep 17 00:00:00 2001 From: james7132 Date: Wed, 15 Feb 2017 21:44:31 +0000 Subject: [PATCH 8/9] Fix typo --- docs/guides/commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/commands.md b/docs/guides/commands.md index 71fdc58e9..8f1a34db9 100644 --- a/docs/guides/commands.md +++ b/docs/guides/commands.md @@ -168,7 +168,7 @@ IDependencyMap as an argument and extract services yourself. ### Module Properties -Modules with public settable properties will be have them injected after +Modules with public settable properties will have them injected after module construction. ### Module Groups From 7476c4ca38e489e0772de61ab132ff2e657b542b Mon Sep 17 00:00:00 2001 From: Christopher F Date: Thu, 23 Feb 2017 15:47:46 -0500 Subject: [PATCH 9/9] Cleanup property injection --- .../Utilities/ReflectionUtils.cs | 38 ++++++++----------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs index 230b30312..1333b9640 100644 --- a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs +++ b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs @@ -30,17 +30,7 @@ namespace Discord.Commands for (int i = 0; i < parameters.Length; i++) { var parameter = parameters[i]; - object arg; - if (map == null || !map.TryGet(parameter.ParameterType, out arg)) - { - if (parameter.ParameterType == typeof(CommandService)) - arg = service; - else if (parameter.ParameterType == typeof(IDependencyMap)) - arg = map; - else - throw new InvalidOperationException($"Failed to create \"{typeInfo.FullName}\", dependency \"{parameter.ParameterType.Name}\" was not found."); - } - args[i] = arg; + args[i] = GetMember(parameter.ParameterType, map, service, typeInfo); } T obj; @@ -55,21 +45,25 @@ namespace Discord.Commands foreach(var property in properties) { - object prop; - if (map == null || !map.TryGet(property.PropertyType, out prop)) - { - if (property.PropertyType == typeof(CommandService)) - prop = service; - else if (property.PropertyType == typeof(IDependencyMap)) - prop = map; - else - throw new InvalidOperationException($"Failed to create \"{typeInfo.FullName}\", dependency \"{property.PropertyType.Name}\" was not found."); - } - property.SetValue(obj, prop); + property.SetValue(obj, GetMember(property.PropertyType, map, service, typeInfo)); } return obj; }; } + internal static object GetMember(Type targetType, IDependencyMap map, CommandService service, TypeInfo baseType) + { + object arg; + if (map == null || !map.TryGet(targetType, out arg)) + { + if (targetType == typeof(CommandService)) + arg = service; + else if (targetType == typeof(IDependencyMap)) + arg = map; + else + throw new InvalidOperationException($"Failed to create \"{baseType.FullName}\", dependency \"{targetType.Name}\" was not found."); + } + return arg; + } } }