Fix TypeLoader (#1320)

* no ilmerge

* .

* .

* nullable

* .UsingNuGet

* fix

* .

* directoriesToSearch

* .
This commit is contained in:
Stef Heyenrath
2025-06-15 11:44:09 +02:00
committed by GitHub
parent 70a9180af4
commit 7b93b2668d
12 changed files with 323 additions and 26 deletions

View File

@@ -22,6 +22,9 @@ jobs:
- name: 'WireMock.Net.Tests' - name: 'WireMock.Net.Tests'
run: dotnet test './test/WireMock.Net.Tests/WireMock.Net.Tests.csproj' -c Release --framework net8.0 run: dotnet test './test/WireMock.Net.Tests/WireMock.Net.Tests.csproj' -c Release --framework net8.0
- name: 'WireMock.Net.Tests.UsingNuGet'
run: dotnet test './test/WireMock.Net.Tests.UsingNuGet/WireMock.Net.Tests.UsingNuGet.csproj' -c Release
- name: 'WireMock.Net.TUnitTests' - name: 'WireMock.Net.TUnitTests'
run: dotnet test './test/WireMock.Net.TUnitTests/WireMock.Net.TUnitTests.csproj' -c Release --framework net8.0 run: dotnet test './test/WireMock.Net.TUnitTests/WireMock.Net.TUnitTests.csproj' -c Release --framework net8.0
@@ -46,6 +49,9 @@ jobs:
- name: 'WireMock.Net.Tests' - name: 'WireMock.Net.Tests'
run: dotnet test './test/WireMock.Net.Tests/WireMock.Net.Tests.csproj' -c Release --framework net8.0 run: dotnet test './test/WireMock.Net.Tests/WireMock.Net.Tests.csproj' -c Release --framework net8.0
- name: 'WireMock.Net.Tests.UsingNuGet'
run: dotnet test './test/WireMock.Net.Tests.UsingNuGet/WireMock.Net.Tests.UsingNuGet.csproj' -c Release
- name: 'WireMock.Net.TUnitTests' - name: 'WireMock.Net.TUnitTests'
run: dotnet test './test/WireMock.Net.TUnitTests/WireMock.Net.TUnitTests.csproj' -c Release --framework net8.0 run: dotnet test './test/WireMock.Net.TUnitTests/WireMock.Net.TUnitTests.csproj' -c Release --framework net8.0

View File

@@ -132,6 +132,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Shared", "src\
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Minimal", "src\WireMock.Net.Minimal\WireMock.Net.Minimal.csproj", "{BFEF8990-65B3-4274-310F-7355F0B84035}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Minimal", "src\WireMock.Net.Minimal\WireMock.Net.Minimal.csproj", "{BFEF8990-65B3-4274-310F-7355F0B84035}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.ConsoleApp.UsingNuGet", "examples\WireMock.Net.ConsoleApp.UsingNuGet\WireMock.Net.ConsoleApp.UsingNuGet.csproj", "{1F80A6E6-D146-4E40-9EA8-49DB8494239F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WireMock.Net.Tests.UsingNuGet", "test\WireMock.Net.Tests.UsingNuGet\WireMock.Net.Tests.UsingNuGet.csproj", "{BBA332C6-28A9-42E7-9C4D-A0816E52A198}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -314,6 +318,14 @@ Global
{BFEF8990-65B3-4274-310F-7355F0B84035}.Debug|Any CPU.Build.0 = Debug|Any CPU {BFEF8990-65B3-4274-310F-7355F0B84035}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BFEF8990-65B3-4274-310F-7355F0B84035}.Release|Any CPU.ActiveCfg = Release|Any CPU {BFEF8990-65B3-4274-310F-7355F0B84035}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BFEF8990-65B3-4274-310F-7355F0B84035}.Release|Any CPU.Build.0 = Release|Any CPU {BFEF8990-65B3-4274-310F-7355F0B84035}.Release|Any CPU.Build.0 = Release|Any CPU
{1F80A6E6-D146-4E40-9EA8-49DB8494239F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1F80A6E6-D146-4E40-9EA8-49DB8494239F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1F80A6E6-D146-4E40-9EA8-49DB8494239F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1F80A6E6-D146-4E40-9EA8-49DB8494239F}.Release|Any CPU.Build.0 = Release|Any CPU
{BBA332C6-28A9-42E7-9C4D-A0816E52A198}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BBA332C6-28A9-42E7-9C4D-A0816E52A198}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BBA332C6-28A9-42E7-9C4D-A0816E52A198}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BBA332C6-28A9-42E7-9C4D-A0816E52A198}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -365,6 +377,8 @@ Global
{F8B4A93E-46EF-4237-88FE-15FDAB7635D4} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} {F8B4A93E-46EF-4237-88FE-15FDAB7635D4} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{D3804228-91F4-4502-9595-39584E5A0177} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} {D3804228-91F4-4502-9595-39584E5A0177} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{BFEF8990-65B3-4274-310F-7355F0B84035} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2} {BFEF8990-65B3-4274-310F-7355F0B84035} = {8F890C6F-9ACC-438D-928A-AD61CDA862F2}
{1F80A6E6-D146-4E40-9EA8-49DB8494239F} = {985E0ADB-D4B4-473A-AA40-567E279B7946}
{BBA332C6-28A9-42E7-9C4D-A0816E52A198} = {0BB8B634-407A-4610-A91F-11586990767A}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458} SolutionGuid = {DC539027-9852-430C-B19F-FD035D018458}

