diff --git a/src/ConfigurationProcessor.Core/ConfigurationExtensions.cs b/src/ConfigurationProcessor.Core/ConfigurationExtensions.cs
index 133cb68..407d344 100644
--- a/src/ConfigurationProcessor.Core/ConfigurationExtensions.cs
+++ b/src/ConfigurationProcessor.Core/ConfigurationExtensions.cs
@@ -3,6 +3,8 @@
// -------------------------------------------------------------------------------------------------
using System;
+using System.Collections.Generic;
+using System.Linq;
using System.Reflection;
using ConfigurationProcessor.Core.Assemblies;
using ConfigurationProcessor.Core.Implementation;
@@ -18,23 +20,23 @@ public static class ConfigurationExtensions
///
/// Processes the configuration.
///
- /// The object type that is transformed by the configuration.
+ /// The object type that is transformed by the configuration.
/// The configuration object.
/// The object that is processed by the configuration.
/// The section in the config that will be used in the configuration.
/// Additional paths that will be searched.
- /// Candidate method name suffixes for matching.
- /// Additional methods that can be used for matching.
+ /// Factory for filtering methods.
+ /// Additional methods that can be used for matching.
/// The object for chaining.
/// Thrown when is null.
- public static TServices ProcessConfiguration(
+ public static TContext ProcessConfiguration(
this IConfiguration configuration,
- TServices context,
+ TContext context,
string configSection,
string[]? contextPaths = null,
- string[]? candidateMethodNameSuffixes = null,
- MethodInfo[]? surrogateMethods = null)
- where TServices : class
+ MethodFilterFactory? methodFilterFactory = null,
+ MethodInfo[]? additionalMethods = null)
+ where TContext : class
{
if (configuration == null)
{
@@ -45,8 +47,8 @@ public static TServices ProcessConfiguration(
configuration,
configuration.GetSection(configSection),
contextPaths ?? new string?[] { string.Empty },
- candidateMethodNameSuffixes ?? Array.Empty(),
- surrogateMethods ?? Array.Empty(),
+ methodFilterFactory,
+ additionalMethods ?? Array.Empty(),
AssemblyFinder.Auto());
return context;
}
@@ -56,26 +58,26 @@ internal static TConfig AddFromConfiguration(
IConfiguration rootConfiguration,
IConfigurationSection configurationSection,
string?[] servicePaths,
- string[] candidateMethodNameSuffixes,
- MethodInfo[] surrogateMethods,
+ MethodFilterFactory? methodFilterFactory,
+ MethodInfo[] additionalMethods,
AssemblyFinder assemblyFinder)
where TConfig : class
{
- var reader = new ConfigurationReader(configurationSection, assemblyFinder, surrogateMethods, rootConfiguration);
+ var reader = new ConfigurationReader(configurationSection, assemblyFinder, additionalMethods, rootConfiguration);
foreach (var servicePath in servicePaths)
{
if (string.IsNullOrEmpty(servicePath))
{
- reader.AddServices(builder, null, true, candidateMethodNameSuffixes);
+ reader.AddServices(builder, null, true, methodFilterFactory);
}
else if (servicePath![0] == '^')
{
- reader.AddServices(builder, servicePath.Substring(1), false, candidateMethodNameSuffixes);
+ reader.AddServices(builder, servicePath.Substring(1), false, methodFilterFactory);
}
else
{
- reader.AddServices(builder, servicePath, true, candidateMethodNameSuffixes);
+ reader.AddServices(builder, servicePath, true, methodFilterFactory);
}
}
diff --git a/src/ConfigurationProcessor.Core/Implementation/ConfigurationReader.cs b/src/ConfigurationProcessor.Core/Implementation/ConfigurationReader.cs
index 84a83b1..bac3740 100644
--- a/src/ConfigurationProcessor.Core/Implementation/ConfigurationReader.cs
+++ b/src/ConfigurationProcessor.Core/Implementation/ConfigurationReader.cs
@@ -15,20 +15,24 @@ namespace ConfigurationProcessor.Core.Implementation
{
internal abstract class ConfigurationReader
{
- private const char GenericTypeMarker = '`';
private readonly IConfigurationSection section;
- private readonly MethodInfo[] surrogateMethods;
+ private readonly MethodInfo[] additionalMethods;
private readonly AssemblyFinder assemblyFinder;
private readonly ResolutionContext resolutionContext;
private readonly IConfiguration rootConfiguration;
- protected ConfigurationReader(ResolutionContext resolutionContext, IConfiguration rootConfiguration, AssemblyFinder assemblyFinder, IConfigurationSection configSection, MethodInfo[] surrogateMethods)
+ protected ConfigurationReader(
+ ResolutionContext resolutionContext,
+ IConfiguration rootConfiguration,
+ AssemblyFinder assemblyFinder,
+ IConfigurationSection configSection,
+ MethodInfo[] additionalMethods)
{
this.resolutionContext = resolutionContext;
this.rootConfiguration = rootConfiguration;
this.assemblyFinder = assemblyFinder;
this.section = configSection;
- this.surrogateMethods = surrogateMethods;
+ this.additionalMethods = additionalMethods;
}
protected ResolutionContext ResolutionContext => this.resolutionContext;
@@ -121,16 +125,18 @@ protected void CallConfigurationMethods(
ResolutionContext resolutionContext,
Type extensionArgumentType,
ILookup methods,
- string[] candidateMethodNameSuffixes,
+ MethodFilterFactory? methodFilterFactory,
Action, MethodInfo> invoker)
{
foreach (var method in methods.SelectMany(g => g.Select(x => new { g.Key, Value = x })))
{
var typeArgs = method.Value.Item1;
var paramArgs = method.Value.Item3;
- var candidateNames = GetCandidateNames(method.Key, candidateMethodNameSuffixes);
- List configurationMethods = resolutionContext.FindConfigurationExtensionMethods(extensionArgumentType, typeArgs, candidateNames);
- configurationMethods.AddRange(surrogateMethods.Where(m => candidateNames.Contains(m.Name)));
+ methodFilterFactory ??= MethodFilterFactories.DefaultMethodFilterFactory;
+ var (methodFilter, candidateNames) = methodFilterFactory(method.Key);
+ IEnumerable configurationMethods = resolutionContext
+ .FindConfigurationExtensionMethods(method.Key, extensionArgumentType, typeArgs, candidateNames, methodFilter);
+ configurationMethods = configurationMethods.Union(additionalMethods.Where(m => methodFilter(m, method.Key))).ToList();
var suppliedArgumentNames = paramArgs.Keys;
var isCollection = suppliedArgumentNames.IsArray();
@@ -204,7 +210,6 @@ protected void CallConfigurationMethods(
else
{
var methodsByName = configurationMethods
- .Where(m => candidateNames.Contains(m.Name))
.Select(m => $"{m.Name}({string.Join(", ", m.GetParameters().Skip(1).Select(p => p.Name))})")
.ToList();
@@ -224,30 +229,6 @@ protected void CallConfigurationMethods(
}
}
}
-
- static List GetCandidateNames(string name, string[] candidateSuffixes)
- {
- var namesplit = name.Split(GenericTypeMarker);
-
- var result = new List { name };
-
- if (candidateSuffixes.Length > 0)
- {
- if (namesplit.Length > 1)
- {
- result.AddRange(candidateSuffixes.Select(x => $"{namesplit[0] + x}{GenericTypeMarker}{namesplit[1]}"));
- }
- else
- {
- result.AddRange(candidateSuffixes.Select(x => name + x));
- }
- }
-
- var withPrefix = result.Select(x => "Add" + x).ToList();
- result.AddRange(withPrefix);
-
- return result;
- }
}
private object? GetImplicitValueForNotSpecifiedKey(
@@ -287,7 +268,7 @@ static List GetCandidateNames(string name, string[] candidateSuffixes)
var methodCalls = GetMethodCalls(sourceConfigurationSection, true, excludeKeys);
- CallConfigurationMethods(currentResolutionContext, argumentType, methodCalls, Array.Empty(), (arguments, methodInfo) =>
+ CallConfigurationMethods(currentResolutionContext, argumentType, methodCalls, null, (arguments, methodInfo) =>
{
var parameters = methodInfo.GetParameters();
diff --git a/src/ConfigurationProcessor.Core/Implementation/ConfigurationReader{TConfig}.cs b/src/ConfigurationProcessor.Core/Implementation/ConfigurationReader{TConfig}.cs
index fe7a5c0..4ba4b14 100644
--- a/src/ConfigurationProcessor.Core/Implementation/ConfigurationReader{TConfig}.cs
+++ b/src/ConfigurationProcessor.Core/Implementation/ConfigurationReader{TConfig}.cs
@@ -12,18 +12,18 @@ namespace ConfigurationProcessor.Core.Implementation
internal class ConfigurationReader : ConfigurationReader, IConfigurationReader
where TConfig : class
{
- public ConfigurationReader(IConfigurationSection configSection, AssemblyFinder assemblyFinder, MethodInfo[] surrogateMethods, IConfiguration configuration = null!)
- : base(new ResolutionContext(assemblyFinder, configuration!, configSection, typeof(TConfig)), configuration, assemblyFinder, configSection, surrogateMethods)
+ public ConfigurationReader(IConfigurationSection configSection, AssemblyFinder assemblyFinder, MethodInfo[] additionalMethods, IConfiguration configuration = null!)
+ : base(new ResolutionContext(assemblyFinder, configuration!, configSection, typeof(TConfig)), configuration, assemblyFinder, configSection, additionalMethods)
{
}
- public void AddServices(TConfig builder, string? sectionName, bool getChildren, params string[] candidateMethodNameSuffixes)
+ public void AddServices(TConfig builder, string? sectionName, bool getChildren, MethodFilterFactory? methodFilterFactory)
{
var builderDirective = string.IsNullOrEmpty(sectionName) ? ConfigurationSection : ConfigurationSection.GetSection(sectionName);
if (!getChildren || builderDirective.GetChildren().Any())
{
var methodCalls = GetMethodCalls(builderDirective, getChildren);
- CallConfigurationMethods(ResolutionContext, typeof(TConfig), methodCalls, candidateMethodNameSuffixes, (arguments, methodInfo) =>
+ CallConfigurationMethods(ResolutionContext, typeof(TConfig), methodCalls, methodFilterFactory, (arguments, methodInfo) =>
{
if (methodInfo.IsStatic)
{
diff --git a/src/ConfigurationProcessor.Core/Implementation/Extensions.cs b/src/ConfigurationProcessor.Core/Implementation/Extensions.cs
index 54617d3..2aa3b25 100644
--- a/src/ConfigurationProcessor.Core/Implementation/Extensions.cs
+++ b/src/ConfigurationProcessor.Core/Implementation/Extensions.cs
@@ -103,9 +103,11 @@ public static bool IsArray(this IEnumerable suppliedArgumentNames)
public static List FindConfigurationExtensionMethods(
this ResolutionContext resolutionContext,
+ string key,
Type configType,
TypeResolver[] typeArgs,
- List candidateNames)
+ IEnumerable candidateNames,
+ MethodFilter filter)
{
IReadOnlyCollection configurationAssemblies = resolutionContext.ConfigurationAssemblies;
@@ -115,6 +117,7 @@ public static List FindConfigurationExtensionMethods(
.Where(t => t.IsSealed && t.IsAbstract && !t.IsNested))
.Union(new[] { configType.GetTypeInfo() })
.SelectMany(t => candidateNames.SelectMany(n => t.GetDeclaredMethods(n)))
+ .Where(m => filter(m, key))
.Where(m => !m.IsDefined(typeof(CompilerGeneratedAttribute), false) && m.IsPublic && ((m.IsStatic && m.IsDefined(typeof(ExtensionAttribute), false)) || m.DeclaringType == configType))
.Where(m => !m.IsStatic || m.SafeGetParameters().ElementAtOrDefault(0)?.ParameterType.IsAssignableFrom(configType) == true) // If static method, checks that the first parameter is same as the extension type
.ToList();
@@ -303,7 +306,7 @@ public static void BindMappableValues(
selectedMethods = selectedMethods.Where(m =>
{
var requiredParamCount = m.GetParameters().Count(x => !x.IsOptional);
- return requiredParamCount == suppliedArgumentNames.Count() + (m.IsStatic ? 1 : 0);
+ return requiredParamCount <= suppliedArgumentNames.Count() + (m.IsStatic ? 1 : 0);
});
if (selectedMethods.Count() > 1)
diff --git a/src/ConfigurationProcessor.Core/Implementation/IConfigurationReader.cs b/src/ConfigurationProcessor.Core/Implementation/IConfigurationReader.cs
index 88db6cc..50c4934 100644
--- a/src/ConfigurationProcessor.Core/Implementation/IConfigurationReader.cs
+++ b/src/ConfigurationProcessor.Core/Implementation/IConfigurationReader.cs
@@ -7,6 +7,6 @@ namespace ConfigurationProcessor.Core.Implementation
internal interface IConfigurationReader
where TConfig : class
{
- void AddServices(TConfig builder, string? sectionName, bool getChildren, params string[] candidateMethodNameSuffixes);
+ void AddServices(TConfig builder, string? sectionName, bool getChildren, MethodFilterFactory methodFilterFactory);
}
}
diff --git a/src/ConfigurationProcessor.Core/MethodFilterFactories.cs b/src/ConfigurationProcessor.Core/MethodFilterFactories.cs
new file mode 100644
index 0000000..fddb38d
--- /dev/null
+++ b/src/ConfigurationProcessor.Core/MethodFilterFactories.cs
@@ -0,0 +1,89 @@
+// -------------------------------------------------------------------------------------------------
+// Copyright (c) almostchristian. All rights reserved.
+// -------------------------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace ConfigurationProcessor.Core
+{
+ ///
+ /// Contains method filter factories.
+ ///
+ public static class MethodFilterFactories
+ {
+ private static bool DefaultMethodFilter(MethodInfo method, string name)
+ => true;
+
+ ///
+ /// Method filter factory that accepts methods with names like '' or 'Add'.
+ ///
+ /// The configuration name to search for.
+ /// The method filter and candidate names.
+ public static (MethodFilter Filter, IEnumerable CandidateNames) DefaultMethodFilterFactory(string name)
+ {
+ var candidates = new HashSet(StringComparer.OrdinalIgnoreCase) { name, $"Add{name}" };
+ return (DefaultMethodFilter, candidates);
+ }
+
+ ///
+ /// Creates a method filter factory with suffixes.
+ ///
+ /// The method name suffixes to search for.
+ /// The method filter factory.
+ public static MethodFilterFactory WithSuffixes(params string[] methodNameSuffixes)
+ => WithSuffixes(DefaultMethodFilter, methodNameSuffixes);
+
+ ///
+ /// Creates a method filter factory with suffixes.
+ ///
+ /// The default method filter factory.
+ /// The method name suffixes to search for.
+ /// The method filter factory.
+ public static MethodFilterFactory WithSuffixes(MethodFilter methodFilter, params string[] methodNameSuffixes)
+ => WithPrefixAndSuffixes(methodFilter, new[] { "Add" }, methodNameSuffixes);
+
+ ///
+ /// Creates a method filter factory with prefixes and suffixes.
+ ///
+ /// The default method filter factory.
+ /// The method name suffixes to search for.
+ /// The method name suffixes to search for.
+ /// The method filter factory.
+ public static MethodFilterFactory WithPrefixAndSuffixes(MethodFilter methodFilter, string[] methodNamePrefixes, string[] methodNameSuffixes)
+ {
+ return name =>
+ {
+ var candidates = GetCandidateNames(name, methodNamePrefixes, methodNameSuffixes);
+ return (methodFilter, candidates);
+ };
+
+ static List GetCandidateNames(string name, string[] methodNamePrefixes, string[] candidateSuffixes)
+ {
+ const char GenericTypeMarker = '`';
+ var namesplit = name.Split(GenericTypeMarker);
+
+ var result = new List { name };
+
+ if (candidateSuffixes.Length > 0)
+ {
+ if (namesplit.Length > 1)
+ {
+ result.AddRange(candidateSuffixes.Select(x => $"{namesplit[0] + x}{GenericTypeMarker}{namesplit[1]}"));
+ }
+ else
+ {
+ result.AddRange(candidateSuffixes.Select(x => name + x));
+ }
+ }
+
+ var withPrefix = result.SelectMany(y => methodNamePrefixes.Select(x => x + y)).ToList();
+ result.AddRange(withPrefix);
+
+ return result;
+ }
+ }
+ }
+}
diff --git a/src/ConfigurationProcessor.Core/MethodFilterFactory.cs b/src/ConfigurationProcessor.Core/MethodFilterFactory.cs
new file mode 100644
index 0000000..524d4b1
--- /dev/null
+++ b/src/ConfigurationProcessor.Core/MethodFilterFactory.cs
@@ -0,0 +1,24 @@
+// -------------------------------------------------------------------------------------------------
+// Copyright (c) almostchristian. All rights reserved.
+// -------------------------------------------------------------------------------------------------
+
+using System.Collections.Generic;
+using System.Reflection;
+
+namespace ConfigurationProcessor.Core
+{
+ ///
+ /// Delegate for filtering a candidate method.
+ ///
+ /// The method to evaluate.
+ /// The configuration name from the method filter factory.
+ /// True if the method is acceptable.
+ public delegate bool MethodFilter(MethodInfo methodInfo, string name);
+
+ ///
+ /// Creates method filters. See for generating factories.
+ ///
+ /// The configuration name.
+ /// Returns the method filter and the candidate names.
+ public delegate (MethodFilter Filter, IEnumerable CandidateNames) MethodFilterFactory(string name);
+}
diff --git a/src/ConfigurationProcessor.DependencyInjection/ConfigurationProcessorServiceCollectionExtensions.cs b/src/ConfigurationProcessor.DependencyInjection/ConfigurationProcessorServiceCollectionExtensions.cs
index 5ab946d..b1dc1f8 100644
--- a/src/ConfigurationProcessor.DependencyInjection/ConfigurationProcessorServiceCollectionExtensions.cs
+++ b/src/ConfigurationProcessor.DependencyInjection/ConfigurationProcessorServiceCollectionExtensions.cs
@@ -3,7 +3,6 @@
// -------------------------------------------------------------------------------------------------
using System;
-using System.Collections.Generic;
using System.Reflection;
using ConfigurationProcessor.Core;
using Microsoft.Extensions.Configuration;
@@ -18,23 +17,60 @@ public static class ConfigurationProcessorServiceCollectionExtensions
///
/// Adds services from configuration.
///
- /// The service collection.
+ /// The service collection.
+ /// The configuration to read from.
+ /// The config section.
+ /// The service collection for chaining.
+ /// Thrown when is null.
+ public static IServiceCollection AddFromConfiguration(
+ this IServiceCollection services,
+ IConfiguration configuration,
+ string servicesSection)
+ => services.AddFromConfiguration(configuration, servicesSection, null, default(MethodFilterFactory), default);
+
+ ///
+ /// Adds services from configuration.
+ ///
/// The service collection.
/// The configuration to read from.
/// The config section.
/// Additional service paths.
/// Candidate method name suffixes for matching.
- /// Additional methods that can be used for matching.
+ /// Additional methods that can be used for matching.
+ /// The service collection for chaining.
+ /// Thrown when is null.
+ public static IServiceCollection AddFromConfiguration(
+ this IServiceCollection services,
+ IConfiguration configuration,
+ string servicesSection,
+ string[]? servicePaths,
+ string[]? candidateMethodNameSuffixes,
+ MethodInfo[]? additionalMethods = null)
+ => services.AddFromConfiguration(
+ configuration,
+ servicesSection,
+ servicePaths,
+ candidateMethodNameSuffixes != null ? MethodFilterFactories.WithSuffixes(candidateMethodNameSuffixes) : null,
+ additionalMethods);
+
+ ///
+ /// Adds services from configuration.
+ ///
+ /// The service collection.
+ /// The configuration to read from.
+ /// The config section.
+ /// Additional service paths.
+ /// Factory for creating method filters.
+ /// Additional methods that can be used for matching.
/// The service collection for chaining.
/// Thrown when is null.
- public static TServices AddFromConfiguration(
- this TServices services,
+ public static IServiceCollection AddFromConfiguration(
+ this IServiceCollection services,
IConfiguration configuration,
string servicesSection,
- string[]? servicePaths = null,
- string[]? candidateMethodNameSuffixes = null,
- MethodInfo[]? surrogateMethods = null)
- where TServices : class, IEnumerable
+ string[]? servicePaths,
+ MethodFilterFactory? methodFilterFactory,
+ MethodInfo[]? additionalMethods = null)
{
if (configuration == null)
{
@@ -45,8 +81,8 @@ public static TServices AddFromConfiguration(
services,
servicesSection,
servicePaths,
- candidateMethodNameSuffixes,
- surrogateMethods);
+ methodFilterFactory,
+ additionalMethods);
}
}
}
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index c539f49..51ca338 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -5,7 +5,7 @@
- 0.9.5
+ 0.9.6
$(Version).$([System.DateTime]::Now.ToString(yy))$([System.DateTime]::Now.DayOfYear.ToString(000))
$(Version)
$(FileVersion)-$(GIT_VERSION)