diff --git a/WireMock.Net Solution.sln.DotSettings b/WireMock.Net Solution.sln.DotSettings index ace990494..d26576695 100644 --- a/WireMock.Net Solution.sln.DotSettings +++ b/WireMock.Net Solution.sln.DotSettings @@ -23,6 +23,7 @@ WWW XMS XUA + True True True True diff --git a/src/WireMock.Net/Plugin/PluginLoader.cs b/src/WireMock.Net/Plugin/PluginLoader.cs deleted file mode 100644 index b6f724ac3..000000000 --- a/src/WireMock.Net/Plugin/PluginLoader.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.IO; -using System.Linq; -using System.Reflection; - -namespace WireMock.Plugin; - -internal static class PluginLoader -{ - private static readonly ConcurrentDictionary Assemblies = new(); - - public static T Load(params object[] args) where T : class - { - var foundType = Assemblies.GetOrAdd(typeof(T), (type) => - { - var files = Directory.GetFiles(Directory.GetCurrentDirectory(), "*.dll"); - - Type? pluginType = null; - foreach (var file in files) - { - try - { - var assembly = Assembly.Load(new AssemblyName - { - Name = Path.GetFileNameWithoutExtension(file) - }); - - pluginType = GetImplementationTypeByInterface(assembly); - if (pluginType != null) - { - break; - } - } - catch - { - // no-op: just try next .dll - } - } - - if (pluginType != null) - { - return pluginType; - } - - throw new DllNotFoundException($"No dll found which implements type '{type}'"); - }); - - return (T)Activator.CreateInstance(foundType, args); - } - - private static Type? GetImplementationTypeByInterface(Assembly assembly) - { - return assembly.GetTypes().FirstOrDefault(t => typeof(T).IsAssignableFrom(t) && !t.GetTypeInfo().IsInterface); - } -} \ No newline at end of file diff --git a/src/WireMock.Net/Serialization/MatcherMapper.cs b/src/WireMock.Net/Serialization/MatcherMapper.cs index 5298c0276..94c068d29 100644 --- a/src/WireMock.Net/Serialization/MatcherMapper.cs +++ b/src/WireMock.Net/Serialization/MatcherMapper.cs @@ -8,7 +8,6 @@ using WireMock.Extensions; using WireMock.Matchers; using WireMock.Models; -using WireMock.Plugin; using WireMock.Settings; using WireMock.Util; @@ -53,7 +52,7 @@ public MatcherMapper(WireMockServerSettings settings) case "CSharpCodeMatcher": if (_settings.AllowCSharpCodeMatcher == true) { - return PluginLoader.Load(matchBehaviour, matchOperator, stringPatterns); + return TypeLoader.Load(matchBehaviour, matchOperator, stringPatterns); } throw new NotSupportedException("It's not allowed to use the 'CSharpCodeMatcher' because WireMockServerSettings.AllowCSharpCodeMatcher is not set to 'true'."); diff --git a/src/WireMock.Net/Util/TypeLoader.cs b/src/WireMock.Net/Util/TypeLoader.cs new file mode 100644 index 000000000..fcd27e779 --- /dev/null +++ b/src/WireMock.Net/Util/TypeLoader.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Reflection; +using Stef.Validation; + +namespace WireMock.Util; + +internal static class TypeLoader +{ + private static readonly ConcurrentDictionary Assemblies = new(); + + public static TInterface Load(params object[] args) where TInterface : class + { + var key = typeof(TInterface).FullName!; + + var pluginType = Assemblies.GetOrAdd(key, _ => + { + if (TryFindTypeInDlls(null, out var foundType)) + { + return foundType; + } + + throw new DllNotFoundException($"No dll found which implements Interface '{key}'."); + }); + + return (TInterface)Activator.CreateInstance(pluginType, args)!; + } + + public static TInterface LoadByFullName(string implementationTypeFullName, params object[] args) where TInterface : class + { + Guard.NotNullOrEmpty(implementationTypeFullName); + + var @interface = typeof(TInterface).FullName; + var key = $"{@interface}_{implementationTypeFullName}"; + + var pluginType = Assemblies.GetOrAdd(key, _ => + { + if (TryFindTypeInDlls(implementationTypeFullName, out var foundType)) + { + return foundType; + } + + throw new DllNotFoundException($"No dll found which implements Interface '{@interface}' and has FullName '{implementationTypeFullName}'."); + }); + + return (TInterface)Activator.CreateInstance(pluginType, args)!; + } + + private static bool TryFindTypeInDlls(string? implementationTypeFullName, [NotNullWhen(true)] out Type? pluginType) where TInterface : class + { + foreach (var file in Directory.GetFiles(Directory.GetCurrentDirectory(), "*.dll")) + { + try + { + var assembly = Assembly.Load(new AssemblyName + { + Name = Path.GetFileNameWithoutExtension(file) + }); + + if (TryGetImplementationTypeByInterfaceAndOptionalFullName(assembly, implementationTypeFullName, out pluginType)) + { + return true; + } + } + catch + { + // no-op: just try next .dll + } + } + + pluginType = null; + return false; + } + + private static bool TryGetImplementationTypeByInterfaceAndOptionalFullName(Assembly assembly, string? implementationTypeFullName, [NotNullWhen(true)] out Type? type) + { + type = assembly + .GetTypes() + .FirstOrDefault(t => + typeof(T).IsAssignableFrom(t) && !t.GetTypeInfo().IsInterface && + (implementationTypeFullName == null || string.Equals(t.FullName, implementationTypeFullName, StringComparison.OrdinalIgnoreCase)) + ); + + return type != null; + } +} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/Plugin/PluginLoaderTests.cs b/test/WireMock.Net.Tests/Plugin/PluginLoaderTests.cs deleted file mode 100644 index 25476def4..000000000 --- a/test/WireMock.Net.Tests/Plugin/PluginLoaderTests.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using AnyOfTypes; -using FluentAssertions; -using WireMock.Matchers; -using WireMock.Models; -using WireMock.Plugin; -using Xunit; - -namespace WireMock.Net.Tests.Plugin; - -public class PluginLoaderTests -{ - public interface IDummy - { - } - - [Fact] - public void Load_Valid() - { - // Act - AnyOf pattern = "x"; - var result = PluginLoader.Load(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, pattern); - - // Assert - result.Should().NotBeNull(); - } - - [Fact] - public void Load_Invalid_ThrowsException() - { - // Act - Action a = () => PluginLoader.Load(); - - // Assert - a.Should().Throw(); - } -} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/Util/TypeLoaderTests.cs b/test/WireMock.Net.Tests/Util/TypeLoaderTests.cs new file mode 100644 index 000000000..d289e5483 --- /dev/null +++ b/test/WireMock.Net.Tests/Util/TypeLoaderTests.cs @@ -0,0 +1,65 @@ +using System; +using AnyOfTypes; +using FluentAssertions; +using WireMock.Matchers; +using WireMock.Models; +using WireMock.Util; +using Xunit; + +namespace WireMock.Net.Tests.Util; + +public class TypeLoaderTests +{ + public interface IDummyInterfaceNoImplementation + { + } + + public interface IDummyInterfaceWithImplementation + { + } + + public class DummyClass : IDummyInterfaceWithImplementation + { + } + + [Fact] + public void Load_ByInterface() + { + // Act + AnyOf pattern = "x"; + var result = TypeLoader.Load(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, pattern); + + // Assert + result.Should().NotBeNull(); + } + + [Fact] + public void Load_ByInterfaceAndFullName() + { + // Act + var result = TypeLoader.LoadByFullName(typeof(DummyClass).FullName!); + + // Assert + result.Should().BeOfType(); + } + + [Fact] + public void Load_ByInterface_ButNoImplementationFoundForInterface_ThrowsException() + { + // Act + Action a = () => TypeLoader.Load(); + + // Assert + a.Should().Throw().WithMessage("No dll found which implements Interface 'WireMock.Net.Tests.Util.TypeLoaderTests+IDummyInterfaceNoImplementation'."); + } + + [Fact] + public void Load_ByInterfaceAndFullName_ButNoImplementationFoundForInterface_ThrowsException() + { + // Act + Action a = () => TypeLoader.LoadByFullName("xyz"); + + // Assert + a.Should().Throw().WithMessage("No dll found which implements Interface 'WireMock.Net.Tests.Util.TypeLoaderTests+IDummyInterfaceWithImplementation' and has FullName 'xyz'."); + } +} \ No newline at end of file