Skip to content

Commit a2c8518

Browse files
committed
Extend PluginLoader
1 parent 2364866 commit a2c8518

File tree

3 files changed

+96
-34
lines changed

3 files changed

+96
-34
lines changed

WireMock.Net Solution.sln.DotSettings

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=WWW/@EntryIndexedValue">WWW</s:String>
2424
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=XMS/@EntryIndexedValue">XMS</s:String>
2525
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=XUA/@EntryIndexedValue">XUA</s:String>
26+
<s:Boolean x:Key="/Default/UserDictionary/Words/=Dlls/@EntryIndexedValue">True</s:Boolean>
2627
<s:Boolean x:Key="/Default/UserDictionary/Words/=Flurl/@EntryIndexedValue">True</s:Boolean>
2728
<s:Boolean x:Key="/Default/UserDictionary/Words/=funcs/@EntryIndexedValue">True</s:Boolean>
2829
<s:Boolean x:Key="/Default/UserDictionary/Words/=Grpc/@EntryIndexedValue">True</s:Boolean>
+62-29
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,89 @@
11
using System;
22
using System.Collections.Concurrent;
3+
using System.Diagnostics.CodeAnalysis;
34
using System.IO;
45
using System.Linq;
56
using System.Reflection;
7+
using Stef.Validation;
68

79
namespace WireMock.Plugin;
810

911
internal static class PluginLoader
1012
{
11-
private static readonly ConcurrentDictionary<Type, Type> Assemblies = new();
13+
private static readonly ConcurrentDictionary<string, Type> Assemblies = new();
1214

13-
public static T Load<T>(params object[] args) where T : class
15+
public static TInterface Load<TInterface>(params object[] args) where TInterface : class
1416
{
15-
var foundType = Assemblies.GetOrAdd(typeof(T), (type) =>
17+
var key = typeof(TInterface).FullName!;
18+
19+
var pluginType = Assemblies.GetOrAdd(key, _ =>
1620
{
17-
var files = Directory.GetFiles(Directory.GetCurrentDirectory(), "*.dll");
21+
if (TryFindTypeInDlls<TInterface>(null, out var foundType))
22+
{
23+
return foundType;
24+
}
25+
26+
throw new DllNotFoundException($"No dll found which implements Interface '{key}'.");
27+
});
28+
29+
return (TInterface)Activator.CreateInstance(pluginType, args)!;
30+
}
31+
32+
public static TInterface LoadByFullName<TInterface>(string implementationTypeFullName, params object[] args) where TInterface : class
33+
{
34+
Guard.NotNullOrEmpty(implementationTypeFullName);
35+
36+
var @interface = typeof(TInterface).FullName;
37+
var key = $"{@interface}_{implementationTypeFullName}";
38+
39+
var pluginType = Assemblies.GetOrAdd(key, _ =>
40+
{
41+
if (TryFindTypeInDlls<TInterface>(implementationTypeFullName, out var foundType))
42+
{
43+
return foundType;
44+
}
45+
46+
throw new DllNotFoundException($"No dll found which implements Interface '{@interface}' and has FullName '{implementationTypeFullName}'.");
47+
});
1848

19-
Type? pluginType = null;
20-
foreach (var file in files)
49+
return (TInterface)Activator.CreateInstance(pluginType, args)!;
50+
}
51+
52+
private static bool TryFindTypeInDlls<TInterface>(string? implementationTypeFullName, [NotNullWhen(true)] out Type? pluginType) where TInterface : class
53+
{
54+
foreach (var file in Directory.GetFiles(Directory.GetCurrentDirectory(), "*.dll"))
55+
{
56+
try
2157
{
22-
try
58+
var assembly = Assembly.Load(new AssemblyName
2359
{
24-
var assembly = Assembly.Load(new AssemblyName
25-
{
26-
Name = Path.GetFileNameWithoutExtension(file)
27-
});
28-
29-
pluginType = GetImplementationTypeByInterface<T>(assembly);
30-
if (pluginType != null)
31-
{
32-
break;
33-
}
34-
}
35-
catch
60+
Name = Path.GetFileNameWithoutExtension(file)
61+
});
62+
63+
if (TryGetImplementationTypeByInterfaceAndOptionalFullName<TInterface>(assembly, implementationTypeFullName, out pluginType))
3664
{
37-
// no-op: just try next .dll
65+
return true;
3866
}
3967
}
40-
41-
if (pluginType != null)
68+
catch
4269
{
43-
return pluginType;
70+
// no-op: just try next .dll
4471
}
72+
}
4573

46-
throw new DllNotFoundException($"No dll found which implements type '{type}'");
47-
});
48-
49-
return (T)Activator.CreateInstance(foundType, args);
74+
pluginType = null;
75+
return false;
5076
}
5177

