| @@ -1,11 +1,12 @@ | |||||
| using System; | using System; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Microsoft.Extensions.DependencyInjection; | |||||
| namespace Discord.Commands | namespace Discord.Commands | ||||
| { | { | ||||
| [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true, Inherited = true)] | [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true, Inherited = true)] | ||||
| public abstract class ParameterPreconditionAttribute : Attribute | public abstract class ParameterPreconditionAttribute : Attribute | ||||
| { | { | ||||
| public abstract Task<PreconditionResult> CheckPermissions(ICommandContext context, ParameterInfo parameter, object value, IDependencyMap map); | |||||
| public abstract Task<PreconditionResult> CheckPermissions(ICommandContext context, ParameterInfo parameter, object value, IServiceProvider services); | |||||
| } | } | ||||
| } | } | ||||
| @@ -6,6 +6,6 @@ namespace Discord.Commands | |||||
| [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = true)] | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = true)] | ||||
| public abstract class PreconditionAttribute : Attribute | public abstract class PreconditionAttribute : Attribute | ||||
| { | { | ||||
| public abstract Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IDependencyMap map); | |||||
| public abstract Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,5 +1,6 @@ | |||||
| using System; | using System; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Microsoft.Extensions.DependencyInjection; | |||||
| namespace Discord.Commands | namespace Discord.Commands | ||||
| { | { | ||||
| @@ -41,7 +42,7 @@ namespace Discord.Commands | |||||
| GuildPermission = null; | GuildPermission = null; | ||||
| } | } | ||||
| public override async Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IDependencyMap map) | |||||
| public override async Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) | |||||
| { | { | ||||
| var guildUser = await context.Guild.GetCurrentUserAsync(); | var guildUser = await context.Guild.GetCurrentUserAsync(); | ||||
| @@ -1,5 +1,6 @@ | |||||
| using System; | using System; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Microsoft.Extensions.DependencyInjection; | |||||
| namespace Discord.Commands | namespace Discord.Commands | ||||
| { | { | ||||
| @@ -37,7 +38,7 @@ namespace Discord.Commands | |||||
| Contexts = contexts; | Contexts = contexts; | ||||
| } | } | ||||
| public override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IDependencyMap map) | |||||
| public override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) | |||||
| { | { | ||||
| bool isValid = false; | bool isValid = false; | ||||
| @@ -1,5 +1,6 @@ | |||||
| using System; | using System; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Microsoft.Extensions.DependencyInjection; | |||||
| namespace Discord.Commands | namespace Discord.Commands | ||||
| { | { | ||||
| @@ -10,7 +11,7 @@ namespace Discord.Commands | |||||
| [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] | ||||
| public class RequireOwnerAttribute : PreconditionAttribute | public class RequireOwnerAttribute : PreconditionAttribute | ||||
| { | { | ||||
| public override async Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IDependencyMap map) | |||||
| public override async Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) | |||||
| { | { | ||||
| switch (context.Client.TokenType) | switch (context.Client.TokenType) | ||||
| { | { | ||||
| @@ -1,5 +1,6 @@ | |||||
| using System; | using System; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Microsoft.Extensions.DependencyInjection; | |||||
| namespace Discord.Commands | namespace Discord.Commands | ||||
| { | { | ||||
| @@ -42,7 +43,7 @@ namespace Discord.Commands | |||||
| GuildPermission = null; | GuildPermission = null; | ||||
| } | } | ||||
| public override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IDependencyMap map) | |||||
| public override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services) | |||||
| { | { | ||||
| var guildUser = context.User as IGuildUser; | var guildUser = context.User as IGuildUser; | ||||
| @@ -2,6 +2,7 @@ | |||||
| using System.Linq; | using System.Linq; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using Microsoft.Extensions.DependencyInjection; | |||||
| namespace Discord.Commands.Builders | namespace Discord.Commands.Builders | ||||
| { | { | ||||
| @@ -12,7 +13,7 @@ namespace Discord.Commands.Builders | |||||
| private readonly List<string> _aliases; | private readonly List<string> _aliases; | ||||
| public ModuleBuilder Module { get; } | public ModuleBuilder Module { get; } | ||||
| internal Func<ICommandContext, object[], IDependencyMap, Task> Callback { get; set; } | |||||
| internal Func<ICommandContext, object[], IServiceProvider, Task> Callback { get; set; } | |||||
| public string Name { get; set; } | public string Name { get; set; } | ||||
| public string Summary { get; set; } | public string Summary { get; set; } | ||||
| @@ -35,7 +36,7 @@ namespace Discord.Commands.Builders | |||||
| _aliases = new List<string>(); | _aliases = new List<string>(); | ||||
| } | } | ||||
| //User-defined | //User-defined | ||||
| internal CommandBuilder(ModuleBuilder module, string primaryAlias, Func<ICommandContext, object[], IDependencyMap, Task> callback) | |||||
| internal CommandBuilder(ModuleBuilder module, string primaryAlias, Func<ICommandContext, object[], IServiceProvider, Task> callback) | |||||
| : this(module) | : this(module) | ||||
| { | { | ||||
| Discord.Preconditions.NotNull(primaryAlias, nameof(primaryAlias)); | Discord.Preconditions.NotNull(primaryAlias, nameof(primaryAlias)); | ||||
| @@ -1,6 +1,7 @@ | |||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Microsoft.Extensions.DependencyInjection; | |||||
| namespace Discord.Commands.Builders | namespace Discord.Commands.Builders | ||||
| { | { | ||||
| @@ -73,7 +74,7 @@ namespace Discord.Commands.Builders | |||||
| _preconditions.Add(precondition); | _preconditions.Add(precondition); | ||||
| return this; | return this; | ||||
| } | } | ||||
| public ModuleBuilder AddCommand(string primaryAlias, Func<ICommandContext, object[], IDependencyMap, Task> callback, Action<CommandBuilder> createFunc) | |||||
| public ModuleBuilder AddCommand(string primaryAlias, Func<ICommandContext, object[], IServiceProvider, Task> callback, Action<CommandBuilder> createFunc) | |||||
| { | { | ||||
| var builder = new CommandBuilder(this, primaryAlias, callback); | var builder = new CommandBuilder(this, primaryAlias, callback); | ||||
| createFunc(builder); | createFunc(builder); | ||||
| @@ -243,7 +243,7 @@ namespace Discord.Commands | |||||
| } | } | ||||
| //We dont have a cached type reader, create one | //We dont have a cached type reader, create one | ||||
| reader = ReflectionUtils.CreateObject<TypeReader>(typeReaderType.GetTypeInfo(), service, DependencyMap.Empty); | |||||
| reader = ReflectionUtils.CreateObject<TypeReader>(typeReaderType.GetTypeInfo(), service, EmptyServiceProvider.Instance); | |||||
| service.AddTypeReader(paramType, reader); | service.AddTypeReader(paramType, reader); | ||||
| return reader; | return reader; | ||||
| @@ -1,5 +1,7 @@ | |||||
| using System.Collections.Generic; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Microsoft.Extensions.DependencyInjection; | |||||
| namespace Discord.Commands | namespace Discord.Commands | ||||
| { | { | ||||
| @@ -14,13 +16,13 @@ namespace Discord.Commands | |||||
| Alias = alias; | Alias = alias; | ||||
| } | } | ||||
| public Task<PreconditionResult> CheckPreconditionsAsync(ICommandContext context, IDependencyMap map = null) | |||||
| => Command.CheckPreconditionsAsync(context, map); | |||||
| public Task<PreconditionResult> CheckPreconditionsAsync(ICommandContext context, IServiceProvider services = null) | |||||
| => Command.CheckPreconditionsAsync(context, services); | |||||
| public Task<ParseResult> ParseAsync(ICommandContext context, SearchResult searchResult, PreconditionResult? preconditionResult = null) | public Task<ParseResult> ParseAsync(ICommandContext context, SearchResult searchResult, PreconditionResult? preconditionResult = null) | ||||
| => Command.ParseAsync(context, Alias.Length, searchResult, preconditionResult); | => Command.ParseAsync(context, Alias.Length, searchResult, preconditionResult); | ||||
| public Task<ExecuteResult> ExecuteAsync(ICommandContext context, IEnumerable<object> argList, IEnumerable<object> paramList, IDependencyMap map) | |||||
| => Command.ExecuteAsync(context, argList, paramList, map); | |||||
| public Task<ExecuteResult> ExecuteAsync(ICommandContext context, ParseResult parseResult, IDependencyMap map) | |||||
| => Command.ExecuteAsync(context, parseResult, map); | |||||
| public Task<ExecuteResult> ExecuteAsync(ICommandContext context, IEnumerable<object> argList, IEnumerable<object> paramList, IServiceProvider services) | |||||
| => Command.ExecuteAsync(context, argList, paramList, services); | |||||
| public Task<ExecuteResult> ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services) | |||||
| => Command.ExecuteAsync(context, parseResult, services); | |||||
| } | } | ||||
| } | } | ||||
| @@ -8,6 +8,7 @@ using System.Linq; | |||||
| using System.Reflection; | using System.Reflection; | ||||
| using System.Threading; | using System.Threading; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Microsoft.Extensions.DependencyInjection; | |||||
| namespace Discord.Commands | namespace Discord.Commands | ||||
| { | { | ||||
| @@ -247,11 +248,11 @@ namespace Discord.Commands | |||||
| return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command."); | return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command."); | ||||
| } | } | ||||
| public Task<IResult> ExecuteAsync(ICommandContext context, int argPos, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) | |||||
| => ExecuteAsync(context, context.Message.Content.Substring(argPos), dependencyMap, multiMatchHandling); | |||||
| public async Task<IResult> ExecuteAsync(ICommandContext context, string input, IDependencyMap dependencyMap = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) | |||||
| public Task<IResult> ExecuteAsync(ICommandContext context, int argPos, IServiceProvider services = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) | |||||
| => ExecuteAsync(context, context.Message.Content.Substring(argPos), services, multiMatchHandling); | |||||
| public async Task<IResult> ExecuteAsync(ICommandContext context, string input, IServiceProvider services = null, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) | |||||
| { | { | ||||
| dependencyMap = dependencyMap ?? DependencyMap.Empty; | |||||
| services = services ?? EmptyServiceProvider.Instance; | |||||
| var searchResult = Search(context, input); | var searchResult = Search(context, input); | ||||
| if (!searchResult.IsSuccess) | if (!searchResult.IsSuccess) | ||||
| @@ -260,7 +261,7 @@ namespace Discord.Commands | |||||
| var commands = searchResult.Commands; | var commands = searchResult.Commands; | ||||
| for (int i = 0; i < commands.Count; i++) | for (int i = 0; i < commands.Count; i++) | ||||
| { | { | ||||
| var preconditionResult = await commands[i].CheckPreconditionsAsync(context, dependencyMap).ConfigureAwait(false); | |||||
| var preconditionResult = await commands[i].CheckPreconditionsAsync(context, services).ConfigureAwait(false); | |||||
| if (!preconditionResult.IsSuccess) | if (!preconditionResult.IsSuccess) | ||||
| { | { | ||||
| if (commands.Count == 1) | if (commands.Count == 1) | ||||
| @@ -294,10 +295,19 @@ namespace Discord.Commands | |||||
| } | } | ||||
| } | } | ||||
| return await commands[i].ExecuteAsync(context, parseResult, dependencyMap).ConfigureAwait(false); | |||||
| return await commands[i].ExecuteAsync(context, parseResult, services).ConfigureAwait(false); | |||||
| } | } | ||||
| return SearchResult.FromError(CommandError.UnknownCommand, "This input does not match any overload."); | return SearchResult.FromError(CommandError.UnknownCommand, "This input does not match any overload."); | ||||
| } | } | ||||
| public ServiceCollection CreateServiceCollection() | |||||
| { | |||||
| var serviceCollection = new ServiceCollection(); | |||||
| serviceCollection.AddSingleton<CommandService>(this); | |||||
| serviceCollection.AddSingleton<IServiceCollection>(serviceCollection); | |||||
| serviceCollection.AddSingleton<ServiceCollection>(serviceCollection); | |||||
| return serviceCollection; | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,102 +0,0 @@ | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| namespace Discord.Commands | |||||
| { | |||||
| public class DependencyMap : IDependencyMap | |||||
| { | |||||
| private static readonly Type[] _typeBlacklist = new[] { | |||||
| typeof(IDependencyMap), | |||||
| typeof(CommandService) | |||||
| }; | |||||
| private Dictionary<Type, Func<object>> map; | |||||
| public static DependencyMap Empty => new DependencyMap(); | |||||
| public DependencyMap() | |||||
| { | |||||
| map = new Dictionary<Type, Func<object>>(); | |||||
| } | |||||
| /// <inheritdoc /> | |||||
| public void Add<T>(T obj) where T : class | |||||
| => AddFactory(() => obj); | |||||
| /// <inheritdoc /> | |||||
| public bool TryAdd<T>(T obj) where T : class | |||||
| => TryAddFactory(() => obj); | |||||
| /// <inheritdoc /> | |||||
| public void AddTransient<T>() where T : class, new() | |||||
| => AddFactory(() => new T()); | |||||
| /// <inheritdoc /> | |||||
| public bool TryAddTransient<T>() where T : class, new() | |||||
| => TryAddFactory(() => new T()); | |||||
| /// <inheritdoc /> | |||||
| public void AddTransient<TKey, TImpl>() where TKey : class | |||||
| where TImpl : class, TKey, new() | |||||
| => AddFactory<TKey>(() => new TImpl()); | |||||
| public bool TryAddTransient<TKey, TImpl>() where TKey : class | |||||
| where TImpl : class, TKey, new() | |||||
| => TryAddFactory<TKey>(() => new TImpl()); | |||||
| /// <inheritdoc /> | |||||
| public void AddFactory<T>(Func<T> factory) where T : class | |||||
| { | |||||
| if (!TryAddFactory(factory)) | |||||
| throw new InvalidOperationException($"The dependency map already contains \"{typeof(T).FullName}\""); | |||||
| } | |||||
| /// <inheritdoc /> | |||||
| public bool TryAddFactory<T>(Func<T> factory) where T : class | |||||
| { | |||||
| var type = typeof(T); | |||||
| if (_typeBlacklist.Contains(type) || map.ContainsKey(type)) | |||||
| return false; | |||||
| map.Add(type, factory); | |||||
| return true; | |||||
| } | |||||
| /// <inheritdoc /> | |||||
| public T Get<T>() where T : class | |||||
| { | |||||
| return (T)Get(typeof(T)); | |||||
| } | |||||
| /// <inheritdoc /> | |||||
| public object Get(Type t) | |||||
| { | |||||
| object result; | |||||
| if (!TryGet(t, out result)) | |||||
| throw new KeyNotFoundException($"The dependency map does not contain \"{t.FullName}\""); | |||||
| else | |||||
| return result; | |||||
| } | |||||
| /// <inheritdoc /> | |||||
| public bool TryGet<T>(out T result) where T : class | |||||
| { | |||||
| object untypedResult; | |||||
| if (TryGet(typeof(T), out untypedResult)) | |||||
| { | |||||
| result = (T)untypedResult; | |||||
| return true; | |||||
| } | |||||
| else | |||||
| { | |||||
| result = default(T); | |||||
| return false; | |||||
| } | |||||
| } | |||||
| /// <inheritdoc /> | |||||
| public bool TryGet(Type t, out object result) | |||||
| { | |||||
| Func<object> func; | |||||
| if (map.TryGetValue(t, out func)) | |||||
| { | |||||
| result = func(); | |||||
| return true; | |||||
| } | |||||
| result = null; | |||||
| return false; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,89 +0,0 @@ | |||||
| using System; | |||||
| namespace Discord.Commands | |||||
| { | |||||
| public interface IDependencyMap | |||||
| { | |||||
| /// <summary> | |||||
| /// Add an instance of a service to be injected. | |||||
| /// </summary> | |||||
| /// <typeparam name="T">The type of service.</typeparam> | |||||
| /// <param name="obj">The instance of a service.</param> | |||||
| void Add<T>(T obj) where T : class; | |||||
| /// <summary> | |||||
| /// Tries to add an instance of a service to be injected. | |||||
| /// </summary> | |||||
| /// <typeparam name="T">The type of service.</typeparam> | |||||
| /// <param name="obj">The instance of a service.</param> | |||||
| /// <returns>A bool, indicating if the service was successfully added to the DependencyMap.</returns> | |||||
| bool TryAdd<T>(T obj) where T : class; | |||||
| /// <summary> | |||||
| /// Add a service that will be injected by a new instance every time. | |||||
| /// </summary> | |||||
| /// <typeparam name="T">The type of instance to inject.</typeparam> | |||||
| void AddTransient<T>() where T : class, new(); | |||||
| /// <summary> | |||||
| /// Tries to add a service that will be injected by a new instance every time. | |||||
| /// </summary> | |||||
| /// <typeparam name="T">The type of instance to inject.</typeparam> | |||||
| /// <returns>A bool, indicating if the service was successfully added to the DependencyMap.</returns> | |||||
| bool TryAddTransient<T>() where T : class, new(); | |||||
| /// <summary> | |||||
| /// Add a service that will be injected by a new instance every time. | |||||
| /// </summary> | |||||
| /// <typeparam name="TKey">The type to look for when injecting.</typeparam> | |||||
| /// <typeparam name="TImpl">The type to inject when injecting.</typeparam> | |||||
| /// <example> | |||||
| /// map.AddTransient<IService, Service> | |||||
| /// </example> | |||||
| void AddTransient<TKey, TImpl>() where TKey: class where TImpl : class, TKey, new(); | |||||
| /// <summary> | |||||
| /// Tries to add a service that will be injected by a new instance every time. | |||||
| /// </summary> | |||||
| /// <typeparam name="TKey">The type to look for when injecting.</typeparam> | |||||
| /// <typeparam name="TImpl">The type to inject when injecting.</typeparam> | |||||
| /// <returns>A bool, indicating if the service was successfully added to the DependencyMap.</returns> | |||||
| bool TryAddTransient<TKey, TImpl>() where TKey : class where TImpl : class, TKey, new(); | |||||
| /// <summary> | |||||
| /// Add a service that will be injected by a factory. | |||||
| /// </summary> | |||||
| /// <typeparam name="T">The type to look for when injecting.</typeparam> | |||||
| /// <param name="factory">The factory that returns a type of this service.</param> | |||||
| void AddFactory<T>(Func<T> factory) where T : class; | |||||
| /// <summary> | |||||
| /// Tries to add a service that will be injected by a factory. | |||||
| /// </summary> | |||||
| /// <typeparam name="T">The type to look for when injecting.</typeparam> | |||||
| /// <param name="factory">The factory that returns a type of this service.</param> | |||||
| /// <returns>A bool, indicating if the service was successfully added to the DependencyMap.</returns> | |||||
| bool TryAddFactory<T>(Func<T> factory) where T : class; | |||||
| /// <summary> | |||||
| /// Pull an object from the map. | |||||
| /// </summary> | |||||
| /// <typeparam name="T">The type of service.</typeparam> | |||||
| /// <returns>An instance of this service.</returns> | |||||
| T Get<T>() where T : class; | |||||
| /// <summary> | |||||
| /// Try to pull an object from the map. | |||||
| /// </summary> | |||||
| /// <typeparam name="T">The type of service.</typeparam> | |||||
| /// <param name="result">The instance of this service.</param> | |||||
| /// <returns>Whether or not this object could be found in the map.</returns> | |||||
| bool TryGet<T>(out T result) where T : class; | |||||
| /// <summary> | |||||
| /// Pull an object from the map. | |||||
| /// </summary> | |||||
| /// <param name="t">The type of service.</param> | |||||
| /// <returns>An instance of this service.</returns> | |||||
| object Get(Type t); | |||||
| /// <summary> | |||||
| /// Try to pull an object from the map. | |||||
| /// </summary> | |||||
| /// <param name="t">The type of service.</param> | |||||
| /// <param name="result">An instance of this service.</param> | |||||
| /// <returns>Whether or not this object could be found in the map.</returns> | |||||
| bool TryGet(Type t, out object result); | |||||
| } | |||||
| } | |||||
| @@ -9,4 +9,7 @@ | |||||
| <ItemGroup> | <ItemGroup> | ||||
| <ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> | <ProjectReference Include="..\Discord.Net.Core\Discord.Net.Core.csproj" /> | ||||
| </ItemGroup> | </ItemGroup> | ||||
| <ItemGroup> | |||||
| <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="1.1.0" /> | |||||
| </ItemGroup> | |||||
| </Project> | </Project> | ||||
| @@ -0,0 +1,11 @@ | |||||
| using System; | |||||
| namespace Discord.Commands | |||||
| { | |||||
| internal class EmptyServiceProvider : IServiceProvider | |||||
| { | |||||
| public static readonly EmptyServiceProvider Instance = new EmptyServiceProvider(); | |||||
| public object GetService(Type serviceType) => null; | |||||
| } | |||||
| } | |||||
| @@ -8,6 +8,7 @@ using System.Linq; | |||||
| using System.Reflection; | using System.Reflection; | ||||
| using System.Runtime.ExceptionServices; | using System.Runtime.ExceptionServices; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Microsoft.Extensions.DependencyInjection; | |||||
| namespace Discord.Commands | namespace Discord.Commands | ||||
| { | { | ||||
| @@ -17,7 +18,7 @@ namespace Discord.Commands | |||||
| private static readonly System.Reflection.MethodInfo _convertParamsMethod = typeof(CommandInfo).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList)); | private static readonly System.Reflection.MethodInfo _convertParamsMethod = typeof(CommandInfo).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList)); | ||||
| private static readonly ConcurrentDictionary<Type, Func<IEnumerable<object>, object>> _arrayConverters = new ConcurrentDictionary<Type, Func<IEnumerable<object>, object>>(); | private static readonly ConcurrentDictionary<Type, Func<IEnumerable<object>, object>> _arrayConverters = new ConcurrentDictionary<Type, Func<IEnumerable<object>, object>>(); | ||||
| private readonly Func<ICommandContext, object[], IDependencyMap, Task> _action; | |||||
| private readonly Func<ICommandContext, object[], IServiceProvider, Task> _action; | |||||
| public ModuleInfo Module { get; } | public ModuleInfo Module { get; } | ||||
| public string Name { get; } | public string Name { get; } | ||||
| @@ -63,21 +64,20 @@ namespace Discord.Commands | |||||
| _action = builder.Callback; | _action = builder.Callback; | ||||
| } | } | ||||
| public async Task<PreconditionResult> CheckPreconditionsAsync(ICommandContext context, IDependencyMap map = null) | |||||
| public async Task<PreconditionResult> CheckPreconditionsAsync(ICommandContext context, IServiceProvider services = null) | |||||
| { | { | ||||
| if (map == null) | |||||
| map = DependencyMap.Empty; | |||||
| services = services ?? EmptyServiceProvider.Instance; | |||||
| foreach (PreconditionAttribute precondition in Module.Preconditions) | foreach (PreconditionAttribute precondition in Module.Preconditions) | ||||
| { | { | ||||
| var result = await precondition.CheckPermissions(context, this, map).ConfigureAwait(false); | |||||
| var result = await precondition.CheckPermissions(context, this, services).ConfigureAwait(false); | |||||
| if (!result.IsSuccess) | if (!result.IsSuccess) | ||||
| return result; | return result; | ||||
| } | } | ||||
| foreach (PreconditionAttribute precondition in Preconditions) | foreach (PreconditionAttribute precondition in Preconditions) | ||||
| { | { | ||||
| var result = await precondition.CheckPermissions(context, this, map).ConfigureAwait(false); | |||||
| var result = await precondition.CheckPermissions(context, this, services).ConfigureAwait(false); | |||||
| if (!result.IsSuccess) | if (!result.IsSuccess) | ||||
| return result; | return result; | ||||
| } | } | ||||
| @@ -96,7 +96,7 @@ namespace Discord.Commands | |||||
| return await CommandParser.ParseArgs(this, context, input, 0).ConfigureAwait(false); | return await CommandParser.ParseArgs(this, context, input, 0).ConfigureAwait(false); | ||||
| } | } | ||||
| public Task<ExecuteResult> ExecuteAsync(ICommandContext context, ParseResult parseResult, IDependencyMap map) | |||||
| public Task<ExecuteResult> ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services) | |||||
| { | { | ||||
| if (!parseResult.IsSuccess) | if (!parseResult.IsSuccess) | ||||
| return Task.FromResult(ExecuteResult.FromError(parseResult)); | return Task.FromResult(ExecuteResult.FromError(parseResult)); | ||||
| @@ -117,12 +117,11 @@ namespace Discord.Commands | |||||
| paramList[i] = parseResult.ParamValues[i].Values.First().Value; | paramList[i] = parseResult.ParamValues[i].Values.First().Value; | ||||
| } | } | ||||
| return ExecuteAsync(context, argList, paramList, map); | |||||
| return ExecuteAsync(context, argList, paramList, services); | |||||
| } | } | ||||
| public async Task<ExecuteResult> ExecuteAsync(ICommandContext context, IEnumerable<object> argList, IEnumerable<object> paramList, IDependencyMap map) | |||||
| public async Task<ExecuteResult> ExecuteAsync(ICommandContext context, IEnumerable<object> argList, IEnumerable<object> paramList, IServiceProvider services) | |||||
| { | { | ||||
| if (map == null) | |||||
| map = DependencyMap.Empty; | |||||
| services = services ?? EmptyServiceProvider.Instance; | |||||
| try | try | ||||
| { | { | ||||
| @@ -132,7 +131,7 @@ namespace Discord.Commands | |||||
| { | { | ||||
| var parameter = Parameters[position]; | var parameter = Parameters[position]; | ||||
| var argument = args[position]; | var argument = args[position]; | ||||
| var result = await parameter.CheckPreconditionsAsync(context, argument, map).ConfigureAwait(false); | |||||
| var result = await parameter.CheckPreconditionsAsync(context, argument, services).ConfigureAwait(false); | |||||
| if (!result.IsSuccess) | if (!result.IsSuccess) | ||||
| return ExecuteResult.FromError(result); | return ExecuteResult.FromError(result); | ||||
| } | } | ||||
| @@ -140,12 +139,12 @@ namespace Discord.Commands | |||||
| switch (RunMode) | switch (RunMode) | ||||
| { | { | ||||
| case RunMode.Sync: //Always sync | case RunMode.Sync: //Always sync | ||||
| await ExecuteAsyncInternal(context, args, map).ConfigureAwait(false); | |||||
| await ExecuteAsyncInternal(context, args, services).ConfigureAwait(false); | |||||
| break; | break; | ||||
| case RunMode.Async: //Always async | case RunMode.Async: //Always async | ||||
| var t2 = Task.Run(async () => | var t2 = Task.Run(async () => | ||||
| { | { | ||||
| await ExecuteAsyncInternal(context, args, map).ConfigureAwait(false); | |||||
| await ExecuteAsyncInternal(context, args, services).ConfigureAwait(false); | |||||
| }); | }); | ||||
| break; | break; | ||||
| } | } | ||||
| @@ -157,12 +156,12 @@ namespace Discord.Commands | |||||
| } | } | ||||
| } | } | ||||
| private async Task ExecuteAsyncInternal(ICommandContext context, object[] args, IDependencyMap map) | |||||
| private async Task ExecuteAsyncInternal(ICommandContext context, object[] args, IServiceProvider services) | |||||
| { | { | ||||
| await Module.Service._cmdLogger.DebugAsync($"Executing {GetLogText(context)}").ConfigureAwait(false); | await Module.Service._cmdLogger.DebugAsync($"Executing {GetLogText(context)}").ConfigureAwait(false); | ||||
| try | try | ||||
| { | { | ||||
| await _action(context, args, map).ConfigureAwait(false); | |||||
| await _action(context, args, services).ConfigureAwait(false); | |||||
| } | } | ||||
| catch (Exception ex) | catch (Exception ex) | ||||
| { | { | ||||
| @@ -3,6 +3,7 @@ using System; | |||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| using Microsoft.Extensions.DependencyInjection; | |||||
| namespace Discord.Commands | namespace Discord.Commands | ||||
| { | { | ||||
| @@ -39,14 +40,13 @@ namespace Discord.Commands | |||||
| _reader = builder.TypeReader; | _reader = builder.TypeReader; | ||||
| } | } | ||||
| public async Task<PreconditionResult> CheckPreconditionsAsync(ICommandContext context, object arg, IDependencyMap map = null) | |||||
| public async Task<PreconditionResult> CheckPreconditionsAsync(ICommandContext context, object arg, IServiceProvider services = null) | |||||
| { | { | ||||
| if (map == null) | |||||
| map = DependencyMap.Empty; | |||||
| services = EmptyServiceProvider.Instance; | |||||
| foreach (var precondition in Preconditions) | foreach (var precondition in Preconditions) | ||||
| { | { | ||||
| var result = await precondition.CheckPermissions(context, this, arg, map).ConfigureAwait(false); | |||||
| var result = await precondition.CheckPermissions(context, this, arg, services).ConfigureAwait(false); | |||||
| if (!result.IsSuccess) | if (!result.IsSuccess) | ||||
| return result; | return result; | ||||
| } | } | ||||
| @@ -2,88 +2,79 @@ | |||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.Linq; | using System.Linq; | ||||
| using System.Reflection; | using System.Reflection; | ||||
| using Microsoft.Extensions.DependencyInjection; | |||||
| namespace Discord.Commands | namespace Discord.Commands | ||||
| { | { | ||||
| internal static class ReflectionUtils | internal static class ReflectionUtils | ||||
| { | { | ||||
| private static readonly TypeInfo objectTypeInfo = typeof(object).GetTypeInfo(); | |||||
| private static readonly TypeInfo _objectTypeInfo = typeof(object).GetTypeInfo(); | |||||
| internal static T CreateObject<T>(TypeInfo typeInfo, CommandService service, IDependencyMap map = null) | |||||
| => CreateBuilder<T>(typeInfo, service)(map); | |||||
| internal static T CreateObject<T>(TypeInfo typeInfo, CommandService commands, IServiceProvider services = null) | |||||
| => CreateBuilder<T>(typeInfo, commands)(services); | |||||
| internal static Func<IServiceProvider, T> CreateBuilder<T>(TypeInfo typeInfo, CommandService commands) | |||||
| { | |||||
| var constructor = GetConstructor(typeInfo); | |||||
| var parameters = constructor.GetParameters(); | |||||
| var properties = GetProperties(typeInfo); | |||||
| return (services) => | |||||
| { | |||||
| var args = new object[parameters.Length]; | |||||
| for (int i = 0; i < parameters.Length; i++) | |||||
| args[i] = GetMember(commands, services, parameters[i].ParameterType, typeInfo); | |||||
| var obj = InvokeConstructor<T>(constructor, args, typeInfo); | |||||
| private static System.Reflection.PropertyInfo[] GetProperties(TypeInfo typeInfo) | |||||
| foreach(var property in properties) | |||||
| property.SetValue(obj, GetMember(commands, services, property.PropertyType, typeInfo)); | |||||
| return obj; | |||||
| }; | |||||
| } | |||||
| private static T InvokeConstructor<T>(ConstructorInfo constructor, object[] args, TypeInfo ownerType) | |||||
| { | { | ||||
| var result = new List<System.Reflection.PropertyInfo>(); | |||||
| while (typeInfo != objectTypeInfo) | |||||
| try | |||||
| { | { | ||||
| foreach (var prop in typeInfo.DeclaredProperties) | |||||
| { | |||||
| if (prop.SetMethod?.IsPublic == true && prop.GetCustomAttribute<DontInjectAttribute>() == null) | |||||
| result.Add(prop); | |||||
| } | |||||
| typeInfo = typeInfo.BaseType.GetTypeInfo(); | |||||
| return (T)constructor.Invoke(args); | |||||
| } | |||||
| catch (Exception ex) | |||||
| { | |||||
| throw new Exception($"Failed to create \"{ownerType.FullName}\"", ex); | |||||
| } | } | ||||
| return result.ToArray(); | |||||
| } | } | ||||
| internal static Func<IDependencyMap, T> CreateBuilder<T>(TypeInfo typeInfo, CommandService service) | |||||
| private static ConstructorInfo GetConstructor(TypeInfo ownerType) | |||||
| { | { | ||||
| var constructors = typeInfo.DeclaredConstructors.Where(x => !x.IsStatic).ToArray(); | |||||
| var constructors = ownerType.DeclaredConstructors.Where(x => !x.IsStatic).ToArray(); | |||||
| if (constructors.Length == 0) | if (constructors.Length == 0) | ||||
| throw new InvalidOperationException($"No constructor found for \"{typeInfo.FullName}\""); | |||||
| throw new InvalidOperationException($"No constructor found for \"{ownerType.FullName}\""); | |||||
| else if (constructors.Length > 1) | else if (constructors.Length > 1) | ||||
| throw new InvalidOperationException($"Multiple constructors found for \"{typeInfo.FullName}\""); | |||||
| var constructor = constructors[0]; | |||||
| System.Reflection.ParameterInfo[] parameters = constructor.GetParameters(); | |||||
| System.Reflection.PropertyInfo[] properties = GetProperties(typeInfo) | |||||
| .Where(p => p.SetMethod?.IsPublic == true && p.GetCustomAttribute<DontInjectAttribute>() == null) | |||||
| .ToArray(); | |||||
| return (map) => | |||||
| throw new InvalidOperationException($"Multiple constructors found for \"{ownerType.FullName}\""); | |||||
| return constructors[0]; | |||||
| } | |||||
| private static System.Reflection.PropertyInfo[] GetProperties(TypeInfo ownerType) | |||||
| { | |||||
| var result = new List<System.Reflection.PropertyInfo>(); | |||||
| while (ownerType != _objectTypeInfo) | |||||
| { | { | ||||
| object[] args = new object[parameters.Length]; | |||||
| for (int i = 0; i < parameters.Length; i++) | |||||
| { | |||||
| var parameter = parameters[i]; | |||||
| args[i] = GetMember(parameter.ParameterType, map, service, typeInfo); | |||||
| } | |||||
| T obj; | |||||
| try | |||||
| { | |||||
| obj = (T)constructor.Invoke(args); | |||||
| } | |||||
| catch (Exception ex) | |||||
| { | |||||
| throw new Exception($"Failed to create \"{typeInfo.FullName}\"", ex); | |||||
| } | |||||
| foreach(var property in properties) | |||||
| foreach (var prop in ownerType.DeclaredProperties) | |||||
| { | { | ||||
| property.SetValue(obj, GetMember(property.PropertyType, map, service, typeInfo)); | |||||
| if (prop.SetMethod?.IsPublic == true && prop.GetCustomAttribute<DontInjectAttribute>() == null) | |||||
| result.Add(prop); | |||||
| } | } | ||||
| return obj; | |||||
| }; | |||||
| ownerType = ownerType.BaseType.GetTypeInfo(); | |||||
| } | |||||
| return result.ToArray(); | |||||
| } | } | ||||
| private static readonly TypeInfo _dependencyTypeInfo = typeof(IDependencyMap).GetTypeInfo(); | |||||
| internal static object GetMember(Type targetType, IDependencyMap map, CommandService service, TypeInfo baseType) | |||||
| private static object GetMember(CommandService commands, IServiceProvider services, Type memberType, TypeInfo ownerType) | |||||
| { | { | ||||
| object arg; | |||||
| if (map == null || !map.TryGet(targetType, out arg)) | |||||
| { | |||||
| if (targetType == typeof(CommandService)) | |||||
| arg = service; | |||||
| else if (targetType == typeof(IDependencyMap) || targetType == map.GetType()) | |||||
| arg = map; | |||||
| else | |||||
| throw new InvalidOperationException($"Failed to create \"{baseType.FullName}\", dependency \"{targetType.Name}\" was not found."); | |||||
| } | |||||
| return arg; | |||||
| if (memberType == typeof(CommandService)) | |||||
| return commands; | |||||
| if (memberType == typeof(IServiceProvider) || memberType == services.GetType()) | |||||
| return services; | |||||
| var service = services?.GetService(memberType); | |||||
| if (service != null) | |||||
| return service; | |||||
| throw new InvalidOperationException($"Failed to create \"{ownerType.FullName}\", dependency \"{memberType.Name}\" was not found."); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||