View File

@@ -109,6 +109,13 @@ jobs:
packageType: 'sdk' packageType: 'sdk'
version: '8.0.x' version: '8.0.x'
- task: DotNetCoreCLI@2
displayName: 'WireMock.Net.Tests.UsingNuGet'
inputs:
command: 'test'
projects: './test/WireMock.Net.Tests.UsingNuGet/WireMock.Net.Tests.UsingNuGet.csproj'
arguments: '--configuration Release'
- task: DotNetCoreCLI@2 - task: DotNetCoreCLI@2
displayName: 'WireMock.Net.Tests with Coverage' displayName: 'WireMock.Net.Tests with Coverage'
inputs: inputs:

View File

@@ -0,0 +1,88 @@
// Copyright © WireMock.Net
using System.Net.Http.Headers;
using System.Text;
using WireMock.Matchers;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Server;
Directory.SetCurrentDirectory(Path.GetTempPath());
using var server = WireMockServer.Start();
var textPlainContent = "This is some plain text";
var textPlainContentType = "text/plain";
var textPlainContentTypeMatcher = new ContentTypeMatcher(textPlainContentType);
var textPlainContentMatcher = new ExactMatcher(textPlainContent);
var textPlainMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, textPlainContentTypeMatcher, null, null, textPlainContentMatcher);
var textJson = "{ \"Key\" : \"Value\" }";
var textJsonContentType = "text/json";
var textJsonContentTypeMatcher = new ContentTypeMatcher(textJsonContentType);
var textJsonContentMatcher = new JsonMatcher(new { Key = "Value" }, true);
var jsonMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, textJsonContentTypeMatcher, null, null, textJsonContentMatcher);
var imagePngBytes = Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAgMAAAAP2OW3AAAADFBMVEX/tID/vpH/pWX/sHidUyjlAAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC");
var imagePngContentMatcher = new ExactObjectMatcher(imagePngBytes);
var imagePngMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, null, null, null, imagePngContentMatcher);
var matchers = new IMatcher[]
{
textPlainMatcher,
jsonMatcher,
imagePngMatcher
};
server
.Given(Request.Create()
.UsingPost()
.WithPath("/multipart")
.WithMultiPart(matchers)
)
.RespondWith(Response.Create()
.WithBodyAsJson(new
{
Method = "{{request.Method}}",
BodyAsMimeMessage = "{{request.BodyAsMimeMessage.TextBody}}"
})
.WithTransformer()
);
server
.Given(Request.Create()
.UsingPost()
.WithPath("/multipart2")
.WithMultiPart(matchers)
)
.RespondWith(Response.Create()
.WithBody(request =>
{
if (request.BodyAsMimeMessage == null)
{
throw new InvalidProgramException("Not expected");
}
return "OK";
})
.WithTransformer()
);
var formDataContent = new MultipartFormDataContent
{
{ new StringContent(textPlainContent, Encoding.UTF8, textPlainContentType), "text" },
{ new StringContent(textJson, Encoding.UTF8, textJsonContentType), "json" }
};
var fileContent = new ByteArrayContent(imagePngBytes);
fileContent.Headers.ContentType = new MediaTypeHeaderValue("image/png");
formDataContent.Add(fileContent, "somefile", "image.png");
var client = server.CreateClient();
var response = await client.PostAsync("/multipart", formDataContent);
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
var response2 = await client.PostAsync("/multipart2", formDataContent);
var content2 = await response2.Content.ReadAsStringAsync();
Console.WriteLine(content2);