52-
private static Type? GetImplementationTypeByInterface<T>(Assembly assembly)
78+
private static bool TryGetImplementationTypeByInterfaceAndOptionalFullName<T>(Assembly assembly, string? implementationTypeFullName, [NotNullWhen(true)] out Type? type)
5379
{
54-
return assembly.GetTypes().FirstOrDefault(t => typeof(T).IsAssignableFrom(t) && !t.GetTypeInfo().IsInterface);
80+
type = assembly
81+
.GetTypes()
82+
.FirstOrDefault(t =>
83+
typeof(T).IsAssignableFrom(t) && !t.GetTypeInfo().IsInterface &&
84+
implementationTypeFullName == null || string.Equals(t.FullName, implementationTypeFullName, StringComparison.OrdinalIgnoreCase)
85+
);
86+
87+
return type != null;
5588
}
5689
}

test/WireMock.Net.Tests/Plugin/PluginLoaderTests.cs

+33-5
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,20 @@ namespace WireMock.Net.Tests.Plugin;
1010

1111
public class PluginLoaderTests
1212
{
13-
public interface IDummy
13+
public interface IDummyInterfaceNoImplementation
14+
{
15+
}
16+
17+
public interface IDummyInterfaceWithImplementation
18+
{
19+
}
20+
21+
public class DummyClass : IDummyInterfaceWithImplementation
1422
{
1523
}
1624

1725
[Fact]
18-
public void Load_Valid()
26+
public void Load_ByInterface()
1927
{
2028
// Act
2129
AnyOf<string, StringPattern> pattern = "x";
@@ -26,12 +34,32 @@ public void Load_Valid()
2634
}
2735

2836
[Fact]
29-
public void Load_Invalid_ThrowsException()
37+
public void Load_ByInterfaceAndFullName()
38+
{
39+
// Act
40+
var result = PluginLoader.LoadByFullName<IDummyInterfaceWithImplementation>(typeof(DummyClass).FullName);
41+
42+
// Assert
43+
result.Should().BeOfType<DummyClass>();
44+
}
45+
46+
[Fact]
47+
public void Load_ByInterface_ButNoImplementationFoundForInterface_ThrowsException()
48+
{
49+
// Act
50+
Action a = () => PluginLoader.Load<IDummyInterfaceNoImplementation>();
51+
52+
// Assert
53+
a.Should().Throw<DllNotFoundException>().WithMessage("No dll found which implements Interface 'WireMock.Net.Tests.Plugin.PluginLoaderTests+IDummyInterfaceNoImplementation'.");
54+
}
55+
56+
[Fact]
57+
public void Load_ByInterfaceAndFullName_ButNoImplementationFoundForInterface_ThrowsException()
3058
{
3159
// Act
32-
Action a = () => PluginLoader.Load<IDummy>();
60+
Action a = () => PluginLoader.LoadByFullName<IDummyInterfaceWithImplementation>("xyz");
3361

3462
// Assert
35-
a.Should().Throw<DllNotFoundException>();
63+
a.Should().Throw<DllNotFoundException>().WithMessage("No dll found which implements Interface 'WireMock.Net.Tests.Plugin.PluginLoaderTests+IDummyInterfaceWithImplementation' and has FullName 'xyz'.");
3664
}
3765
}

0 commit comments

Comments
 (0)