View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\WireMock.Net\WireMock.Net.csproj" />
</ItemGroup>
<!--<ItemGroup>
<PackageReference Include="WireMock.Net" Version="1.8.11" />
</ItemGroup>-->
</Project>

View File

@@ -28,7 +28,7 @@ internal class MimeKitUtils : IMimeKitUtils
if (requestMessage.BodyData != null && if (requestMessage.BodyData != null &&
requestMessage.Headers?.TryGetValue(HttpKnownHeaderNames.ContentType, out var contentTypeHeader) == true && requestMessage.Headers?.TryGetValue(HttpKnownHeaderNames.ContentType, out var contentTypeHeader) == true &&
StartsWithMultiPart(contentTypeHeader) // Only parse when "multipart/mixed" StartsWithMultiPart(contentTypeHeader)
) )
{ {
var bytes = requestMessage.BodyData?.DetectedBodyType switch var bytes = requestMessage.BodyData?.DetectedBodyType switch

View File

@@ -36,9 +36,9 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="PolySharp" Version="1.15.0"> <PackageReference Include="Nullable" Version="1.3.1">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Stef.Validation" Version="0.1.1" /> <PackageReference Include="Stef.Validation" Version="0.1.1" />
</ItemGroup> </ItemGroup>

View File

@@ -51,7 +51,7 @@ internal static class TypeLoader
{ {
var key = typeof(TInterface).FullName!; var key = typeof(TInterface).FullName!;
var pluginType = Assemblies.GetOrAdd(key, _ => return Assemblies.GetOrAdd(key, _ =>
{ {
if (TryFindTypeInDlls<TInterface>(null, out var foundType)) if (TryFindTypeInDlls<TInterface>(null, out var foundType))
{ {
@@ -60,7 +60,6 @@ internal static class TypeLoader
throw new DllNotFoundException($"No dll found which implements interface '{key}'."); throw new DllNotFoundException($"No dll found which implements interface '{key}'.");
}); });
return pluginType;
} }
private static Type GetPluginTypeByFullName<TInterface>(string implementationTypeFullName) where TInterface : class private static Type GetPluginTypeByFullName<TInterface>(string implementationTypeFullName) where TInterface : class
@@ -68,7 +67,7 @@ internal static class TypeLoader
var @interface = typeof(TInterface).FullName; var @interface = typeof(TInterface).FullName;
var key = $"{@interface}_{implementationTypeFullName}"; var key = $"{@interface}_{implementationTypeFullName}";
var pluginType = Assemblies.GetOrAdd(key, _ => return Assemblies.GetOrAdd(key, _ =>
{ {
if (TryFindTypeInDlls<TInterface>(implementationTypeFullName, out var foundType)) if (TryFindTypeInDlls<TInterface>(implementationTypeFullName, out var foundType))
{ {
@@ -77,28 +76,40 @@ internal static class TypeLoader
throw new DllNotFoundException($"No dll found which implements Interface '{@interface}' and has FullName '{implementationTypeFullName}'."); throw new DllNotFoundException($"No dll found which implements Interface '{@interface}' and has FullName '{implementationTypeFullName}'.");
}); });
return pluginType;
} }
private static bool TryFindTypeInDlls<TInterface>(string? implementationTypeFullName, [NotNullWhen(true)] out Type? pluginType) where TInterface : class private static bool TryFindTypeInDlls<TInterface>(string? implementationTypeFullName, [NotNullWhen(true)] out Type? pluginType) where TInterface : class
{ {
foreach (var file in Directory.GetFiles(Directory.GetCurrentDirectory(), "*.dll")) #if NETSTANDARD1_3
var directoriesToSearch = new[] { AppContext.BaseDirectory };
#else
var processDirectory = Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName);
var assemblyDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var directoriesToSearch = new[] { processDirectory, assemblyDirectory }
.Where(d => !string.IsNullOrEmpty(d))
.Distinct()
.ToArray();
#endif
foreach (var directory in directoriesToSearch)
{ {
try foreach (var file in Directory.GetFiles(directory!, "*.dll"))
{ {
var assembly = Assembly.Load(new AssemblyName try
{ {
Name = Path.GetFileNameWithoutExtension(file) var assembly = Assembly.Load(new AssemblyName
}); {
Name = Path.GetFileNameWithoutExtension(file)
});
if (TryGetImplementationTypeByInterfaceAndOptionalFullName<TInterface>(assembly, implementationTypeFullName, out pluginType)) if (TryGetImplementationTypeByInterfaceAndOptionalFullName<TInterface>(assembly, implementationTypeFullName, out pluginType))
{ {
return true; return true;
}
}
catch
{
// no-op: just try next .dll
} }
}
catch
{
// no-op: just try next .dll
} }
} }

View File

@@ -65,7 +65,7 @@
<PackageReference Include="JmesPath.Net" Version="1.0.330" /> <PackageReference Include="JmesPath.Net" Version="1.0.330" />
<PackageReference Include="AnyOf" Version="0.4.0" /> <PackageReference Include="AnyOf" Version="0.4.0" />
<PackageReference Include="TinyMapper.Signed" Version="4.0.0" /> <PackageReference Include="TinyMapper.Signed" Version="4.0.0" />
<PackageReference Include="PolySharp" Version="1.15.0"> <PackageReference Include="Nullable" Version="1.3.1">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

View File

@@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<PropertyGroup>
<DefineConstants>$(DefineConstants);GRAPHQL;MIMEKIT;PROTOBUF</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AwesomeAssertions" Version="9.0.0" />
<PackageReference Include="coverlet.collector" Version="6.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="WireMock.Net" Version="1.8.11" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<PackageReference Update="SonarAnalyzer.CSharp" Version="10.11.0.117924" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,103 @@
// Copyright © WireMock.Net
using System.Net;
using System.Net.Http.Headers;
using System.Text;
using AwesomeAssertions;
using WireMock.Matchers;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Server;
// ReSharper disable once CheckNamespace
namespace WireMock.Net.Tests;
public partial class WireMockServerTests
{
[Fact]
public async Task WireMockServer_WithMultiPartBody_Using_MimePartMatchers()
{
// Arrange
using var server = WireMockServer.Start();
var textPlainContent = "This is some plain text";
var textPlainContentType = "text/plain";
var textPlainContentTypeMatcher = new ContentTypeMatcher(textPlainContentType);
var textPlainContentMatcher = new ExactMatcher(textPlainContent);
var textPlainMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, textPlainContentTypeMatcher, null, null, textPlainContentMatcher);
var textJson = "{ \"Key\" : \"Value\" }";
var textJsonContentType = "text/json";
var textJsonContentTypeMatcher = new ContentTypeMatcher(textJsonContentType);
var textJsonContentMatcher = new JsonMatcher(new { Key = "Value" }, true);
var jsonMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, textJsonContentTypeMatcher, null, null, textJsonContentMatcher);
var imagePngBytes = Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAAIAAAACAgMAAAAP2OW3AAAADFBMVEX/tID/vpH/pWX/sHidUyjlAAAADElEQVR4XmMQYNgAAADkAMHebX3mAAAAAElFTkSuQmCC");
var imagePngContentMatcher = new ExactObjectMatcher(imagePngBytes);
var imagePngMatcher = new MimePartMatcher(MatchBehaviour.AcceptOnMatch, null, null, null, imagePngContentMatcher);
var matchers = new IMatcher[]
{
textPlainMatcher,
jsonMatcher,
imagePngMatcher
};
server
.Given(Request.Create()
.UsingPost()
.WithPath("/multipart")
.WithMultiPart(matchers)
)
.RespondWith(Response.Create()
.WithBody("{{request.Method}};{{request.BodyAsMimeMessage.TextBody}}")
.WithTransformer()
);
server
.Given(Request.Create()
.UsingPost()
.WithPath("/multipart2")
.WithMultiPart(matchers)
)
.RespondWith(Response.Create()
.WithBody(request =>
{
if (request.BodyAsMimeMessage == null)
{
throw new InvalidProgramException("Not expected");
}
return "OK";
})
.WithTransformer()
);
var formDataContent = new MultipartFormDataContent
{
{ new StringContent(textPlainContent, Encoding.UTF8, textPlainContentType), "text" },
{ new StringContent(textJson, Encoding.UTF8, textJsonContentType), "json" }
};
var fileContent = new ByteArrayContent(imagePngBytes);
fileContent.Headers.ContentType = new MediaTypeHeaderValue("image/png");
formDataContent.Add(fileContent, "somefile", "image.png");
var client = server.CreateClient();
// Act 1
var response1 = await client.PostAsync("/multipart", formDataContent);
// Assert 1
response1.StatusCode.Should().Be(HttpStatusCode.OK);
var content1 = await response1.Content.ReadAsStringAsync();
content1.Should().Be("POST;This is some plain text");
// Act 2
var response2 = await client.PostAsync("/multipart2", formDataContent);
// Assert 1
response2.StatusCode.Should().Be(HttpStatusCode.OK);
var content2 = await response2.Content.ReadAsStringAsync();
content2.Should().Be("OK");
}
}

View File

@@ -1,6 +1,7 @@
// Copyright © WireMock.Net // Copyright © WireMock.Net
using System; using System;
using System.IO;
using AnyOfTypes; using AnyOfTypes;
using FluentAssertions; using FluentAssertions;
using WireMock.Matchers; using WireMock.Matchers;
@@ -57,12 +58,22 @@ public class TypeLoaderTests
[Fact] [Fact]
public void LoadNewInstance() public void LoadNewInstance()
{ {
// Act var current = Directory.GetCurrentDirectory();
AnyOf<string, StringPattern> pattern = "x"; try
var result = TypeLoader.LoadNewInstance<ICSharpCodeMatcher>(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, pattern); {
Directory.SetCurrentDirectory(Path.GetTempPath());
// Assert // Act
result.Should().NotBeNull(); AnyOf<string, StringPattern> pattern = "x";
var result = TypeLoader.LoadNewInstance<ICSharpCodeMatcher>(MatchBehaviour.AcceptOnMatch, MatchOperator.Or, pattern);
// Assert
result.Should().NotBeNull();
}
finally
{
Directory.SetCurrentDirectory(current);
}
} }
[Fact] [Fact]